diff --git a/DEPS b/DEPS
index eb18453..2fada0a 100644
--- a/DEPS
+++ b/DEPS
@@ -253,11 +253,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '66efe821b72f995f9e9a32cfd7050cba27a0e3aa',
+  'skia_revision': '3d381dcbd2fd94eead35d3fb95cb4af2b86f152a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '84e10038cd86725be18eab63e6866d2091874f52',
+  'v8_revision': 'f311a18650a2c679503e80242d75be86fd41ea60',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -265,7 +265,7 @@
   # 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': 'c27e99245d423648fe5f0eafa73d49ce7b53d201',
+  'swiftshader_revision': 'b3b1a3fe8351b2feae986b124142cc74e0aa4e8e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -328,7 +328,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': 'b9d36166b65b2ab9666f5c9e5eb1003224b0f6fa',
+  'devtools_frontend_revision': '4d17e989f0f5bad0f9d4d5badff16fd6da09ae33',
   # 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.
@@ -368,7 +368,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.
-  'dawn_revision': '2baa3467d996446db4d9a62139da9ac7ff7838bd',
+  'dawn_revision': '25d7e333206c14edc865b7be4ecebb93557abece',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -809,7 +809,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': 'P4qBSseJmvWmZlQIzoHQ4DMHg0GKIBtDtKL6P3UQoUUC',
+          'version': 'eEKxTcWI5TCbL_dNqak7VgCt705-dYKLw6O697CNiSoC',
         },
       ],
       'dep_type': 'cipd',
@@ -820,7 +820,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'tpt6BzVqkkpz2j2fGso8GkB1rcdgDjqBKDUKOoSjL8oC',
+          'version': 'nXXmYVXgcExxRTjx7e9SgpsJzxsTolp9ObHPfX1DKXIC',
         },
       ],
       'dep_type': 'cipd',
@@ -892,7 +892,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'o519o3phZFQGY6K0mO0EJ-xJQUoucapeX-jwNBAal5wC',
+          'version': '4144ITIgXUisP3mBnV8td3mdaIKKku5UW_bQjgoP9r8C',
       },
     ],
     'condition': 'checkout_android',
@@ -1100,7 +1100,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '71d6cd90214d4ae857c1a94e21ef2cd0175a8d3d',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '32317b1903affb8798ac58ecc15ab503d5bb6127',
       'condition': 'checkout_chromeos',
   },
 
@@ -1503,7 +1503,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'bc7a9c2bb682f97e2c5666bf19ec0a742c26ae26',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'c899a37e18fcc1e4f0fcac2b648e491ac2e6c18a',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1581,7 +1581,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/android/aemu/release/linux-amd64',
-              'version': 'ua1BrIds-RI-QFJX8UfAJzEgMzmNPkYDPbV_WSf2pG8C'
+              'version': 'Jd7H1L5yq-3E50xh7PJcQXhYKh4arojFgYADEd9s8x0C'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1600,7 +1600,7 @@
   },
 
   'src/third_party/re2/src':
-    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + 'f2eff71ac32b81ecb93396f9e51973f52a22c4d4',
+    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + '1a7dc61c03941e8fd4e6ff21c2a466bb8efbaa8d',
 
   'src/third_party/r8': {
       'packages': [
@@ -1721,7 +1721,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'cf04aebdf9b53bb2853f22a81465688daf879ec6',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'd4ab434e91c1a578f6a43c7418eae2b92856ee05',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '90654f28f4092a4993e91726cb18f697aefb201f',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + '8f56242e984022821f1b630c94e107d0006d4b64',
@@ -1794,7 +1794,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c81527fdf3300c59c7f4c941c2f5dba1f6b674d0',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@4a50cf59e3f0b22d23e653cd48ccf51fa5347ff6',
     'condition': 'checkout_src_internal',
   },
 
@@ -1824,7 +1824,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'qmOdhceXhbiRQaLH3daJFi-BnBFE5TYeDhX1dLyfR9oC',
+        'version': 'pT8LaiqCoa5lm3HWqD8ZoHWiCo_hdvy_NhldI8QQjtwC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1835,7 +1835,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': '-qW-FqtbWmLBB13-MErgAEDAU-LgGUuuiugd7q0N2uwC',
+        'version': 'jrLhP-V80o1Z-u1LbNe_K_dPWWsnqa-DeUgZFO7yL_kC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 2ebda3e3..89a8ccd 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -7,6 +7,11 @@
 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
 for more details about the presubmit API built into depot_tools.
 """
+
+from typing import Optional
+from typing import Sequence
+from dataclasses import dataclass
+
 PRESUBMIT_VERSION = '2.0.0'
 
 # This line is 'magic' in that git-cl looks for it to decide whether to
@@ -104,51 +109,75 @@
     'release apk.')
 
 
-_INCLUDE_ORDER_WARNING = (
-    'Your #include order seems to be broken. Remember to use the right '
-    'collation (LC_COLLATE=C) and check\nhttps://google.github.io/styleguide/'
-    'cppguide.html#Names_and_Order_of_Includes')
+@dataclass
+class BanRule:
+  # String pattern. If the pattern begins with a slash, the pattern will be
+  # treated as a regular expression instead.
+  pattern: str
+  # Explanation as a sequence of strings. Each string in the sequence will be
+  # printed on its own line.
+  explanation: Sequence[str]
+  # Whether or not to treat this ban as a fatal error. If unspecified, defaults
+  # to true.
+  treat_as_error: Optional[bool] = None
+  # Paths that should be excluded from the ban check. Each string is a regular
+  # expression that will be matched against the path of the file being checked
+  # relative to the root of the source tree.
+  excluded_paths: Optional[Sequence[str]] = None
 
-# Format: Sequence of tuples containing:
-# * Full import path.
-# * Sequence of strings to show when the pattern matches.
-# * Sequence of path or filename exceptions to this rule
-_BANNED_JAVA_IMPORTS = ((
-    'java.net.URI;',
-    ('Use org.chromium.url.GURL instead of java.net.URI, where possible.', ),
-    (
-        'net/android/javatests/src/org/chromium/net/'
-        'AndroidProxySelectorTest.java',
-        'components/cronet/',
-        'third_party/robolectric/local/',
+
+# Format: Sequence of BanRule:
+_BANNED_JAVA_IMPORTS = (
+    BanRule(
+      'import java.net.URI;',
+      (
+       'Use org.chromium.url.GURL instead of java.net.URI, where possible.',
+      ),
+      excluded_paths=(
+        (r'net/android/javatests/src/org/chromium/net/'
+         'AndroidProxySelectorTest\.java'),
+        r'components/cronet/',
+        r'third_party/robolectric/local/',
+      ),
     ),
-), (
-    'android.annotation.TargetApi;',
-    ('Do not use TargetApi, use @androidx.annotation.RequiresApi instead. '
-     'RequiresApi ensures that any calls are guarded by the appropriate '
-     'SDK_INT check. See https://crbug.com/1116486.', ),
-    (),
-), (
-    'android.support.test.rule.UiThreadTestRule;',
-    ('Do not use UiThreadTestRule, just use '
-     '@org.chromium.base.test.UiThreadTest on test methods that should run '
-     'on the UI thread. See https://crbug.com/1111893.', ),
-    (),
-), ('android.support.test.annotation.UiThreadTest;',
-    ('Do not use android.support.test.annotation.UiThreadTest, use '
-     'org.chromium.base.test.UiThreadTest instead. See '
-     'https://crbug.com/1111893.', ),
-    ()), ('android.support.test.rule.ActivityTestRule;',
-          ('Do not use ActivityTestRule, use '
-           'org.chromium.base.test.BaseActivityTestRule instead.', ),
-          ('components/cronet/', )))
+    BanRule(
+      'import android.annotation.TargetApi;',
+      (
+       'Do not use TargetApi, use @androidx.annotation.RequiresApi instead. '
+       'RequiresApi ensures that any calls are guarded by the appropriate '
+       'SDK_INT check. See https://crbug.com/1116486.',
+      ),
+    ),
+    BanRule(
+      'import android.support.test.rule.UiThreadTestRule;',
+      (
+       'Do not use UiThreadTestRule, just use '
+       '@org.chromium.base.test.UiThreadTest on test methods that should run '
+       'on the UI thread. See https://crbug.com/1111893.',
+      ),
+    ),
+    BanRule(
+      'import android.support.test.annotation.UiThreadTest;',
+      ('Do not use android.support.test.annotation.UiThreadTest, use '
+       'org.chromium.base.test.UiThreadTest instead. See '
+       'https://crbug.com/1111893.',
+      ),
+    ),
+    BanRule(
+      'import android.support.test.rule.ActivityTestRule;',
+      (
+       'Do not use ActivityTestRule, use '
+       'org.chromium.base.test.BaseActivityTestRule instead.',
+      ),
+      excluded_paths=(
+        'components/cronet/',
+      ),
+    ),
+)
 
-# Format: Sequence of tuples containing:
-# * String pattern or, if starting with a slash, a regular expression.
-# * Sequence of strings to show when the pattern matches.
-# * Error flag. True if a match is a presubmit error, otherwise it's a warning.
+# Format: Sequence of BanRule:
 _BANNED_JAVA_FUNCTIONS = (
-    (
+    BanRule(
       'StrictMode.allowThreadDiskReads()',
       (
        'Prefer using StrictModeContext.allowDiskReads() to using StrictMode '
@@ -156,7 +185,7 @@
       ),
       False,
     ),
-    (
+    BanRule(
       'StrictMode.allowThreadDiskWrites()',
       (
        'Prefer using StrictModeContext.allowDiskWrites() to using StrictMode '
@@ -174,12 +203,9 @@
     ),
 )
 
-# Format: Sequence of tuples containing:
-# * String pattern or, if starting with a slash, a regular expression.
-# * Sequence of strings to show when the pattern matches.
-# * Error flag. True if a match is a presubmit error, otherwise it's a warning.
+# Format: Sequence of BanRule:
 _BANNED_OBJC_FUNCTIONS = (
-    (
+    BanRule(
       'addTrackingRect:',
       (
        'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
@@ -188,7 +214,7 @@
       ),
       False,
     ),
-    (
+    BanRule(
       r'/NSTrackingArea\W',
       (
        'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
@@ -197,7 +223,7 @@
       ),
       False,
     ),
-    (
+    BanRule(
       'convertPointFromBase:',
       (
        'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
@@ -206,7 +232,7 @@
       ),
       True,
     ),
-    (
+    BanRule(
       'convertPointToBase:',
       (
        'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
@@ -215,7 +241,7 @@
       ),
       True,
     ),
-    (
+    BanRule(
       'convertRectFromBase:',
       (
        'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
@@ -224,7 +250,7 @@
       ),
       True,
     ),
-    (
+    BanRule(
       'convertRectToBase:',
       (
        'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
@@ -233,7 +259,7 @@
       ),
       True,
     ),
-    (
+    BanRule(
       'convertSizeFromBase:',
       (
        'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
@@ -242,7 +268,7 @@
       ),
       True,
     ),
-    (
+    BanRule(
       'convertSizeToBase:',
       (
        'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
@@ -251,7 +277,7 @@
       ),
       True,
     ),
-    (
+    BanRule(
       r"/\s+UTF8String\s*]",
       (
        'The use of -[NSString UTF8String] is dangerous as it can return null',
@@ -260,7 +286,7 @@
       ),
       True,
     ),
-    (
+    BanRule(
       r'__unsafe_unretained',
       (
         'The use of __unsafe_unretained is almost certainly wrong, unless',
@@ -269,7 +295,7 @@
       ),
       False,
     ),
-    (
+    BanRule(
       'freeWhenDone:NO',
       (
         'The use of "freeWhenDone:NO" with the NoCopy creation of ',
@@ -279,12 +305,9 @@
     ),
 )
 
-# Format: Sequence of tuples containing:
-# * String pattern or, if starting with a slash, a regular expression.
-# * Sequence of strings to show when the pattern matches.
-# * Error flag. True if a match is a presubmit error, otherwise it's a warning.
+# Format: Sequence of BanRule:
 _BANNED_IOS_OBJC_FUNCTIONS = (
-    (
+    BanRule(
       r'/\bTEST[(]',
       (
         'TEST() macro should not be used in Objective-C++ code as it does not ',
@@ -294,7 +317,7 @@
       ),
       True,
     ),
-    (
+    BanRule(
       r'/\btesting::Test\b',
       (
         'testing::Test should not be used in Objective-C++ code as it does ',
@@ -305,12 +328,9 @@
     ),
 )
 
-# Format: Sequence of tuples containing:
-# * String pattern or, if starting with a slash, a regular expression.
-# * Sequence of strings to show when the pattern matches.
-# * Error flag. True if a match is a presubmit error, otherwise it's a warning.
+# Format: Sequence of BanRule:
 _BANNED_IOS_EGTEST_FUNCTIONS = (
-    (
+    BanRule(
       r'/\bEXPECT_OCMOCK_VERIFY\b',
       (
         'EXPECT_OCMOCK_VERIFY should not be used in EarlGrey tests because ',
@@ -320,13 +340,9 @@
     ),
 )
 
-# Format: Sequence of tuples containing:
-# * String pattern or, if starting with a slash, a regular expression.
-# * Sequence of strings to show when the pattern matches.
-# * Error flag. True if a match is a presubmit error, otherwise it's a warning.
-# * Sequence of paths to *not* check (regexps).
+# Format: Sequence of BanRule:
 _BANNED_CPP_FUNCTIONS = (
-    (
+    BanRule(
       r'/\busing namespace ',
       (
        'Using directives ("using namespace x") are banned by the Google Style',
@@ -339,7 +355,7 @@
     # Make sure that gtest's FRIEND_TEST() macro is not used; the
     # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
     # used instead since that allows for FLAKY_ and DISABLED_ prefixes.
-    (
+    BanRule(
       'FRIEND_TEST(',
       (
        'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
@@ -348,7 +364,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'setMatrixClip',
       (
         'Overriding setMatrixClip() is prohibited; ',
@@ -357,7 +373,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       'SkRefPtr',
       (
         'The use of SkRefPtr is prohibited. ',
@@ -366,7 +382,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       'SkAutoRef',
       (
         'The indirect use of SkRefPtr via SkAutoRef is prohibited. ',
@@ -375,7 +391,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       'SkAutoTUnref',
       (
         'The use of SkAutoTUnref is dangerous because it implicitly ',
@@ -384,7 +400,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       'SkAutoUnref',
       (
         'The indirect use of SkAutoTUnref through SkAutoUnref is dangerous ',
@@ -394,7 +410,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       r'/HANDLE_EINTR\(.*close',
       (
        'HANDLE_EINTR(close) is invalid. If close fails with EINTR, the file',
@@ -405,7 +421,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       r'/IGNORE_EINTR\((?!.*close)',
       (
        'IGNORE_EINTR is only valid when wrapping close. To wrap other system',
@@ -418,7 +434,7 @@
         r'^ppapi[\\/]tests[\\/]test_broker\.cc$',
       ),
     ),
-    (
+    BanRule(
       r'/v8::Extension\(',
       (
         'Do not introduce new v8::Extensions into the code base, use',
@@ -429,7 +445,7 @@
         r'extensions[\\/]renderer[\\/]safe_builtins\.*',
       ),
     ),
-    (
+    BanRule(
       '#pragma comment(lib,',
       (
         'Specify libraries to link with in build files and not in the source.',
@@ -440,7 +456,7 @@
           r'^third_party[\\/]abseil-cpp[\\/].*',
       ),
     ),
-    (
+    BanRule(
       r'/base::SequenceChecker\b',
       (
         'Consider using SEQUENCE_CHECKER macros instead of the class directly.',
@@ -448,7 +464,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       r'/base::ThreadChecker\b',
       (
         'Consider using THREAD_CHECKER macros instead of the class directly.',
@@ -456,7 +472,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       r'/(Time(|Delta|Ticks)|ThreadTicks)::FromInternalValue|ToInternalValue',
       (
         'base::TimeXXX::FromInternalValue() and ToInternalValue() are',
@@ -471,7 +487,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'CallJavascriptFunctionUnsafe',
       (
         "Don't use CallJavascriptFunctionUnsafe() in new code. Instead, use",
@@ -485,7 +501,7 @@
         r'^content[\\/]public[\\/]test[\\/]test_web_ui\.(cc|h)$',
       ),
     ),
-    (
+    BanRule(
       'leveldb::DB::Open',
       (
         'Instead of leveldb::DB::Open() use leveldb_env::OpenDB() from',
@@ -497,7 +513,7 @@
         r'^third_party/leveldatabase/.*\.(cc|h)$',
       ),
     ),
-    (
+    BanRule(
       'leveldb::NewMemEnv',
       (
         'Instead of leveldb::NewMemEnv() use leveldb_chrome::NewMemEnv() from',
@@ -509,7 +525,7 @@
         r'^third_party/leveldatabase/.*\.(cc|h)$',
       ),
     ),
-    (
+    BanRule(
       'RunLoop::QuitCurrent',
       (
         'Please migrate away from RunLoop::QuitCurrent*() methods. Use member',
@@ -518,7 +534,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'base::ScopedMockTimeMessageLoopTaskRunner',
       (
         'ScopedMockTimeMessageLoopTaskRunner is deprecated. Prefer',
@@ -530,7 +546,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'std::regex',
       (
         'Using std::regex adds unnecessary binary size to Chrome. Please use',
@@ -540,7 +556,7 @@
       # Abseil's benchmarks never linked into chrome.
       ['third_party/abseil-cpp/.*_benchmark.cc'],
     ),
-    (
+    BanRule(
       r'/\bstd::stoi\b',
       (
         'std::stoi uses exceptions to communicate results. ',
@@ -549,7 +565,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::stol\b',
       (
         'std::stol uses exceptions to communicate results. ',
@@ -558,7 +574,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::stoul\b',
       (
         'std::stoul uses exceptions to communicate results. ',
@@ -567,7 +583,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::stoll\b',
       (
         'std::stoll uses exceptions to communicate results. ',
@@ -576,7 +592,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::stoull\b',
       (
         'std::stoull uses exceptions to communicate results. ',
@@ -585,7 +601,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::stof\b',
       (
         'std::stof uses exceptions to communicate results. ',
@@ -596,7 +612,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::stod\b',
       (
         'std::stod uses exceptions to communicate results. ',
@@ -607,7 +623,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::stold\b',
       (
         'std::stold uses exceptions to communicate results. ',
@@ -618,7 +634,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::to_string\b',
       (
         'std::to_string is locale dependent and slower than alternatives.',
@@ -630,7 +646,7 @@
       False,  # Only a warning since it is already used.
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::shared_ptr\b',
       (
         'std::shared_ptr should not be used. Use scoped_refptr instead.',
@@ -654,7 +670,7 @@
        '^tools/clang/plugins/tests/',
        _THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::weak_ptr\b',
       (
         'std::weak_ptr should not be used. Use base::WeakPtr instead.',
@@ -662,7 +678,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       r'/\blong long\b',
       (
         'long long is banned. Use stdint.h if you need a 64 bit number.',
@@ -670,16 +686,16 @@
       False,  # Only a warning since it is already used.
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
-    (
+    BanRule(
       r'\b(absl|std)::any\b',
       (
-        'absl::any / std::any are not safe to use in a component build.'
+        'absl::any / std::any are not safe to use in a component build.',
       ),
       True,
       # Not an error in third party folders, though it probably should be :)
       [_THIRD_PARTY_EXCEPT_BLINK],
     ),
-    (
+    BanRule(
       r'/\bstd::bind\b',
       (
         'std::bind is banned because of lifetime risks.',
@@ -688,7 +704,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::optional\b',
       (
         'std::optional is banned. Use absl::optional instead.',
@@ -696,7 +712,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       r'/\b#include <chrono>\b',
       (
         '<chrono> overlaps with Time APIs in base. Keep using',
@@ -705,7 +721,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       r'/\b#include <exception>\b',
       (
         'Exceptions are banned and disabled in Chromium.',
@@ -713,7 +729,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::function\b',
       (
         'std::function is banned. Instead use base::OnceCallback or ',
@@ -723,7 +739,7 @@
       False,  # Only a warning since it is already used.
       [_THIRD_PARTY_EXCEPT_BLINK],  # Do not warn in third_party folders.
     ),
-    (
+    BanRule(
       r'/\b#include <random>\b',
       (
         'Do not use any random number engines from <random>. Instead',
@@ -732,7 +748,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       r'/\b#include <X11/',
       (
         'Do not use Xlib. Use xproto (from //ui/gfx/x:xproto) instead.',
@@ -740,7 +756,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       r'/\bstd::ratio\b',
       (
         'std::ratio is banned by the Google Style Guide.',
@@ -748,7 +764,7 @@
       True,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       ('base::ThreadRestrictions::ScopedAllowIO'),
       (
         'ScopedAllowIO is deprecated, use ScopedAllowBlocking instead.',
@@ -756,7 +772,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       r'/\bRunMessageLoop\b',
       (
           'RunMessageLoop is deprecated, use RunLoop instead.',
@@ -764,7 +780,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'RunThisRunLoop',
       (
           'RunThisRunLoop is deprecated, use RunLoop directly instead.',
@@ -772,7 +788,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'RunAllPendingInMessageLoop()',
       (
           "Prefer RunLoop over RunAllPendingInMessageLoop, please contact gab@",
@@ -781,7 +797,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'RunAllPendingInMessageLoop(BrowserThread',
       (
           'RunAllPendingInMessageLoop is deprecated. Use RunLoop for',
@@ -792,7 +808,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       r'MessageLoopRunner',
       (
           'MessageLoopRunner is deprecated, use RunLoop instead.',
@@ -800,7 +816,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'GetDeferredQuitTaskForRunLoop',
       (
           "GetDeferredQuitTaskForRunLoop shouldn't be needed, please contact",
@@ -809,7 +825,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'sqlite3_initialize(',
       (
         'Instead of calling sqlite3_initialize(), depend on //sql, ',
@@ -821,7 +837,7 @@
         r'^third_party/sqlite/.*\.(c|cc|h)$',
       ),
     ),
-    (
+    BanRule(
       'std::random_shuffle',
       (
         'std::random_shuffle is deprecated in C++14, and removed in C++17. Use',
@@ -830,7 +846,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       'ios/web/public/test/http_server',
       (
         'web::HTTPserver is deprecated use net::EmbeddedTestServer instead.',
@@ -838,7 +854,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'GetAddressOf',
       (
         'Improper use of Microsoft::WRL::ComPtr<T>::GetAddressOf() has been ',
@@ -849,7 +865,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       'SHFileOperation',
       (
         'SHFileOperation was deprecated in Windows Vista, and there are less ',
@@ -859,7 +875,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       'StringFromGUID2',
       (
         'StringFromGUID2 introduces an unnecessary dependency on ole32.dll.',
@@ -867,10 +883,10 @@
       ),
       True,
       (
-        r'/base/win/win_util_unittest.cc'
+        r'/base/win/win_util_unittest.cc',
       ),
     ),
-    (
+    BanRule(
       'StringFromCLSID',
       (
         'StringFromCLSID introduces an unnecessary dependency on ole32.dll.',
@@ -878,10 +894,10 @@
       ),
       True,
       (
-        r'/base/win/win_util_unittest.cc'
+        r'/base/win/win_util_unittest.cc',
       ),
     ),
-    (
+    BanRule(
       'kCFAllocatorNull',
       (
         'The use of kCFAllocatorNull with the NoCopy creation of ',
@@ -890,7 +906,7 @@
       True,
       (),
     ),
-    (
+    BanRule(
       'mojo::ConvertTo',
       (
         'mojo::ConvertTo and TypeConverter are deprecated. Please consider',
@@ -906,7 +922,7 @@
         r'^content/renderer/.*\.(cc|h)$',
       ),
     ),
-    (
+    BanRule(
       'GetInterfaceProvider',
       (
         'InterfaceProvider is deprecated.',
@@ -916,7 +932,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'CComPtr',
       (
         'New code should use Microsoft::WRL::ComPtr from wrl/client.h as a ',
@@ -926,7 +942,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       r'/\b(IFACE|STD)METHOD_?\(',
       (
         'IFACEMETHOD() and STDMETHOD() make code harder to format and read.',
@@ -935,7 +951,7 @@
       False,
       [_THIRD_PARTY_EXCEPT_BLINK],  # Not an error in third_party folders.
     ),
-    (
+    BanRule(
       'set_owned_by_client',
       (
         'set_owned_by_client is deprecated.',
@@ -946,7 +962,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'RemoveAllChildViewsWithoutDeleting',
       (
         'RemoveAllChildViewsWithoutDeleting is deprecated.',
@@ -956,7 +972,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       r'/\bTRACE_EVENT_ASYNC_',
       (
           'Please use TRACE_EVENT_NESTABLE_ASYNC_.. macros instead',
@@ -968,7 +984,7 @@
         r'^base/tracing/.*',
       ),
     ),
-    (
+    BanRule(
       r'/\bbase::debug::DumpWithoutCrashingUnthrottled[(][)]',
       (
           'base::debug::DumpWithoutCrashingUnthrottled() does not throttle',
@@ -978,7 +994,7 @@
       False,
       (),
     ),
-    (
+    BanRule(
       'RoInitialize',
       (
         'Improper use of [base::win]::RoInitialize() has been implicated in a ',
@@ -987,27 +1003,16 @@
       ),
       True,
       (
-          r'^base[\\/]win[\\/]scoped_winrt_initializer\.cc$'
+          r'^base[\\/]win[\\/]scoped_winrt_initializer\.cc$',
       ),
     ),
-    (
-        r'/base::(size|empty|data)',
-        (
-            'Please use the STL equivalent (std::size/std::empty/std::data) ',
-            'instead. The base versions are being removed: ',
-            'https://crbug.com/1299695'
-
-        ),
-        False,
-        [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
-    ),
 )
 
 # Format: Sequence of tuples containing:
 # * String pattern or, if starting with a slash, a regular expression.
 # * Sequence of strings to show when the pattern matches.
 _DEPRECATED_MOJO_TYPES = (
-    (
+    BanRule(
       r'/\bmojo::AssociatedInterfacePtrInfo\b',
       (
         'mojo::AssociatedInterfacePtrInfo<Interface> is deprecated.',
@@ -1576,7 +1581,7 @@
 
 
 def _GetMessageForMatchingType(input_api, affected_file, line_number, line,
-                               type_name, message):
+                               ban_rule):
     """Helper method for CheckNoBannedFunctions and CheckNoDeprecatedMojoTypes.
 
     Returns an string composed of the name of the file, the line number where the
@@ -1585,25 +1590,25 @@
     """
     result = []
 
-    if input_api.re.search(r"^ *//",
-                           line):  # Ignore comments about banned types.
+    # Ignore comments about banned types.
+    if input_api.re.search(r"^ *//", line):
         return result
-    if line.endswith(
-            " nocheck"):  # A // nocheck comment will bypass this error.
+    # A // nocheck comment will bypass this error.
+    if line.endswith(" nocheck"):
         return result
 
     matched = False
-    if type_name[0:1] == '/':
-        regex = type_name[1:]
+    if ban_rule.pattern[0:1] == '/':
+        regex = ban_rule.pattern[1:]
         if input_api.re.search(regex, line):
             matched = True
-    elif type_name in line:
+    elif ban_rule.pattern in line:
         matched = True
 
     if matched:
         result.append('    %s:%d:' % (affected_file.LocalPath(), line_number))
-        for message_line in message:
-            result.append('      %s' % message_line)
+        for line in ban_rule.explanation:
+            result.append('      %s' % line)
 
     return result
 
@@ -1614,6 +1619,9 @@
     errors = []
 
     def IsExcludedFile(affected_file, excluded_paths):
+        if not excluded_paths:
+            return False
+
         local_path = affected_file.LocalPath()
         for item in excluded_paths:
             if input_api.re.match(item, local_path):
@@ -1633,12 +1641,15 @@
                 return True
         return False
 
-    def CheckForMatch(affected_file, line_num, line, func_name, message,
-                      error):
+    def CheckForMatch(affected_file, line_num: int, line: str,
+                      ban_rule: BanRule):
+        if IsExcludedFile(affected_file, ban_rule.excluded_paths):
+            return
+
         problems = _GetMessageForMatchingType(input_api, f, line_num, line,
-                                              func_name, message)
+                                              ban_rule)
         if problems:
-            if error:
+            if ban_rule.treat_as_error is not None and ban_rule.treat_as_error:
                 errors.extend(problems)
             else:
                 warnings.extend(problems)
@@ -1646,33 +1657,31 @@
     file_filter = lambda f: f.LocalPath().endswith(('.java'))
     for f in input_api.AffectedFiles(file_filter=file_filter):
         for line_num, line in f.ChangedContents():
-            for func_name, message, error in _BANNED_JAVA_FUNCTIONS:
-                CheckForMatch(f, line_num, line, func_name, message, error)
+            for ban_rule in _BANNED_JAVA_FUNCTIONS:
+                CheckForMatch(f, line_num, line, ban_rule)
 
     file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
     for f in input_api.AffectedFiles(file_filter=file_filter):
         for line_num, line in f.ChangedContents():
-            for func_name, message, error in _BANNED_OBJC_FUNCTIONS:
-                CheckForMatch(f, line_num, line, func_name, message, error)
+            for ban_rule in _BANNED_OBJC_FUNCTIONS:
+                CheckForMatch(f, line_num, line, ban_rule)
 
     for f in input_api.AffectedFiles(file_filter=IsIosObjcFile):
         for line_num, line in f.ChangedContents():
-            for func_name, message, error in _BANNED_IOS_OBJC_FUNCTIONS:
-                CheckForMatch(f, line_num, line, func_name, message, error)
+            for ban_rule in _BANNED_IOS_OBJC_FUNCTIONS:
+                CheckForMatch(f, line_num, line, ban_rule)
 
     egtest_filter = lambda f: f.LocalPath().endswith(('_egtest.mm'))
     for f in input_api.AffectedFiles(file_filter=egtest_filter):
         for line_num, line in f.ChangedContents():
-            for func_name, message, error in _BANNED_IOS_EGTEST_FUNCTIONS:
-                CheckForMatch(f, line_num, line, func_name, message, error)
+            for ban_rule in _BANNED_IOS_EGTEST_FUNCTIONS:
+                CheckForMatch(f, line_num, line, ban_rule)
 
     file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
     for f in input_api.AffectedFiles(file_filter=file_filter):
         for line_num, line in f.ChangedContents():
-            for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
-                if IsExcludedFile(f, excluded_paths):
-                    continue
-                CheckForMatch(f, line_num, line, func_name, message, error)
+            for ban_rule in _BANNED_CPP_FUNCTIONS:
+                CheckForMatch(f, line_num, line, ban_rule)
 
     result = []
     if (warnings):
@@ -1690,22 +1699,16 @@
     """Make sure that banned java imports are not used."""
     errors = []
 
-    def IsException(path, exceptions):
-        for exception in exceptions:
-            if (path.startswith(exception)):
-                return True
-        return False
-
     file_filter = lambda f: f.LocalPath().endswith(('.java'))
     for f in input_api.AffectedFiles(file_filter=file_filter):
         for line_num, line in f.ChangedContents():
-            for import_name, message, exceptions in _BANNED_JAVA_IMPORTS:
-                if IsException(f.LocalPath(), exceptions):
-                    continue
+            for ban_rule in _BANNED_JAVA_IMPORTS:
+                # Consider merging this into the above function. There is no
+                # real difference anymore other than helping with a little
+                # bit of boilerplate text. Doing so means things like
+                # `treat_as_error` will also be uniformly handled.
                 problems = _GetMessageForMatchingType(input_api, f, line_num,
-                                                      line,
-                                                      'import ' + import_name,
-                                                      message)
+                                                      line, ban_rule)
                 if problems:
                     errors.extend(problems)
     result = []
@@ -1733,9 +1736,9 @@
             continue
 
         for line_num, line in f.ChangedContents():
-            for func_name, message in _DEPRECATED_MOJO_TYPES:
+            for ban_rule in _DEPRECATED_MOJO_TYPES:
                 problems = _GetMessageForMatchingType(input_api, f, line_num,
-                                                      line, func_name, message)
+                                                      line, ban_rule)
 
                 if problems:
                     # Raise errors inside |error_paths| and warnings everywhere else.
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index 493ea175..7b7e98f 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -296,5 +296,7 @@
                     "Enables the use of sec-ch-viewport-height client hint."),
             Flag.baseFeature(BlinkFeatures.USER_AGENT_OVERRIDE_EXPERIMENT,
                     "Collects metrics on when the User-Agent string is overridden and how"),
+            Flag.baseFeature(GpuFeatures.CANVAS_CONTEXT_LOST_IN_BACKGROUND,
+                    "Free Canvas2D resources when the webview is in the background."),
     };
 }
diff --git a/ash/ambient/ambient_photo_controller.cc b/ash/ambient/ambient_photo_controller.cc
index 225db1b7..0c0b4a6 100644
--- a/ash/ambient/ambient_photo_controller.cc
+++ b/ash/ambient/ambient_photo_controller.cc
@@ -150,7 +150,7 @@
 AmbientPhotoController::~AmbientPhotoController() = default;
 
 void AmbientPhotoController::Init() {
-  state_ = State::kPreparingInitialTopicSets;
+  state_ = State::kPreparingNextTopicSet;
   topic_index_ = 0;
   retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
   backup_retries_to_read_from_cache_ = GetBackupPhotoUrls().size();
@@ -202,26 +202,24 @@
   }
 
   DVLOG(3) << "UI event " << marker << " triggering topic refresh";
-  switch (state_) {
-    case State::kInactive:
-    case State::kPreparingInitialTopicSets:
-      // In these states, the UI shouldn't be active, so it it's unexpected for
-      // the controller to receive a UI event.
-      LOG(ERROR) << "Received unexpected UI marker " << marker << " in state "
-                 << state_;
-      break;
-    case State::kWaitingForNextMarker:
-      state_ = State::kPreparingNextTopicSet;
-      num_topics_prepared_ = 0;
-      StartPreparingNextTopic();
-      break;
-    case State::kPreparingNextTopicSet:
-      // The controller is still in the middle of preparing a topic from the
-      // previous set (i.e. waiting on a callback or timer to fire). Resetting
-      // |num_topics_prepared_| to 0 is enough, and the topic currently being
-      // prepared will count towards the next set.
-      num_topics_prepared_ = 0;
-      break;
+  if (state_ == State::kInactive) {
+    LOG(DFATAL) << "Received unexpected UI marker " << marker
+                << " while inactive";
+    return;
+  }
+
+  bool is_still_preparing_topics = state_ != State::kWaitingForNextMarker;
+  state_ = State::kPreparingNextTopicSet;
+  num_topics_prepared_ = 0;
+  if (is_still_preparing_topics) {
+    // The controller is still in the middle of preparing a topic from the
+    // previous set (i.e. waiting on a callback or timer to fire). Resetting
+    // |num_topics_prepared_| to 0 above is enough, and the topic currently
+    // being prepared will count towards the next set.
+    DVLOG(4) << "Did not finished preparing current topic set in time. "
+                "Starting new set...";
+  } else {
+    StartPreparingNextTopic();
   }
 }
 
@@ -231,10 +229,10 @@
     ScheduleFetchTopics(/*backoff=*/false);
 
   // Only call FetchPhotoRawData() for on-demand fetches. If a scheduled topic
-  // fetch happens to occur during the PREPARING_INITIAL_TOPIC_SETS or
-  // PREPARING_NEXT_TOPIC_SET state and FetchPhotoRawData() is called, not only
-  // is that unnecessary but it could also result in the controller decoding
-  // multiple topics simultaneously, which is currently not supported.
+  // fetch happens to occur during the |kPreparingNextTopicSet| state and
+  // FetchPhotoRawData() is called, not only is that unnecessary but it could
+  // also result in the controller decoding multiple topics simultaneously,
+  // which is currently not supported.
   if (latest_fetch_topic_request_type_ == FetchTopicRequestType::kOnDemand) {
     FetchPhotoRawData();
   }
@@ -575,43 +573,28 @@
 
   ResetImageData();
 
+  if (state_ != State::kPreparingNextTopicSet) {
+    LOG(ERROR) << "Topic prepared when controller should be idle in state "
+               << state_;
+    return;
+  }
+
   // AddNextImage() can call out to observers, who can synchronously interact
   // with the controller within their observer notification methods. So the
   // internal |state_| should be updated before calling AddNextImage() so that
   // it is consistent with the model.
-  State previous_state = state_;
-  size_t target_num_topics_to_prepare = 0;
-  switch (state_) {
-    case State::kInactive:
-    case State::kWaitingForNextMarker:
-      LOG(ERROR) << "Topic prepared when controller should be idle in state "
-                 << state_;
-      return;
-    case State::kPreparingInitialTopicSets:
-      target_num_topics_to_prepare =
-          ambient_backend_model_.photo_config().GetNumDecodedTopicsToBuffer();
-      break;
-    case State::kPreparingNextTopicSet:
-      target_num_topics_to_prepare =
-          ambient_backend_model_.photo_config().topic_set_size;
-      break;
-  }
-
+  size_t target_num_topics_to_prepare =
+      ambient_backend_model_.ImagesReady()
+          ? ambient_backend_model_.photo_config().topic_set_size
+          : ambient_backend_model_.photo_config().GetNumDecodedTopicsToBuffer();
   ++num_topics_prepared_;
-  if (num_topics_prepared_ == target_num_topics_to_prepare)
+  if (num_topics_prepared_ >= target_num_topics_to_prepare)
     state_ = State::kWaitingForNextMarker;
 
   ambient_backend_model_.AddNextImage(std::move(detailed_photo));
 
-  if (previous_state == State::kPreparingInitialTopicSets &&
-      state_ == State::kWaitingForNextMarker) {
-    DCHECK(ambient_backend_model_.ImagesReady());
-  }
-
-  if (state_ == State::kPreparingInitialTopicSets ||
-      state_ == State::kPreparingNextTopicSet) {
+  if (state_ == State::kPreparingNextTopicSet)
     StartPreparingNextTopic();
-  }
 }
 
 void AmbientPhotoController::StartDownloadingWeatherConditionIcon(
@@ -677,8 +660,7 @@
 }
 
 void AmbientPhotoController::StartPreparingNextTopic() {
-  DCHECK(state_ == State::kPreparingInitialTopicSets ||
-         state_ == State::kPreparingNextTopicSet);
+  DCHECK_EQ(state_, State::kPreparingNextTopicSet);
   if (HasExhaustedAllTopicsInModel() && !HasModelReachedMaxTopicCapacity()) {
     FetchTopics(FetchTopicRequestType::kOnDemand);
   } else {
@@ -691,8 +673,6 @@
   switch (state) {
     case AmbientPhotoController::State::kInactive:
       return os << "INACTIVE";
-    case AmbientPhotoController::State::kPreparingInitialTopicSets:
-      return os << "PREPARING_INITIAL_TOPIC_SETS";
     case AmbientPhotoController::State::kWaitingForNextMarker:
       return os << "WAITING_FOR_NEXT_MARKER";
     case AmbientPhotoController::State::kPreparingNextTopicSet:
diff --git a/ash/ambient/ambient_photo_controller.h b/ash/ambient/ambient_photo_controller.h
index 48f1599..ffb2773f 100644
--- a/ash/ambient/ambient_photo_controller.h
+++ b/ash/ambient/ambient_photo_controller.h
@@ -68,47 +68,44 @@
 //
 // The controller's state machine:
 //
-//        INACTIVE
+//        kInactive
 //           |
 //           |
 //           v
-// PREPARING_INITIAL_TOPIC_SETS
-//           |
-//           |
-//           v
-// WAITING_FOR_NEXT_MARKER <-----
-//           |                  |
-//           |                  |
-//           v                  |
-// PREPARING_NEXT_TOPIC_SET -----
+// kPreparingNextTopicSet <-----
+//           |                   |
+//           |                   |
+//           v                   |
+// kWaitingForNextMarker -------
 //
-// INACTIVE:
+// kInactive:
 // The controller is idle, and the model has no decoded topics in it. This is
 // the initial state when the controller is constructed. Although not
 // illustrated above, the controller can transition to this state from any of
 // the other states via a call to StopScreenUpdate().
 //
-// PREPARING_INITIAL_TOPIC_SETS:
-// At this point, the UI has not started rendering yet, and the controller is
-// preparing initial sets of topics. The AmbientPhotoConfig dictates how many
-// sets to prepare initially. This state is triggered by a call to
-// StartScreenUpdate(). It is complete when the desired number of initial topic
-// sets have been prepared.
 //
-// WAITING_FOR_NEXT_MARKER:
+// kPreparingNextTopicSet (a.k.a. "refreshing" the model's topics):
+// The very first time this state is reached, the UI has not started rendering
+// yet, and the controller is preparing initial sets of topics. The
+// AmbientPhotoConfig dictates how many sets to prepare initially. This state is
+// initially triggered by a call to StartScreenUpdate(), and it ends when
+// AmbientBackendModel::ImagesReady() is true.
+//
+// kWaitingForNextMarker:
 // The UI is rendering the decoded topics currently in the model, and the
 // controller is idle. It's waiting for the right marker(s) to be hit in the UI
 // before it becomes active and starts preparing the next set of topics.
 //
-// PREPARING_NEXT_TOPIC_SET (a.k.a. "refreshing" the model's topics):
+// kPreparingNextTopicSet (again):
 // A target marker has been hit, and the controller immediately starts preparing
-// the next set of topics. Unlike the PREPARING_INITIAL_TOPIC_SETS state, there
-// is only ever 1 topic set prepared in this state, and the UI is rendering
-// while the topics are being prepared. After the topic set is completely
-// prepared, the controller goes back to WAITING_FOR_NEXT_MARKER. If another
-// target marker is received while the controller is still preparing a topic
-// set, the controller will simply reset its internal "counter" to 0 and start
-// preparing a brand new set.
+// the next set of topics. Unlike the first time this state was hit, there
+// is only ever 1 topic set prepared, and the UI is rendering while the topics
+// are being prepared. After the topic set is completely prepared, the
+// controller goes back to WAITING_FOR_NEXT_MARKER. If another target marker is
+// received while the controller is still preparing a topic set, the controller
+// will simply reset its internal "counter" to 0 and start preparing a brand new
+// set.
 class ASH_EXPORT AmbientPhotoController : public AmbientBackendModelObserver,
                                           public AmbientViewEventHandler {
  public:
@@ -146,27 +143,21 @@
   void ClearCache();
 
  private:
-  enum class State {
-    kInactive,
-    kPreparingInitialTopicSets,
-    kWaitingForNextMarker,
-    kPreparingNextTopicSet
-  };
+  enum class State { kInactive, kWaitingForNextMarker, kPreparingNextTopicSet };
 
   // Describes the 2 cases for when new topics are fetched from the IMAX sever.
   enum class FetchTopicRequestType {
-    // The controller is in the PREPARING_INITIAL_TOPIC_SETS or
-    // PREPARING_NEXT_TOPIC_SET state and hence, there is an immediate demand to
-    // prepare more topics. If it has exhausted all of the existing topics in
-    // the model and the model has capacity to store more topics, the controller
-    // will fetch a new set of topics and will immediately download/save/decode
-    // topics from the new set afterwards.
+    // The controller is in the |kPreparingNextTopicSet| state and hence, there
+    // is an immediate demand to prepare more topics. If it has exhausted all of
+    // the existing topics in the model and the model has capacity to store more
+    // topics, the controller will fetch a new set of topics and will
+    // immediately download/save/decode topics from the new set afterwards.
     kOnDemand,
-    // The controller can be in any state other than INACTIVE. It will fetch a
-    // new set of topics if the |kTopicFetchInterval| has elapsed since the last
-    // topic fetch completed, regardless of that fetch's request type. This is
-    // done to guarantee that a sufficient amount of topics are available in the
-    // model before the access token required to fetch new topics from the
+    // The controller can be in any state other than |kInactive|. It will fetch
+    // a new set of topics if the |kTopicFetchInterval| has elapsed since the
+    // last topic fetch completed, regardless of that fetch's request type. This
+    // is done to guarantee that a sufficient amount of topics are available in
+    // the model before the access token required to fetch new topics from the
     // server expires. In other words, a topic fetch is guaranteed to occur at
     // least every |kTopicFetchInterval| seconds until
     // |kMaxNumberOfCachedImages| topics are available in the model.
@@ -339,8 +330,7 @@
   gfx::ImageSkia related_image_;
 
   // Tracks the number of topics that have been prepared since the controller
-  // last transitioned to either the PREPARING_INITIAL_TOPIC_SETS or
-  // PREPARING_NEXT_TOPIC_SET state.
+  // last transitioned to the |kPreparingNextTopicSet| state.
   size_t num_topics_prepared_ = 0;
 
   // Transient variable. Type of the most recent successful topic fetch.
diff --git a/ash/ambient/ambient_photo_controller_unittest.cc b/ash/ambient/ambient_photo_controller_unittest.cc
index 24f37f0..ab761468 100644
--- a/ash/ambient/ambient_photo_controller_unittest.cc
+++ b/ash/ambient/ambient_photo_controller_unittest.cc
@@ -4,11 +4,14 @@
 
 #include "ash/ambient/ambient_photo_controller.h"
 
+#include <array>
 #include <memory>
+#include <tuple>
 #include <utility>
 
 #include "ash/ambient/ambient_constants.h"
 #include "ash/ambient/ambient_controller.h"
+#include "ash/ambient/model/ambient_animation_photo_config.h"
 #include "ash/ambient/model/ambient_backend_model.h"
 #include "ash/ambient/model/ambient_backend_model_observer.h"
 #include "ash/ambient/model/ambient_photo_config.h"
@@ -22,6 +25,7 @@
 #include "base/base_paths.h"
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/check.h"
 #include "base/containers/contains.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
@@ -35,6 +39,7 @@
 #include "base/test/scoped_run_loop_timeout.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "cc/paint/skottie_resource_metadata.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/gfx/image/image_skia.h"
 
@@ -44,6 +49,7 @@
 using ::testing::Contains;
 using ::testing::Eq;
 using ::testing::Not;
+using ::testing::Pointwise;
 using ::testing::SizeIs;
 
 namespace {
@@ -57,9 +63,18 @@
   MOCK_METHOD(void, OnImagesReady, (), (override));
 };
 
+bool AreBackedBySameImage(const PhotoWithDetails& topic_l,
+                          const PhotoWithDetails& topic_r) {
+  return !topic_l.photo.isNull() && !topic_r.photo.isNull() &&
+         topic_l.photo.BackedBySameObjectAs(topic_r.photo);
+}
+
+MATCHER(BackedBySameImage, "") {
+  return AreBackedBySameImage(std::get<0>(arg), std::get<1>(arg));
+}
+
 MATCHER_P(BackedBySameImageAs, photo_with_details, "") {
-  return !arg.photo.isNull() && !photo_with_details.photo.isNull() &&
-         arg.photo.BackedBySameObjectAs(photo_with_details.photo);
+  return AreBackedBySameImage(arg, photo_with_details);
 }
 
 }  // namespace
@@ -158,14 +173,32 @@
   }
 };
 
+// Has 2 positions in the animation for photos and 2 dynamic assets per
+// position.
 class AmbientPhotoControllerAnimationTest : public AmbientPhotoControllerTest {
  protected:
-  static constexpr int kNumDynamicAssets = 4;
-
   void SetUp() override {
     AmbientAshTestBase::SetUp();
+
+    cc::SkottieResourceMetadataMap resource_metadata;
+    std::array<std::string, 4> all_dynamic_asset_ids = {
+        GenerateLottieDynamicAssetIdForTesting(/*position=*/"A", /*idx=*/1),
+        GenerateLottieDynamicAssetIdForTesting(/*position=*/"A", /*idx=*/2),
+        GenerateLottieDynamicAssetIdForTesting(/*position=*/"B", /*idx=*/1),
+        GenerateLottieDynamicAssetIdForTesting(/*position=*/"B", /*idx=*/2)};
+    for (const std::string& asset_id : all_dynamic_asset_ids) {
+      CHECK(resource_metadata.RegisterAsset("test-path", "test-name", asset_id,
+                                            /*size=*/absl::nullopt));
+    }
+
     photo_controller()->ambient_backend_model()->SetPhotoConfig(
-        GenerateAnimationConfigWithNAssets(kNumDynamicAssets));
+        CreateAmbientAnimationPhotoConfig(resource_metadata));
+    CHECK_EQ(photo_config().GetNumDecodedTopicsToBuffer(), 4u);
+    CHECK_EQ(photo_config().topic_set_size, 2u);
+  }
+
+  const AmbientPhotoConfig& photo_config() {
+    return photo_controller()->ambient_backend_model()->photo_config();
   }
 };
 
@@ -640,7 +673,7 @@
   photo_controller()->StartScreenUpdate();
   RunUntilImagesReady();
   EXPECT_THAT(photo_controller()->ambient_backend_model()->all_decoded_topics(),
-              SizeIs(kNumDynamicAssets));
+              SizeIs(photo_config().GetNumDecodedTopicsToBuffer()));
 }
 
 TEST_F(AmbientPhotoControllerAnimationTest,
@@ -655,32 +688,31 @@
       AmbientPhotoConfig::Marker::kUiStartRendering);
   // Simulate cycle ending. This should trigger an image refresh.
   photo_controller()->OnMarkerHit(AmbientPhotoConfig::Marker::kUiCycleEnded);
-  RunUntilNextTopicsAdded(kNumDynamicAssets);
+  RunUntilNextTopicsAdded(photo_config().topic_set_size);
   base::circular_deque<PhotoWithDetails> new_photos =
       photo_controller()->ambient_backend_model()->all_decoded_topics();
-  EXPECT_THAT(new_photos, SizeIs(kNumDynamicAssets));
+  EXPECT_THAT(new_photos, SizeIs(photo_config().GetNumDecodedTopicsToBuffer()));
   ASSERT_THAT(old_photos.size(), Eq(new_photos.size()));
 
-  // Verify that the new set actually has different photos from the initial set.
-  EXPECT_THAT(new_photos,
-              Not(AnyOf(Contains(BackedBySameImageAs(old_photos[0])),
-                        Contains(BackedBySameImageAs(old_photos[1])),
-                        Contains(BackedBySameImageAs(old_photos[2])),
-                        Contains(BackedBySameImageAs(old_photos[3])))));
+  // Verify that the new set actually has 2 new photos and 2 photos from the
+  // old set.
+  EXPECT_THAT(new_photos[0], BackedBySameImageAs(old_photos[2]));
+  EXPECT_THAT(new_photos[1], BackedBySameImageAs(old_photos[3]));
+  EXPECT_THAT(old_photos, Not(Contains(BackedBySameImageAs(new_photos[2]))));
+  EXPECT_THAT(old_photos, Not(Contains(BackedBySameImageAs(new_photos[3]))));
   old_photos = new_photos;
 
   // One more cycle.
   photo_controller()->OnMarkerHit(AmbientPhotoConfig::Marker::kUiCycleEnded);
-  RunUntilNextTopicsAdded(kNumDynamicAssets);
+  RunUntilNextTopicsAdded(photo_config().topic_set_size);
   new_photos =
       photo_controller()->ambient_backend_model()->all_decoded_topics();
-  EXPECT_THAT(new_photos, SizeIs(kNumDynamicAssets));
+  EXPECT_THAT(new_photos, SizeIs(photo_config().GetNumDecodedTopicsToBuffer()));
   ASSERT_THAT(old_photos.size(), Eq(new_photos.size()));
-  EXPECT_THAT(new_photos,
-              Not(AnyOf(Contains(BackedBySameImageAs(old_photos[0])),
-                        Contains(BackedBySameImageAs(old_photos[1])),
-                        Contains(BackedBySameImageAs(old_photos[2])),
-                        Contains(BackedBySameImageAs(old_photos[3])))));
+  EXPECT_THAT(new_photos[0], BackedBySameImageAs(old_photos[2]));
+  EXPECT_THAT(new_photos[1], BackedBySameImageAs(old_photos[3]));
+  EXPECT_THAT(old_photos, Not(Contains(BackedBySameImageAs(new_photos[2]))));
+  EXPECT_THAT(old_photos, Not(Contains(BackedBySameImageAs(new_photos[3]))));
 }
 
 TEST_F(AmbientPhotoControllerAnimationTest,
@@ -691,13 +723,18 @@
   photo_controller()->OnMarkerHit(
       AmbientPhotoConfig::Marker::kUiStartRendering);
   photo_controller()->OnMarkerHit(AmbientPhotoConfig::Marker::kUiCycleEnded);
-  RunUntilNextTopicsAdded(kNumDynamicAssets);
+  RunUntilNextTopicsAdded(photo_config().topic_set_size);
 
   // Fast forward time to make sure no more images are prepared after
   // |kNumDynamicAssets| has been added.
+  base::circular_deque<PhotoWithDetails> old_photos =
+      photo_controller()->ambient_backend_model()->all_decoded_topics();
   task_environment()->FastForwardBy(base::Minutes(1));
+  base::circular_deque<PhotoWithDetails> new_photos =
+      photo_controller()->ambient_backend_model()->all_decoded_topics();
   EXPECT_THAT(photo_controller()->ambient_backend_model()->all_decoded_topics(),
-              SizeIs(kNumDynamicAssets));
+              SizeIs(photo_config().GetNumDecodedTopicsToBuffer()));
+  EXPECT_THAT(new_photos, Pointwise(BackedBySameImage(), old_photos));
 }
 
 TEST_F(AmbientPhotoControllerAnimationTest,
@@ -710,33 +747,62 @@
   photo_controller()->OnMarkerHit(
       AmbientPhotoConfig::Marker::kUiStartRendering);
   photo_controller()->OnMarkerHit(AmbientPhotoConfig::Marker::kUiCycleEnded);
-  RunUntilNextTopicsAdded(kNumDynamicAssets / 2);
+  RunUntilNextTopicsAdded(photo_config().topic_set_size / 2);
   base::circular_deque<PhotoWithDetails> new_photos =
       photo_controller()->ambient_backend_model()->all_decoded_topics();
-  EXPECT_THAT(new_photos, SizeIs(kNumDynamicAssets));
+  EXPECT_THAT(new_photos, SizeIs(photo_config().GetNumDecodedTopicsToBuffer()));
   ASSERT_THAT(old_photos.size(), Eq(new_photos.size()));
 
-  EXPECT_THAT(new_photos[0], BackedBySameImageAs(old_photos[2]));
-  EXPECT_THAT(new_photos[1], BackedBySameImageAs(old_photos[3]));
-  EXPECT_THAT(old_photos, Not(Contains(BackedBySameImageAs(new_photos[2]))));
+  EXPECT_THAT(new_photos[0], BackedBySameImageAs(old_photos[1]));
+  EXPECT_THAT(new_photos[1], BackedBySameImageAs(old_photos[2]));
+  EXPECT_THAT(new_photos[2], BackedBySameImageAs(old_photos[3]));
   EXPECT_THAT(old_photos, Not(Contains(BackedBySameImageAs(new_photos[3]))));
   old_photos = new_photos;
 
   // Cycle ends when only half of target amount refreshed.
   photo_controller()->OnMarkerHit(AmbientPhotoConfig::Marker::kUiCycleEnded);
-  RunUntilNextTopicsAdded(kNumDynamicAssets);
+  RunUntilNextTopicsAdded(photo_config().topic_set_size);
   // Fast forward time to make sure no more images are prepared after
-  // |kNumDynamicAssets| has been added.
+  // |photo_config().topic_set_size| has been added.
   task_environment()->FastForwardBy(base::Minutes(1));
   new_photos =
       photo_controller()->ambient_backend_model()->all_decoded_topics();
-  EXPECT_THAT(new_photos, SizeIs(kNumDynamicAssets));
+  EXPECT_THAT(new_photos, SizeIs(photo_config().GetNumDecodedTopicsToBuffer()));
   ASSERT_THAT(old_photos.size(), Eq(new_photos.size()));
-  EXPECT_THAT(new_photos,
-              Not(AnyOf(Contains(BackedBySameImageAs(old_photos[0])),
-                        Contains(BackedBySameImageAs(old_photos[1])),
-                        Contains(BackedBySameImageAs(old_photos[2])),
-                        Contains(BackedBySameImageAs(old_photos[3])))));
+  EXPECT_THAT(new_photos[0], BackedBySameImageAs(old_photos[2]));
+  EXPECT_THAT(new_photos[1], BackedBySameImageAs(old_photos[3]));
+  EXPECT_THAT(old_photos, Not(Contains(BackedBySameImageAs(new_photos[2]))));
+  EXPECT_THAT(old_photos, Not(Contains(BackedBySameImageAs(new_photos[3]))));
+}
+
+TEST_F(AmbientPhotoControllerAnimationTest,
+       HandlesMarkerWhenInitialTopicSetIncomplete) {
+  constexpr base::TimeDelta kPhotoDownloadDelay = base::Seconds(5);
+  constexpr base::TimeDelta kTimeoutAfterFirstPhoto = base::Seconds(10);
+  SetPhotoDownloadDelay(kPhotoDownloadDelay);
+  photo_controller()->StartScreenUpdate();
+  task_environment()->FastForwardBy(kPhotoDownloadDelay +
+                                    kTimeoutAfterFirstPhoto);
+  ASSERT_TRUE(photo_controller()->ambient_backend_model()->ImagesReady());
+  ASSERT_THAT(photo_controller()->ambient_backend_model()->all_decoded_topics(),
+              SizeIs(3));
+
+  // UI starts rendering when only 3/4 of the initial topic set is prepared.
+  // The controller should immediately start preparing another 2 topics (the
+  // size of 1 topic set).
+  photo_controller()->OnMarkerHit(
+      AmbientPhotoConfig::Marker::kUiStartRendering);
+  task_environment()->FastForwardBy(kPhotoDownloadDelay * 2);
+  ASSERT_THAT(photo_controller()->ambient_backend_model()->all_decoded_topics(),
+              SizeIs(photo_config().GetNumDecodedTopicsToBuffer()));
+  // Fast forward time to make sure no more images are prepared after
+  // |photo_config().topic_set_size| has been added.
+  base::circular_deque<PhotoWithDetails> photos_before =
+      photo_controller()->ambient_backend_model()->all_decoded_topics();
+  task_environment()->FastForwardBy(base::Minutes(1));
+  base::circular_deque<PhotoWithDetails> photos_after =
+      photo_controller()->ambient_backend_model()->all_decoded_topics();
+  EXPECT_THAT(photos_after, Pointwise(BackedBySameImage(), photos_before));
 }
 
 }  // namespace ash
diff --git a/ash/ambient/test/ambient_ash_test_base.cc b/ash/ambient/test/ambient_ash_test_base.cc
index 0edac98..2b7b1f0 100644
--- a/ash/ambient/test/ambient_ash_test_base.cc
+++ b/ash/ambient/test/ambient_ash_test_base.cc
@@ -69,7 +69,7 @@
     // Pretend to respond asynchronously.
     base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
         FROM_HERE, base::BindOnce(std::move(callback), std::move(data)),
-        base::Milliseconds(1));
+        photo_download_delay_);
   }
 
   void DownloadPhotoToFile(const std::string& url,
@@ -146,6 +146,10 @@
 
   void SetDecodedPhoto(const gfx::ImageSkia& image) { decoded_image_ = image; }
 
+  void SetPhotoDownloadDelay(base::TimeDelta delay) {
+    photo_download_delay_ = delay;
+  }
+
   const std::map<int, ::ambient::PhotoCacheEntry>& get_files() {
     return files_;
   }
@@ -162,6 +166,8 @@
   absl::optional<gfx::ImageSkia> decoded_image_;
 
   std::map<int, ::ambient::PhotoCacheEntry> files_;
+
+  base::TimeDelta photo_download_delay_ = base::Milliseconds(1);
 };
 
 AmbientAshTestBase::AmbientAshTestBase()
@@ -577,4 +583,11 @@
   photo_cache->SetDecodedPhoto(image);
 }
 
+void AmbientAshTestBase::SetPhotoDownloadDelay(base::TimeDelta delay) {
+  auto* photo_cache = static_cast<TestAmbientPhotoCacheImpl*>(
+      photo_controller()->get_photo_cache_for_testing());
+
+  photo_cache->SetPhotoDownloadDelay(delay);
+}
+
 }  // namespace ash
diff --git a/ash/ambient/test/ambient_ash_test_base.h b/ash/ambient/test/ambient_ash_test_base.h
index 4bb1a43b..3594a0a 100644
--- a/ash/ambient/test/ambient_ash_test_base.h
+++ b/ash/ambient/test/ambient_ash_test_base.h
@@ -18,6 +18,7 @@
 #include "ash/public/cpp/test/test_image_downloader.h"
 #include "ash/test/ash_test_base.h"
 #include "base/callback.h"
+#include "base/time/time.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
@@ -189,6 +190,8 @@
 
   void SetDecodePhotoImage(const gfx::ImageSkia& image);
 
+  void SetPhotoDownloadDelay(base::TimeDelta delay);
+
  private:
   void SpinWaitForAmbientViewAvailable(
       const base::RepeatingClosure& quit_closure);
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index e2e26315..69bd26dd 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -1196,6 +1196,12 @@
   }
 }
 
+void AppListControllerImpl::StartZeroStateSearch(base::OnceClosure callback,
+                                                 base::TimeDelta timeout) {
+  if (client_)
+    client_->StartZeroStateSearch(std::move(callback), timeout);
+}
+
 void AppListControllerImpl::OpenSearchResult(const std::string& result_id,
                                              int event_flags,
                                              AppListLaunchedFrom launched_from,
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index c279e7a..7f6dd3c 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -152,6 +152,8 @@
   AppListNotifier* GetNotifier() override;
   void StartAssistant() override;
   void StartSearch(const std::u16string& raw_query) override;
+  void StartZeroStateSearch(base::OnceClosure callback,
+                            base::TimeDelta timeout) override;
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
                         AppListLaunchedFrom launched_from,
diff --git a/ash/app_list/app_list_presenter_impl.cc b/ash/app_list/app_list_presenter_impl.cc
index a6867dea..6ee1bca 100644
--- a/ash/app_list/app_list_presenter_impl.cc
+++ b/ash/app_list/app_list_presenter_impl.cc
@@ -300,6 +300,7 @@
 void AppListPresenterImpl::SetViewVisibility(bool visible) {
   if (!view_)
     return;
+  view_->OnAppListVisibilityWillChange(visible);
   view_->SetVisible(visible);
   view_->search_box_view()->SetVisible(visible);
 }
diff --git a/ash/app_list/app_list_test_view_delegate.cc b/ash/app_list/app_list_test_view_delegate.cc
index 57854cd..181b0e06 100644
--- a/ash/app_list/app_list_test_view_delegate.cc
+++ b/ash/app_list/app_list_test_view_delegate.cc
@@ -30,6 +30,11 @@
   return true;
 }
 
+void AppListTestViewDelegate::StartZeroStateSearch(base::OnceClosure callback,
+                                                   base::TimeDelta timeout) {
+  std::move(callback).Run();
+}
+
 void AppListTestViewDelegate::OpenSearchResult(
     const std::string& result_id,
     int event_flags,
diff --git a/ash/app_list/app_list_test_view_delegate.h b/ash/app_list/app_list_test_view_delegate.h
index a03204d..0711794 100644
--- a/ash/app_list/app_list_test_view_delegate.h
+++ b/ash/app_list/app_list_test_view_delegate.h
@@ -65,6 +65,8 @@
   bool KeyboardTraversalEngaged() override;
   void StartAssistant() override {}
   void StartSearch(const std::u16string& raw_query) override {}
+  void StartZeroStateSearch(base::OnceClosure callback,
+                            base::TimeDelta timeout) override;
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
                         ash::AppListLaunchedFrom launched_from,
diff --git a/ash/app_list/app_list_view_delegate.h b/ash/app_list/app_list_view_delegate.h
index f3c39b2..3f2b5d4 100644
--- a/ash/app_list/app_list_view_delegate.h
+++ b/ash/app_list/app_list_view_delegate.h
@@ -49,6 +49,13 @@
   // box by the user.
   virtual void StartSearch(const std::u16string& raw_query) = 0;
 
+  // Starts zero state search to load suggested content shown in productivity
+  // launcher. Called when the tablet mode productivity launcher visibility
+  // starts changing to visible. `callback` is called when the zero state search
+  // completes, or times out (i.e. takes more time than `timeout`).
+  virtual void StartZeroStateSearch(base::OnceClosure callback,
+                                    base::TimeDelta timeout) = 0;
+
   // Invoked to open the search result and log a click. If the result is
   // represented by a SuggestedChipView or is a zero state result,
   // |suggested_index| is the index of the view in the list of suggestions.
diff --git a/ash/app_list/views/app_list_bubble_apps_page.cc b/ash/app_list/views/app_list_bubble_apps_page.cc
index 6249e7c..6cafec4 100644
--- a/ash/app_list/views/app_list_bubble_apps_page.cc
+++ b/ash/app_list/views/app_list_bubble_apps_page.cc
@@ -639,8 +639,8 @@
   // Hide the undo toast instantly before starting the toast fade in animation.
   toast_container_->layer()->SetOpacity(0.f);
 
-  animation_builder.GetCurrentSequence().SetOpacity(toast_container_->layer(),
-                                                    1.f);
+  animation_builder.GetCurrentSequence().SetOpacity(
+      toast_container_->layer(), 1.f, gfx::Tween::ACCEL_5_70_DECEL_90);
 }
 
 void AppListBubbleAppsPage::OnAppsGridViewFadeInAnimationEnded(bool aborted) {
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index fb788cb..47ddf6949 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -1706,6 +1706,10 @@
   }
 }
 
+void AppListView::OnAppListVisibilityWillChange(bool visible) {
+  GetAppsContainerView()->OnAppListVisibilityWillChange(visible);
+}
+
 void AppListView::OnAppListVisibilityChanged(bool shown) {
   GetAppsContainerView()->OnAppListVisibilityChanged(shown);
 }
diff --git a/ash/app_list/views/app_list_view.h b/ash/app_list/views/app_list_view.h
index a404442..5550679 100644
--- a/ash/app_list/views/app_list_view.h
+++ b/ash/app_list/views/app_list_view.h
@@ -410,6 +410,7 @@
   void UpdateWindowTitle();
 
   // Called when app list visibility changed.
+  void OnAppListVisibilityWillChange(bool visible);
   void OnAppListVisibilityChanged(bool shown);
 
  private:
diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc
index 62b6634..af8432b 100644
--- a/ash/app_list/views/apps_container_view.cc
+++ b/ash/app_list/views/apps_container_view.cc
@@ -139,6 +139,10 @@
 // `scrollable_container_`.
 constexpr int kDefaultFadeoutMaskHeight = 16;
 
+// Max amount of time to wait for zero state results when refreshing recent apps
+// and continue section when launcher becomes visible.
+constexpr base::TimeDelta kZeroStateSearchTimeout = base::Milliseconds(16);
+
 }  // namespace
 
 // A view that contains continue section, recent apps and a separator view,
@@ -500,6 +504,17 @@
   show_state_ = AppsContainerView::SHOW_APPS;
 }
 
+void AppsContainerView::OnAppListVisibilityWillChange(bool visible) {
+  // Start zero state search to refresh contents of the continue section and
+  // recent apps (which are only shown for productivity launcher).
+  if (visible && features::IsProductivityLauncherEnabled()) {
+    contents_view_->GetAppListMainView()->view_delegate()->StartZeroStateSearch(
+        base::BindOnce(&AppsContainerView::UpdateRecentApps,
+                       weak_ptr_factory_.GetWeakPtr()),
+        kZeroStateSearchTimeout);
+  }
+}
+
 void AppsContainerView::OnAppListVisibilityChanged(bool shown) {
   if (!toast_container_)
     return;
@@ -1513,14 +1528,14 @@
   // Hide the toast to prepare for the fade in animation,
   toast_container_->layer()->SetOpacity(0.f);
 
-  animation_builder.GetCurrentSequence().SetOpacity(toast_container_->layer(),
-                                                    1.f);
+  animation_builder.GetCurrentSequence().SetOpacity(
+      toast_container_->layer(), 1.f, gfx::Tween::ACCEL_5_70_DECEL_90);
 
   // Continue section should be faded in only when the page changes.
   if (page_change) {
     continue_container_->layer()->SetOpacity(0.f);
     animation_builder.GetCurrentSequence().SetOpacity(
-        continue_container_->layer(), 1.f);
+        continue_container_->layer(), 1.f, gfx::Tween::ACCEL_5_70_DECEL_90);
   }
 }
 
diff --git a/ash/app_list/views/apps_container_view.h b/ash/app_list/views/apps_container_view.h
index 3f416cad..e4dba36 100644
--- a/ash/app_list/views/apps_container_view.h
+++ b/ash/app_list/views/apps_container_view.h
@@ -183,6 +183,12 @@
   void OnTemporarySortOrderChanged(
       const absl::optional<AppListSortOrder>& new_order);
 
+  // Called by app list controller when the app list visibility is about to
+  // change - when the app list is about to be shown, initiates zero state
+  // search in order to update set of apps shown in recent apps and continue
+  // section contents.
+  void OnAppListVisibilityWillChange(bool visible);
+
   // Updates the nudge in `toast_container_` when app list visibility changes.
   void OnAppListVisibilityChanged(bool shown);
 
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 7713d41..ae86f0e 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -2095,22 +2095,6 @@
   reorder_animation_tracker_->Start(metrics_util::ForSmoothness(
       base::BindRepeating(&ReportReorderAnimationSmoothness, IsTabletMode())));
 
-  const absl::optional<VisibleItemIndexRange> range =
-      GetVisibleItemIndexRange();
-
-  // TODO(https://crbug.com/1289411): handle the case that `range` is null.
-  DCHECK(range);
-
-  // Assume all the items matched by the indices in `range` are placed on the
-  // same page.
-  const int page_index =
-      view_structure_.GetIndexFromModelIndex(range->first_index).page;
-  const int offset =
-      kFadeAnimationOffsetRatio * GetTotalTileSize(page_index).height();
-
-  gfx::Transform translate_offset;
-  translate_offset.Translate(0, offset);
-
   {
     views::AnimationBuilder animation_builder;
     reorder_animation_abort_handle_ = animation_builder.GetAbortHandle();
@@ -2123,9 +2107,7 @@
                                   /*abort=*/true))
         .Once()
         .SetDuration(kFadeOutAnimationDuration)
-        .SetOpacity(layer(), 0.f, gfx::Tween::LINEAR)
-        .SetTransform(layer(), translate_offset,
-                      gfx::Tween::LINEAR_OUT_SLOW_IN);
+        .SetOpacity(layer(), 0.f, gfx::Tween::LINEAR);
   }
 
   if (fade_out_start_closure_for_test_)
@@ -2170,7 +2152,7 @@
                                 /*abort=*/true))
       .Once()
       .SetDuration(kFadeInAnimationDuration)
-      .SetOpacity(layer(), 1.f, gfx::Tween::LINEAR);
+      .SetOpacity(layer(), 1.f, gfx::Tween::ACCEL_5_70_DECEL_90);
 
   // Assume all the items matched by the indices in `range` are
   // placed on the same page.
@@ -2197,7 +2179,7 @@
     // existing time duration.
     SlideViewIntoPositionWithSequenceBlock(
         animated_view, offset,
-        /*time_delta=*/absl::nullopt, gfx::Tween::LINEAR,
+        /*time_delta=*/absl::nullopt, gfx::Tween::ACCEL_5_70_DECEL_90,
         &animation_builder.GetCurrentSequence());
   }
 
diff --git a/ash/metrics/user_metrics_action.h b/ash/metrics/user_metrics_action.h
index 38c161d..d4cc99a7 100644
--- a/ash/metrics/user_metrics_action.h
+++ b/ash/metrics/user_metrics_action.h
@@ -12,18 +12,9 @@
 // instead of adding things here.
 enum UserMetricsAction {
   UMA_DESKTOP_SWITCH_TASK,
-  UMA_LAUNCHER_BUTTON_PRESSED_WITH_MOUSE,
-  UMA_LAUNCHER_BUTTON_PRESSED_WITH_TOUCH,
-  UMA_LAUNCHER_CLICK_ON_APP,
-  UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON,
   UMA_LAUNCHER_LAUNCH_TASK,
   UMA_LAUNCHER_MINIMIZE_TASK,
   UMA_LAUNCHER_SWITCH_TASK,
-  UMA_SHELF_ALIGNMENT_SET_BOTTOM,
-  UMA_SHELF_ALIGNMENT_SET_LEFT,
-  UMA_SHELF_ALIGNMENT_SET_RIGHT,
-  UMA_SHELF_ITEM_PINNED,
-  UMA_SHELF_ITEM_UNPINNED,
   UMA_STATUS_AREA_AUDIO_CURRENT_INPUT_DEVICE,
   UMA_STATUS_AREA_AUDIO_CURRENT_OUTPUT_DEVICE,
   UMA_STATUS_AREA_AUDIO_SWITCH_INPUT_DEVICE,
diff --git a/ash/metrics/user_metrics_recorder.cc b/ash/metrics/user_metrics_recorder.cc
index 89ad614..e6cabf8 100644
--- a/ash/metrics/user_metrics_recorder.cc
+++ b/ash/metrics/user_metrics_recorder.cc
@@ -17,17 +17,14 @@
 #include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/public/cpp/shelf_item.h"
 #include "ash/public/cpp/shelf_model.h"
-#include "ash/public/cpp/shell_window_ids.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_view.h"
 #include "ash/shell.h"
-#include "ash/wm/desks/desks_util.h"
 #include "ash/wm/window_state.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "components/prefs/pref_service.h"
-#include "ui/aura/window.h"
 
 namespace ash {
 
@@ -100,62 +97,6 @@
   return session->IsActiveUserSessionStarted() && !session->IsScreenLocked();
 }
 
-// Returns a list of window container ids that contain visible windows to be
-// counted for UMA statistics. Note the containers are ordered from top most
-// visible container to the lowest to allow the |GetNumVisibleWindows| method to
-// short circuit when processing a maximized or fullscreen window.
-std::vector<int> GetVisibleWindowContainerIds() {
-  std::vector<int> ids{kShellWindowId_PipContainer,
-                       kShellWindowId_AlwaysOnTopContainer};
-  // TODO(afakhry): Add metrics for the inactive desks.
-  ids.emplace_back(desks_util::GetActiveDeskContainerId());
-  return ids;
-}
-
-// Returns an approximate count of how many windows are currently visible in the
-// primary root window.
-int GetNumVisibleWindowsInPrimaryDisplay() {
-  int visible_window_count = 0;
-  bool maximized_or_fullscreen_window_present = false;
-
-  for (const int& current_container_id : GetVisibleWindowContainerIds()) {
-    if (maximized_or_fullscreen_window_present)
-      break;
-
-    const aura::Window::Windows& children =
-        Shell::GetContainer(Shell::Get()->GetPrimaryRootWindow(),
-                            current_container_id)
-            ->children();
-    // Reverse iterate over the child windows so that they are processed in
-    // visible stacking order.
-    for (aura::Window::Windows::const_reverse_iterator it = children.rbegin(),
-                                                       rend = children.rend();
-         it != rend; ++it) {
-      const aura::Window* child_window = *it;
-      const WindowState* child_window_state = WindowState::Get(child_window);
-
-      if (!child_window->IsVisible() || child_window_state->IsMinimized())
-        continue;
-
-      // Only count activatable windows for 1 reason:
-      //  - Ensures that a browser window and its transient, modal child will
-      //    only count as 1 visible window.
-      if (child_window_state->CanActivate())
-        ++visible_window_count;
-
-      // Stop counting windows that will be hidden by maximized or fullscreen
-      // windows. Only windows in the active desk container and
-      // kShellWindowId_AlwaysOnTopContainer can be maximized or fullscreened
-      // and completely obscure windows beneath them.
-      if (child_window_state->IsMaximizedOrFullscreenOrPinned()) {
-        maximized_or_fullscreen_window_present = true;
-        break;
-      }
-    }
-  }
-  return visible_window_count;
-}
-
 // Records the number of items in the shelf as an UMA statistic.
 void RecordShelfItemCounts() {
   int pinned_item_count = 0;
@@ -222,18 +163,6 @@
       RecordAction(UserMetricsAction("Desktop_SwitchTask"));
       task_switch_metrics_recorder_.OnTaskSwitch(TaskSwitchSource::DESKTOP);
       break;
-    case UMA_LAUNCHER_BUTTON_PRESSED_WITH_MOUSE:
-      RecordAction(UserMetricsAction("Launcher_ButtonPressed_Mouse"));
-      break;
-    case UMA_LAUNCHER_BUTTON_PRESSED_WITH_TOUCH:
-      RecordAction(UserMetricsAction("Launcher_ButtonPressed_Touch"));
-      break;
-    case UMA_LAUNCHER_CLICK_ON_APP:
-      RecordAction(UserMetricsAction("Launcher_ClickOnApp"));
-      break;
-    case UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON:
-      RecordAction(UserMetricsAction("Launcher_ClickOnApplistButton"));
-      break;
     case UMA_LAUNCHER_LAUNCH_TASK:
       RecordAction(UserMetricsAction("Launcher_LaunchTask"));
       task_switch_metrics_recorder_.OnTaskSwitch(TaskSwitchSource::SHELF);
@@ -245,21 +174,6 @@
       RecordAction(UserMetricsAction("Launcher_SwitchTask"));
       task_switch_metrics_recorder_.OnTaskSwitch(TaskSwitchSource::SHELF);
       break;
-    case UMA_SHELF_ALIGNMENT_SET_BOTTOM:
-      RecordAction(UserMetricsAction("Shelf_AlignmentSetBottom"));
-      break;
-    case UMA_SHELF_ALIGNMENT_SET_LEFT:
-      RecordAction(UserMetricsAction("Shelf_AlignmentSetLeft"));
-      break;
-    case UMA_SHELF_ALIGNMENT_SET_RIGHT:
-      RecordAction(UserMetricsAction("Shelf_AlignmentSetRight"));
-      break;
-    case UMA_SHELF_ITEM_PINNED:
-      RecordAction(UserMetricsAction("Shelf_ItemPinned"));
-      break;
-    case UMA_SHELF_ITEM_UNPINNED:
-      RecordAction(UserMetricsAction("Shelf_ItemUnpinned"));
-      break;
     case UMA_STATUS_AREA_AUDIO_CURRENT_INPUT_DEVICE:
       RecordAction(UserMetricsAction("StatusArea_Audio_CurrentInputDevice"));
       break;
@@ -490,8 +404,6 @@
   if (IsUserInActiveDesktopEnvironment()) {
     RecordShelfItemCounts();
     RecordPeriodicAppListMetrics();
-    UMA_HISTOGRAM_COUNTS_100("Ash.NumberOfVisibleWindowsInPrimaryDisplay",
-                             GetNumVisibleWindowsInPrimaryDisplay());
 
     base::UmaHistogramBoolean(
         "Ash.AppNotificationBadgingPref",
diff --git a/ash/metrics/user_metrics_recorder_unittest.cc b/ash/metrics/user_metrics_recorder_unittest.cc
index abaf391d..0fe4a3e 100644
--- a/ash/metrics/user_metrics_recorder_unittest.cc
+++ b/ash/metrics/user_metrics_recorder_unittest.cc
@@ -21,9 +21,6 @@
 namespace ash {
 namespace {
 
-const char kAsh_NumberOfVisibleWindowsInPrimaryDisplay[] =
-    "Ash.NumberOfVisibleWindowsInPrimaryDisplay";
-
 const char kAsh_ActiveWindowShowTypeOverTime[] =
     "Ash.ActiveWindowShowTypeOverTime";
 
@@ -103,7 +100,6 @@
   ASSERT_FALSE(test_api().IsUserInActiveDesktopEnvironment());
   test_api().RecordPeriodicMetrics();
 
-  histograms().ExpectTotalCount(kAsh_NumberOfVisibleWindowsInPrimaryDisplay, 0);
   histograms().ExpectTotalCount(kAsh_Shelf_NumberOfItems, 0);
   histograms().ExpectTotalCount(kAsh_Shelf_NumberOfPinnedItems, 0);
   histograms().ExpectTotalCount(kAsh_Shelf_NumberOfUnpinnedItems, 0);
@@ -118,7 +114,6 @@
   ASSERT_TRUE(test_api().IsUserInActiveDesktopEnvironment());
   test_api().RecordPeriodicMetrics();
 
-  histograms().ExpectTotalCount(kAsh_NumberOfVisibleWindowsInPrimaryDisplay, 1);
   histograms().ExpectTotalCount(kAsh_Shelf_NumberOfItems, 1);
   histograms().ExpectTotalCount(kAsh_Shelf_NumberOfPinnedItems, 1);
   histograms().ExpectTotalCount(kAsh_Shelf_NumberOfUnpinnedItems, 1);
diff --git a/ash/quick_pair/repository/BUILD.gn b/ash/quick_pair/repository/BUILD.gn
index 063acb1..4a8058d0 100644
--- a/ash/quick_pair/repository/BUILD.gn
+++ b/ash/quick_pair/repository/BUILD.gn
@@ -110,6 +110,8 @@
     "fast_pair/pending_write_store_unittest.cc",
     "fast_pair/saved_device_registry_unittest.cc",
     "fast_pair_repository_impl_unittest.cc",
+    "oauth_http_fetcher_unittest.cc",
+    "unauthenticated_http_fetcher_unittest.cc",
   ]
 
   deps = [
@@ -124,8 +126,10 @@
     "//base/test:test_support",
     "//chromeos/services/bluetooth_config/public/cpp",
     "//components/prefs:test_support",
+    "//components/signin/public/identity_manager:test_support",
     "//device/bluetooth:mocks",
     "//net",
+    "//net/traffic_annotation:test_support",
     "//services/data_decoder/public/cpp:test_support",
     "//services/network:test_support",
     "//services/network/public/cpp",
diff --git a/ash/quick_pair/repository/oauth_http_fetcher.cc b/ash/quick_pair/repository/oauth_http_fetcher.cc
index ff771d2..8fd030e 100644
--- a/ash/quick_pair/repository/oauth_http_fetcher.cc
+++ b/ash/quick_pair/repository/oauth_http_fetcher.cc
@@ -96,7 +96,7 @@
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
       QuickPairBrowserDelegate::Get()->GetURLLoaderFactory();
   if (!url_loader_factory) {
-    QP_LOG(WARNING) << __func__ << ": No SharedURLLoaderFactory is available.";
+    QP_LOG(WARNING) << __func__ << ": URLLoaderFactory is not available.";
     std::move(callback_).Run(nullptr, nullptr);
     return;
   }
diff --git a/ash/quick_pair/repository/oauth_http_fetcher.h b/ash/quick_pair/repository/oauth_http_fetcher.h
index 8571f978d..f1509594 100644
--- a/ash/quick_pair/repository/oauth_http_fetcher.h
+++ b/ash/quick_pair/repository/oauth_http_fetcher.h
@@ -7,6 +7,7 @@
 
 #include "ash/quick_pair/repository/http_fetcher.h"
 #include "base/callback.h"
+#include "base/gtest_prod_util.h"
 #include "base/memory/weak_ptr.h"
 #include "components/signin/public/identity_manager/access_token_info.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -60,6 +61,9 @@
   std::string GetRequestTypeForBody(const std::string& body) override;
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(OAuthHttpFetcherTest,
+                           ExecuteGetRequest_MultipleRaceCondition);
+
   void StartRequest(const GURL& url, FetchCompleteCallback callback);
   void OnAccessTokenFetched(GoogleServiceAuthError error,
                             signin::AccessTokenInfo access_token_info);
diff --git a/ash/quick_pair/repository/oauth_http_fetcher_unittest.cc b/ash/quick_pair/repository/oauth_http_fetcher_unittest.cc
new file mode 100644
index 0000000..caaa9fa
--- /dev/null
+++ b/ash/quick_pair/repository/oauth_http_fetcher_unittest.cc
@@ -0,0 +1,241 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/quick_pair/repository/oauth_http_fetcher.h"
+
+#include "ash/quick_pair/common/mock_quick_pair_browser_delegate.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/test/task_environment.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
+#include "net/http/http_util.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char kBody[] = "body";
+constexpr char kTestUrl[] = "http://www.test.com/";
+constexpr char kTestScope[] = "http://www.test.com/scope";
+const net::PartialNetworkTrafficAnnotationTag kTrafficAnnotation =
+    net::DefinePartialNetworkTrafficAnnotation("test_request",
+                                               "oauth2_api_call_flow",
+                                               R"(
+      semantics {
+          sender: "Test Request"
+        description:
+            "Test request."
+        trigger:
+          "Test request."
+        data: "Test Request."
+        destination: GOOGLE_OWNED_SERVICE
+      }
+      policy {
+          cookies_allowed: NO
+          setting:
+            "Test Request."
+        })");
+
+}  // namespace
+
+namespace ash {
+namespace quick_pair {
+
+class OAuthHttpFetcherTest : public testing::Test {
+ public:
+  OAuthHttpFetcherTest() : identity_test_env_(&url_loader_factory_) {
+    identity_test_env_.MakePrimaryAccountAvailable("1@mail.com",
+                                                   signin::ConsentLevel::kSync);
+  }
+
+  void SetUp() override {
+    http_fetcher_ =
+        std::make_unique<OAuthHttpFetcher>(kTrafficAnnotation, kTestScope);
+    browser_delegate_ = std::make_unique<MockQuickPairBrowserDelegate>();
+    ON_CALL(*browser_delegate_, GetURLLoaderFactory())
+        .WillByDefault(
+            testing::Return(url_loader_factory_.GetSafeWeakWrapper()));
+    ON_CALL(*browser_delegate_, GetIdentityManager())
+        .WillByDefault(testing::Return(identity_test_env_.identity_manager()));
+    identity_test_env_.SetAutomaticIssueOfAccessTokens(true);
+  }
+
+  void TearDown() override { url_loader_factory_.ClearResponses(); }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<OAuthHttpFetcher> http_fetcher_;
+  std::unique_ptr<MockQuickPairBrowserDelegate> browser_delegate_;
+  network::TestURLLoaderFactory url_loader_factory_;
+  signin::IdentityTestEnvironment identity_test_env_;
+};
+
+TEST_F(OAuthHttpFetcherTest, ExecuteGetRequest_Success) {
+  GURL url(kTestUrl);
+  std::string body(kBody);
+  auto head = network::mojom::URLResponseHead::New();
+  head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+      net::HttpUtil::AssembleRawHeaders(""));
+  head->headers->GetMimeType(&head->mime_type);
+  network::URLLoaderCompletionStatus status(net::Error::OK);
+  status.decoded_body_length = body.size();
+  url_loader_factory_.AddResponse(url, std::move(head), body, status);
+
+  http_fetcher_->ExecuteGetRequest(
+      url, base::BindOnce([](std::unique_ptr<std::string> response,
+                             std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_EQ(kBody, *response);
+        ASSERT_TRUE(result->IsSuccess());
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OAuthHttpFetcherTest, ExecuteGetRequest_Failure) {
+  url_loader_factory_.AddResponse(kTestUrl, "",
+                                  net::HTTP_INTERNAL_SERVER_ERROR);
+
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce([](std::unique_ptr<std::string> response,
+                        std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_EQ(nullptr, response);
+        ASSERT_FALSE(result->IsSuccess());
+        ASSERT_EQ(result->http_response_error(),
+                  net::HTTP_INTERNAL_SERVER_ERROR);
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OAuthHttpFetcherTest, ExecuteGetRequest_MultipleCalls) {
+  url_loader_factory_.AddResponse(kTestUrl, "",
+                                  net::HTTP_INTERNAL_SERVER_ERROR);
+
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce([](std::unique_ptr<std::string> response,
+                        std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_EQ(nullptr, response);
+        ASSERT_FALSE(result->IsSuccess());
+        ASSERT_EQ(result->http_response_error(),
+                  net::HTTP_INTERNAL_SERVER_ERROR);
+      }));
+#if DCHECK_IS_ON()
+  EXPECT_DEATH(
+      http_fetcher_->ExecuteGetRequest(GURL(kTestUrl), base::DoNothing()), "");
+#else
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce(
+          [](std::unique_ptr<std::string> response,
+             std::unique_ptr<FastPairHttpResult> result) { FAIL(); }));
+#endif
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OAuthHttpFetcherTest, ExecuteGetRequest_NoToken) {
+  identity_test_env_.SetAutomaticIssueOfAccessTokens(false);
+  url_loader_factory_.AddResponse(kTestUrl, "",
+                                  net::HTTP_INTERNAL_SERVER_ERROR);
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce([](std::unique_ptr<std::string> response,
+                        std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_EQ(nullptr, response);
+        ASSERT_EQ(nullptr, result);
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OAuthHttpFetcherTest, ExecuteGetRequest_NoUrlFactory) {
+  ON_CALL(*browser_delegate_, GetURLLoaderFactory())
+      .WillByDefault(testing::Return(nullptr));
+  url_loader_factory_.AddResponse(kTestUrl, "",
+                                  net::HTTP_INTERNAL_SERVER_ERROR);
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce([](std::unique_ptr<std::string> response,
+                        std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_EQ(nullptr, response);
+        ASSERT_EQ(nullptr, result);
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OAuthHttpFetcherTest, ExecuteGetRequest_NoIdentityManager) {
+  ON_CALL(*browser_delegate_, GetIdentityManager())
+      .WillByDefault(testing::Return(nullptr));
+
+#if DCHECK_IS_ON()
+  EXPECT_DEATH(
+      http_fetcher_->ExecuteGetRequest(GURL(kTestUrl), base::DoNothing()), "");
+#else
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce(
+          [](std::unique_ptr<std::string> response,
+             std::unique_ptr<FastPairHttpResult> result) { FAIL(); }));
+#endif
+
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OAuthHttpFetcherTest, ExecuteGetRequest_MultipleRaceCondition) {
+  http_fetcher_->ExecuteGetRequest(GURL(kTestUrl), base::DoNothing());
+#if DCHECK_IS_ON()
+  EXPECT_DEATH(
+      http_fetcher_->ExecuteGetRequest(GURL(kTestUrl), base::DoNothing()), "");
+#else
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce(
+          [](std::unique_ptr<std::string> response,
+             std::unique_ptr<FastPairHttpResult> result) { FAIL(); }));
+#endif
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OAuthHttpFetcherTest, ExecutePostRequest_Success) {
+  GURL url(kTestUrl);
+  std::string body(kBody);
+  auto head = network::mojom::URLResponseHead::New();
+  head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+      net::HttpUtil::AssembleRawHeaders(""));
+  head->headers->GetMimeType(&head->mime_type);
+  network::URLLoaderCompletionStatus status(net::Error::OK);
+  status.decoded_body_length = body.size();
+  url_loader_factory_.AddResponse(url, std::move(head), body, status);
+
+  http_fetcher_->ExecutePostRequest(
+      url, kBody,
+      base::BindOnce([](std::unique_ptr<std::string> response,
+                        std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_TRUE(result->IsSuccess());
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(OAuthHttpFetcherTest, ExecuteDeleteRequest_Success) {
+  GURL url(kTestUrl);
+  std::string body(kBody);
+  auto head = network::mojom::URLResponseHead::New();
+  head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+      net::HttpUtil::AssembleRawHeaders(""));
+  head->headers->GetMimeType(&head->mime_type);
+  network::URLLoaderCompletionStatus status(net::Error::OK);
+  status.decoded_body_length = body.size();
+  url_loader_factory_.AddResponse(url, std::move(head), body, status);
+
+  http_fetcher_->ExecuteDeleteRequest(
+      url, base::BindOnce([](std::unique_ptr<std::string> response,
+                             std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_TRUE(result->IsSuccess());
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/quick_pair/repository/unauthenticated_http_fetcher.cc b/ash/quick_pair/repository/unauthenticated_http_fetcher.cc
index 85a94e9..a047ab7 100644
--- a/ash/quick_pair/repository/unauthenticated_http_fetcher.cc
+++ b/ash/quick_pair/repository/unauthenticated_http_fetcher.cc
@@ -14,7 +14,7 @@
 
 namespace {
 
-// Max size set to 2MB.  This is well over the expected maximum for our
+// Max size set to 2MB. This is well over the expected maximum for our
 // expected responses, however it can be increased if needed in the future.
 constexpr int kMaxDownloadBytes = 2 * 1024 * 1024;
 
diff --git a/ash/quick_pair/repository/unauthenticated_http_fetcher_unittest.cc b/ash/quick_pair/repository/unauthenticated_http_fetcher_unittest.cc
new file mode 100644
index 0000000..3ce0fe88
--- /dev/null
+++ b/ash/quick_pair/repository/unauthenticated_http_fetcher_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/quick_pair/repository/unauthenticated_http_fetcher.h"
+
+#include "ash/quick_pair/common/mock_quick_pair_browser_delegate.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/test/task_environment.h"
+#include "net/http/http_util.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char kBody[] = "body";
+constexpr char kTestUrl[] = "http://www.test.com/";
+
+}  // namespace
+
+namespace ash {
+namespace quick_pair {
+
+class UnauthenticatedHttpFetcherTest : public testing::Test {
+ public:
+  void SetUp() override {
+    http_fetcher_ = std::make_unique<UnauthenticatedHttpFetcher>(
+        TRAFFIC_ANNOTATION_FOR_TESTS);
+    browser_delegate_ = std::make_unique<MockQuickPairBrowserDelegate>();
+    ON_CALL(*browser_delegate_, GetURLLoaderFactory())
+        .WillByDefault(
+            testing::Return(url_loader_factory_.GetSafeWeakWrapper()));
+  }
+
+  void TearDown() override { url_loader_factory_.ClearResponses(); }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<UnauthenticatedHttpFetcher> http_fetcher_;
+  std::unique_ptr<MockQuickPairBrowserDelegate> browser_delegate_;
+  network::TestURLLoaderFactory url_loader_factory_;
+};
+
+TEST_F(UnauthenticatedHttpFetcherTest, ExecuteGetRequest_Success) {
+  GURL url(kTestUrl);
+  std::string body(kBody);
+  auto head = network::mojom::URLResponseHead::New();
+  head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+      net::HttpUtil::AssembleRawHeaders(""));
+  head->headers->GetMimeType(&head->mime_type);
+  network::URLLoaderCompletionStatus status(net::Error::OK);
+  status.decoded_body_length = body.size();
+  url_loader_factory_.AddResponse(url, std::move(head), body, status);
+
+  http_fetcher_->ExecuteGetRequest(
+      url, base::BindOnce([](std::unique_ptr<std::string> response,
+                             std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_EQ(kBody, *response);
+        ASSERT_TRUE(result->IsSuccess());
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(UnauthenticatedHttpFetcherTest, ExecuteGetRequest_Failure) {
+  url_loader_factory_.AddResponse(kTestUrl, "",
+                                  net::HTTP_INTERNAL_SERVER_ERROR);
+
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce([](std::unique_ptr<std::string> response,
+                        std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_EQ(nullptr, response);
+        ASSERT_FALSE(result->IsSuccess());
+        ASSERT_EQ(result->http_response_error(),
+                  net::HTTP_INTERNAL_SERVER_ERROR);
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(UnauthenticatedHttpFetcherTest, ExecuteGetRequest_Failure_NoUrlLoader) {
+  ON_CALL(*browser_delegate_, GetURLLoaderFactory())
+      .WillByDefault(testing::Return(nullptr));
+
+  http_fetcher_->ExecuteGetRequest(
+      GURL(kTestUrl),
+      base::BindOnce([](std::unique_ptr<std::string> response,
+                        std::unique_ptr<FastPairHttpResult> result) {
+        ASSERT_EQ(nullptr, response);
+        ASSERT_EQ(nullptr, result);
+      }));
+  task_environment_.RunUntilIdle();
+}
+
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/shelf/shelf_button_pressed_metric_tracker.cc b/ash/shelf/shelf_button_pressed_metric_tracker.cc
index 03967d5..56c1afc 100644
--- a/ash/shelf/shelf_button_pressed_metric_tracker.cc
+++ b/ash/shelf/shelf_button_pressed_metric_tracker.cc
@@ -7,6 +7,7 @@
 #include "ash/metrics/user_metrics_recorder.h"
 #include "ash/shell.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics.h"
 #include "base/time/default_tick_clock.h"
 #include "ui/views/controls/button/button.h"
 
@@ -46,12 +47,12 @@
 
 void ShelfButtonPressedMetricTracker::RecordButtonPressedSource(
     const ui::Event& event) {
+  // NOTE: These metrics are called "launcher" instead of "shelf" because the
+  // original name of the shelf UI was "launcher".
   if (event.IsMouseEvent()) {
-    Shell::Get()->metrics()->RecordUserMetricsAction(
-        UMA_LAUNCHER_BUTTON_PRESSED_WITH_MOUSE);
+    base::RecordAction(base::UserMetricsAction("Launcher_ButtonPressed_Mouse"));
   } else if (event.IsGestureEvent()) {
-    Shell::Get()->metrics()->RecordUserMetricsAction(
-        UMA_LAUNCHER_BUTTON_PRESSED_WITH_TOUCH);
+    base::RecordAction(base::UserMetricsAction("Launcher_ButtonPressed_Touch"));
   }
 }
 
diff --git a/ash/shelf/shelf_context_menu_model.cc b/ash/shelf/shelf_context_menu_model.cc
index 5fb4c36..ce900836 100644
--- a/ash/shelf/shelf_context_menu_model.cc
+++ b/ash/shelf/shelf_context_menu_model.cc
@@ -87,7 +87,6 @@
   if (!prefs)  // Null during startup.
     return;
 
-  UserMetricsRecorder* metrics = shell->metrics();
   // Clamshell mode only options should not activate in tablet mode.
   const bool is_tablet_mode = shell->tablet_mode_controller()->InTabletMode();
   switch (command_id) {
@@ -101,17 +100,17 @@
       break;
     case MENU_ALIGNMENT_LEFT:
       DCHECK(!is_tablet_mode);
-      metrics->RecordUserMetricsAction(UMA_SHELF_ALIGNMENT_SET_LEFT);
+      base::RecordAction(base::UserMetricsAction("Shelf_AlignmentSetLeft"));
       SetShelfAlignmentPref(prefs, display_id_, ShelfAlignment::kLeft);
       break;
     case MENU_ALIGNMENT_RIGHT:
       DCHECK(!is_tablet_mode);
-      metrics->RecordUserMetricsAction(UMA_SHELF_ALIGNMENT_SET_RIGHT);
+      base::RecordAction(base::UserMetricsAction("Shelf_AlignmentSetRight"));
       SetShelfAlignmentPref(prefs, display_id_, ShelfAlignment::kRight);
       break;
     case MENU_ALIGNMENT_BOTTOM:
       DCHECK(!is_tablet_mode);
-      metrics->RecordUserMetricsAction(UMA_SHELF_ALIGNMENT_SET_BOTTOM);
+      base::RecordAction(base::UserMetricsAction("Shelf_AlignmentSetBottom"));
       SetShelfAlignmentPref(prefs, display_id_, ShelfAlignment::kBottom);
       break;
     case MENU_CHANGE_WALLPAPER:
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index eff652eb..47316ed 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -14,7 +14,6 @@
 #include "ash/constants/ash_features.h"
 #include "ash/keyboard/keyboard_util.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
-#include "ash/metrics/user_metrics_recorder.h"
 #include "ash/public/cpp/metrics_util.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shelf_types.h"
@@ -51,6 +50,7 @@
 #include "base/cxx17_backports.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics.h"
 #include "base/scoped_observation.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/timer/timer.h"
@@ -257,8 +257,11 @@
 // Records the user metric action for whenever a shelf item is pinned or
 // unpinned.
 void RecordPinUnpinUserAction(bool pinned) {
-  Shell::Get()->metrics()->RecordUserMetricsAction(
-      pinned ? UMA_SHELF_ITEM_PINNED : UMA_SHELF_ITEM_UNPINNED);
+  if (pinned) {
+    base::RecordAction(base::UserMetricsAction("Shelf_ItemPinned"));
+  } else {
+    base::RecordAction(base::UserMetricsAction("Shelf_ItemUnpinned"));
+  }
 }
 
 }  // namespace
@@ -793,8 +796,7 @@
     case TYPE_BROWSER_SHORTCUT:
     case TYPE_APP:
     case TYPE_UNPINNED_BROWSER_SHORTCUT:
-      Shell::Get()->metrics()->RecordUserMetricsAction(
-          UMA_LAUNCHER_CLICK_ON_APP);
+      base::RecordAction(base::UserMetricsAction("Launcher_ClickOnApp"));
       break;
 
     case TYPE_DIALOG:
diff --git a/ash/shortcut_viewer/views/keyboard_shortcut_view.cc b/ash/shortcut_viewer/views/keyboard_shortcut_view.cc
index bf756ec..fc63030 100644
--- a/ash/shortcut_viewer/views/keyboard_shortcut_view.cc
+++ b/ash/shortcut_viewer/views/keyboard_shortcut_view.cc
@@ -27,7 +27,6 @@
 #include "ash/shortcut_viewer/views/ksv_search_box_view.h"
 #include "base/bind.h"
 #include "base/i18n/string_search.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/string_number_conversions.h"
@@ -41,11 +40,9 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/chromeos/events/keyboard_layout_util.h"
-#include "ui/compositor/compositor.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/paint_vector_icon.h"
-#include "ui/gfx/presentation_feedback.h"
 #include "ui/views/accessibility/accessibility_paint_checks.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/background.h"
@@ -175,7 +172,6 @@
     else
       g_ksv_view->GetWidget()->Activate();
   } else {
-    const base::TimeTicks start_time = base::TimeTicks::Now();
     TRACE_EVENT0("shortcut_viewer", "CreateWidget");
     base::RecordAction(
         base::UserMetricsAction("KeyboardShortcutViewer.CreateWindow"));
@@ -229,14 +225,6 @@
     g_ksv_view->did_first_paint_ = false;
     g_ksv_view->GetWidget()->Show();
     g_ksv_view->search_box_view_->search_box()->RequestFocus();
-
-    widget->GetCompositor()->RequestPresentationTimeForNextFrame(base::BindOnce(
-        [](base::TimeTicks start_time,
-           const gfx::PresentationFeedback& feedback) {
-          UMA_HISTOGRAM_TIMES("Keyboard.ShortcutViewer.StartupTime",
-                              feedback.timestamp - start_time);
-        },
-        start_time));
   }
   return g_ksv_view->GetWidget();
 }
diff --git a/ash/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc b/ash/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc
index 0d09ae2..b96834b 100644
--- a/ash/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc
+++ b/ash/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc
@@ -11,11 +11,9 @@
 #include "ash/shortcut_viewer/views/ksv_search_box_view.h"
 #include "ash/test/ash_test_base.h"
 #include "base/bind.h"
-#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/window.h"
-#include "ui/compositor/test/test_utils.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/events/devices/device_data_manager_test_api.h"
@@ -82,8 +80,6 @@
     }
   }
 
-  base::HistogramTester histograms_;
-
  private:
   KeyboardShortcutView* GetView() const {
     return KeyboardShortcutView::GetInstanceForTesting();
@@ -100,13 +96,6 @@
   widget->CloseNow();
 }
 
-TEST_F(KeyboardShortcutViewTest, StartupTimeHistogram) {
-  views::Widget* widget = Toggle();
-  EXPECT_TRUE(ui::WaitForNextFrameToBePresented(widget->GetCompositor()));
-  histograms_.ExpectTotalCount("Keyboard.ShortcutViewer.StartupTime", 1);
-  widget->CloseNow();
-}
-
 // KeyboardShortcutViewer window should be centered in screen.
 TEST_F(KeyboardShortcutViewTest, CenterWindowInScreen) {
   // Show the widget.
diff --git a/ash/webui/personalization_app/mojom/personalization_app.mojom b/ash/webui/personalization_app/mojom/personalization_app.mojom
index 81a41fe..8be0c0ea 100644
--- a/ash/webui/personalization_app/mojom/personalization_app.mojom
+++ b/ash/webui/personalization_app/mojom/personalization_app.mojom
@@ -52,6 +52,19 @@
   kDark = 2,
 };
 
+// Values for the user-level setting indicating access to Google Photos data.
+enum GooglePhotosEnablementState {
+  // Setting could not be retrieved due to a network or server error, or the
+  // server returned an unknown setting state.
+  kError = 0,
+
+  // An enterprise setting prevents this user from accessing Google Photos data.
+  kDisabled = 1,
+
+  // This user can access Google Photos data.
+  kEnabled = 2,
+};
+
 // WallpaperCollection contains a list of |WallpaperImage| objects.
 struct WallpaperCollection {
     // This ID is used as the argument to |FetchImagesForCollection|.
@@ -197,6 +210,9 @@
     // will be -1 on failure.
     FetchGooglePhotosCount() => (int32 count);
 
+    // Fetch whether the user is allowed to access Google Photos data.
+    FetchGooglePhotosEnabled() => (GooglePhotosEnablementState state);
+
     // Fetch photo(s) the user has stored in Google Photos. If provided,
     // |item_id| specifies one photo to fetch. If provided, |album_id| specifies
     // an album whose photos are fetched. At most one of |item_id| or |album_id|
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.html
index 2ef387e5..806da6b6 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.html
@@ -3,6 +3,7 @@
     overflow: hidden;
   }
 
+  iron-scroll-threshold,
   iron-list {
     height: 100%;
     width: 100%;
@@ -39,28 +40,32 @@
     width: 100%;
   }
 </style>
-<iron-list id="grid" items="[[photosByRow_]]" as="row">
-  <template>
-    <div class="row" rowindex$="[[index]]" tabindex$="[[tabIndex]]"
-      on-focus="onGridRowFocused_" on-keydown="onGridRowKeyDown_">
-      <template is="dom-if"
-        if="[[isGridRowTitleVisible_(row, photosBySection_)]]">
-        <p class="title">[[getGridRowTitle_(row, photosBySection_)]]</p>
-      </template>
-      <div class="photos">
-        <template is="dom-repeat" items="[[row]]" as="photo">
-          <wallpaper-grid-item
-            class="photo"
-            colindex$="[[index]]"
-            image-src="[[photo.url.url]]"
-            on-click="onPhotoSelected_"
-            on-keypress="onPhotoSelected_"
-            selected="[[isPhotoSelected_(
-              photo, currentSelected_, pendingSelected_)]]"
-            tabindex="-1">
-          </wallpaper-grid-item>
+<iron-scroll-threshold id="gridScrollThreshold"
+  on-lower-threshold="onGridScrollThresholdReached_">
+  <iron-list id="grid" items="[[photosByRow_]]" as="row"
+    scroll-target="gridScrollThreshold">
+    <template>
+      <div class="row" rowindex$="[[index]]" tabindex$="[[tabIndex]]"
+        on-focus="onGridRowFocused_" on-keydown="onGridRowKeyDown_">
+        <template is="dom-if"
+          if="[[isGridRowTitleVisible_(row, photosBySection_)]]">
+          <p class="title">[[getGridRowTitle_(row, photosBySection_)]]</p>
         </template>
+        <div class="photos">
+          <template is="dom-repeat" items="[[row]]" as="photo">
+            <wallpaper-grid-item
+              class="photo"
+              colindex$="[[index]]"
+              image-src="[[photo.url.url]]"
+              on-click="onPhotoSelected_"
+              on-keypress="onPhotoSelected_"
+              selected="[[isPhotoSelected_(
+                photo, currentSelected_, pendingSelected_)]]"
+              tabindex="-1">
+            </wallpaper-grid-item>
+          </template>
+        </div>
       </div>
-    </div>
-  </template>
-</iron-list>
+    </template>
+  </iron-list>
+</iron-scroll-threshold>
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
index b5e3b13..d2492633 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
@@ -7,12 +7,14 @@
  */
 
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
+import 'chrome://resources/polymer/v3_0/iron-scroll-threshold/iron-scroll-threshold.js';
 import './styles.js';
 import '../../common/styles.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
+import {IronScrollThresholdElement} from 'chrome://resources/polymer/v3_0/iron-scroll-threshold/iron-scroll-threshold.js';
 import {afterNextRender, html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getNumberOfGridItemsPerRow, isNonEmptyArray, isSelectionEvent, normalizeKeyForRTL} from '../../common/utils.js';
@@ -20,7 +22,7 @@
 import {WithPersonalizationStore} from '../personalization_store.js';
 import {isGooglePhotosPhoto} from '../utils.js';
 
-import {selectWallpaper} from './wallpaper_controller.js';
+import {fetchGooglePhotosPhotos, selectWallpaper} from './wallpaper_controller.js';
 import {getWallpaperProvider} from './wallpaper_interface_provider.js';
 
 /** A list of |GooglePhotosPhoto|'s to be rendered in a row. */
@@ -33,7 +35,7 @@
 };
 
 export interface GooglePhotosPhotos {
-  $: {grid: IronListElement;};
+  $: {grid: IronListElement; gridScrollThreshold: IronScrollThresholdElement};
 }
 
 export class GooglePhotosPhotos extends WithPersonalizationStore {
@@ -63,16 +65,12 @@
 
       pendingSelected_: Object,
       photos_: Array,
-
-      photosByRow_: {
-        type: Array,
-        computed: 'computePhotosByRow_(photosBySection_)',
-      },
+      photosByRow_: Array,
 
       photosBySection_: {
         type: Array,
-        computed:
-            'computePhotosBySection_(photos_, photosLoading_, photosPerRow_)',
+        computed: 'computePhotosBySection_(photos_, photosPerRow_)',
+        observer: 'onPhotosBySectionChanged_',
       },
 
       photosLoading_: Boolean,
@@ -83,6 +81,11 @@
           return getNumberOfGridItemsPerRow();
         },
       },
+
+      photosResumeToken_: {
+        type: String,
+        observer: 'onPhotosResumeTokenChanged_',
+      },
     };
   }
 
@@ -119,6 +122,9 @@
   /** The number of photos to render per row in a grid. */
   private photosPerRow_: number;
 
+  /** The resume token needed to fetch the next page of photos. */
+  private photosResumeToken_: string|null;
+
   /** The singleton wallpaper provider interface. */
   private wallpaperProvider_: WallpaperProviderInterface =
       getWallpaperProvider();
@@ -136,20 +142,29 @@
         'photos_', state => state.wallpaper.googlePhotos.photos);
     this.watch<GooglePhotosPhotos['photosLoading_']>(
         'photosLoading_', state => state.wallpaper.loading.googlePhotos.photos);
+    this.watch<GooglePhotosPhotos['photosResumeToken_']>(
+        'photosResumeToken_',
+        state => state.wallpaper.googlePhotos.resumeTokens.photos);
 
     this.updateFromStore();
   }
 
-  /** Invoked on changes to this element's |hidden| state. */
-  private onHiddenChanged_(hidden: GooglePhotosPhotos['hidden']) {
-    if (hidden) {
+  /** Invoked on grid scroll threshold reached. */
+  private onGridScrollThresholdReached_() {
+    // Ignore this event if fired during initialization.
+    if (!this.$.gridScrollThreshold.scrollHeight) {
+      this.$.gridScrollThreshold.clearTriggers();
       return;
     }
 
-    // When iron-list items change while their parent element is hidden, the
-    // iron-list will render incorrectly. Force relayout by invalidating the
-    // iron-list when this element becomes visible.
-    afterNextRender(this, () => this.$.grid.fire('iron-resize'));
+    // Ignore this event if photos are already being loading or if there is no
+    // resume token (indicating there are no additional photos to load).
+    if (this.photosLoading_ === true || this.photosResumeToken_ === null) {
+      return;
+    }
+
+    // Fetch the next page of photos.
+    fetchGooglePhotosPhotos(this.wallpaperProvider_, this.getStore());
   }
 
   /** Invoked on focus of a grid row. */
@@ -202,6 +217,18 @@
     }
   }
 
+  /** Invoked on changes to this element's |hidden| state. */
+  private onHiddenChanged_(hidden: GooglePhotosPhotos['hidden']) {
+    if (hidden) {
+      return;
+    }
+
+    // When iron-list items change while their parent element is hidden, the
+    // iron-list will render incorrectly. Force relayout by invalidating the
+    // iron-list when this element becomes visible.
+    afterNextRender(this, () => this.$.grid.fire('iron-resize'));
+  }
+
   /** Invoked on selection of a photo. */
   private onPhotoSelected_(e: Event&{model: {photo: GooglePhotosPhoto}}) {
     assert(e.model.photo);
@@ -210,31 +237,55 @@
     }
   }
 
+  /** Invoked on changes to |photosBySection_|. */
+  private onPhotosBySectionChanged_(
+      photosBySection: GooglePhotosPhotos['photosBySection_']) {
+    if (!isNonEmptyArray(photosBySection)) {
+      this.photosByRow_ = null;
+      return;
+    }
+
+    const photosByRow = photosBySection.flatMap(section => section.rows);
+
+    // Case: First batch of photos.
+    if (this.photosByRow_ === null) {
+      this.photosByRow_ = photosByRow;
+      return;
+    }
+
+    // Case: Subsequent batches of photos.
+    photosByRow.forEach((row, i) => {
+      if (i < this.photosByRow_!.length) {
+        this.set(`photosByRow_.${i}`, row);
+      } else {
+        this.push('photosByRow_', row);
+      }
+    });
+
+    while (this.photosByRow_.length > photosByRow.length) {
+      this.pop('photosByRow_');
+    }
+  }
+
+  /** Invoked on changes to |photosResumeToken_|. */
+  private onPhotosResumeTokenChanged_(
+      photosResumeToken: GooglePhotosPhotos['photosResumeToken_']) {
+    if (photosResumeToken?.length) {
+      this.$.gridScrollThreshold.clearTriggers();
+    }
+  }
+
   /** Invoked on resize of this element. */
   private onResized_() {
     this.photosPerRow_ = getNumberOfGridItemsPerRow();
   }
 
-  /** Invoked to compute |photosByRow_|. */
-  private computePhotosByRow_(photosBySection:
-                                  GooglePhotosPhotos['photosBySection_']):
-      GooglePhotosPhotosRow[]|null {
-    if (!isNonEmptyArray(photosBySection)) {
-      return null;
-    }
-    return photosBySection.flatMap(section => section.rows);
-  }
-
   /** Invoked to compute |photosBySection_|. */
   private computePhotosBySection_(
       photos: GooglePhotosPhotos['photos_'],
-      photosLoading: GooglePhotosPhotos['photosLoading_'],
       photosPerRow: GooglePhotosPhotos['photosPerRow_']):
       GooglePhotosPhotosSection[]|null {
-    if (photosLoading || !photosPerRow) {
-      return null;
-    }
-    if (!isNonEmptyArray(photos)) {
+    if (!isNonEmptyArray(photos) || !photosPerRow) {
       return null;
     }
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_actions.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_actions.ts
index d74356b5..e8396906 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_actions.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_actions.ts
@@ -14,6 +14,7 @@
  */
 
 export enum WallpaperActionName {
+  APPEND_GOOGLE_PHOTOS_PHOTOS = 'append_google_photos_photos',
   BEGIN_LOAD_GOOGLE_PHOTOS_ALBUM = 'begin_load_google_photos_album',
   BEGIN_LOAD_GOOGLE_PHOTOS_ALBUMS = 'begin_load_google_photos_albums',
   BEGIN_LOAD_GOOGLE_PHOTOS_COUNT = 'begin_load_google_photos_count',
@@ -30,7 +31,6 @@
   SET_GOOGLE_PHOTOS_ALBUM = 'set_google_photos_album',
   SET_GOOGLE_PHOTOS_ALBUMS = 'set_google_photos_albums',
   SET_GOOGLE_PHOTOS_COUNT = 'set_google_photos_count',
-  SET_GOOGLE_PHOTOS_PHOTOS = 'set_google_photos_photos',
   SET_IMAGES_FOR_COLLECTION = 'set_images_for_collection',
   SET_LOCAL_IMAGES = 'set_local_images',
   SET_LOCAL_IMAGE_DATA = 'set_local_image_data',
@@ -40,18 +40,38 @@
 }
 
 export type WallpaperActions =
-    BeginLoadGooglePhotosAlbumAction|BeginLoadGooglePhotosAlbumsAction|
-    BeginLoadGooglePhotosCountAction|BeginLoadGooglePhotosPhotosAction|
-    BeginLoadImagesForCollectionsAction|BeginLoadLocalImagesAction|
-    BeginLoadLocalImageDataAction|BeginUpdateDailyRefreshImageAction|
-    BeginLoadSelectedImageAction|BeginSelectImageAction|EndSelectImageAction|
-    SetCollectionsAction|SetDailyRefreshCollectionIdAction|
-    SetGooglePhotosAlbumAction|SetGooglePhotosAlbumsAction|
-    SetGooglePhotosCountAction|SetGooglePhotosPhotosAction|
+    AppendGooglePhotosPhotosAction|BeginLoadGooglePhotosAlbumAction|
+    BeginLoadGooglePhotosAlbumsAction|BeginLoadGooglePhotosCountAction|
+    BeginLoadGooglePhotosPhotosAction|BeginLoadImagesForCollectionsAction|
+    BeginLoadLocalImagesAction|BeginLoadLocalImageDataAction|
+    BeginUpdateDailyRefreshImageAction|BeginLoadSelectedImageAction|
+    BeginSelectImageAction|EndSelectImageAction|SetCollectionsAction|
+    SetDailyRefreshCollectionIdAction|SetGooglePhotosAlbumAction|
+    SetGooglePhotosAlbumsAction|SetGooglePhotosCountAction|
     SetImagesForCollectionAction|SetLocalImageDataAction|SetLocalImagesAction|
     SetUpdatedDailyRefreshImageAction|SetSelectedImageAction|
     SetFullscreenEnabledAction;
 
+export type AppendGooglePhotosPhotosAction = Action&{
+  name: WallpaperActionName.APPEND_GOOGLE_PHOTOS_PHOTOS;
+  photos: GooglePhotosPhoto[]|null;
+  resumeToken: string|null;
+};
+
+/**
+ * Appends to the list of Google Photos photos. May be called with null on
+ * error.
+ */
+export function appendGooglePhotosPhotosAction(
+    photos: GooglePhotosPhoto[]|null,
+    resumeToken: string|null): AppendGooglePhotosPhotosAction {
+  return {
+    photos,
+    resumeToken,
+    name: WallpaperActionName.APPEND_GOOGLE_PHOTOS_PHOTOS
+  };
+}
+
 export type BeginLoadGooglePhotosAlbumAction = Action&{
   name: WallpaperActionName.BEGIN_LOAD_GOOGLE_PHOTOS_ALBUM;
   albumId: string;
@@ -265,17 +285,6 @@
   return {count, name: WallpaperActionName.SET_GOOGLE_PHOTOS_COUNT};
 }
 
-export type SetGooglePhotosPhotosAction = Action&{
-  name: WallpaperActionName.SET_GOOGLE_PHOTOS_PHOTOS;
-  photos: GooglePhotosPhoto[]|null;
-};
-
-/** Sets the list of Google Photos photos. May be called with null on error. */
-export function setGooglePhotosPhotosAction(photos: GooglePhotosPhoto[]|
-                                            null): SetGooglePhotosPhotosAction {
-  return {photos, name: WallpaperActionName.SET_GOOGLE_PHOTOS_PHOTOS};
-}
-
 export type SetImagesForCollectionAction = Action&{
   name: WallpaperActionName.SET_IMAGES_FOR_COLLECTION;
   collectionId: string;
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_controller.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_controller.ts
index 527c0f6c..ce83d0d0 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_controller.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_controller.ts
@@ -140,28 +140,26 @@
 }
 
 /** Fetches the list of Google Photos photos and saves it to the store. */
-async function fetchGooglePhotosPhotos(
+export async function fetchGooglePhotosPhotos(
     provider: WallpaperProviderInterface,
     store: PersonalizationStore): Promise<void> {
   store.dispatch(action.beginLoadGooglePhotosPhotosAction());
 
   let photos: Array<GooglePhotosPhoto>|null = [];
-  let resumeToken: string|null|undefined = null;
+  let resumeToken = store.data.wallpaper.googlePhotos.resumeTokens.photos;
 
-  // TODO(b/216882690): Support incremental load of photos as the user scrolls
-  // through their library as opposed to loading them all at once.
-  do {
-    const {response} = await provider.fetchGooglePhotosPhotos(
-                           /*itemId=*/ null, /*albumId=*/ null, resumeToken) as
-        {response: FetchGooglePhotosPhotosResponse};
-    if (!Array.isArray(response.photos)) {
-      console.warn('Failed to fetch Google Photos photos');
-      photos = null;
-      break;
-    }
+  const {response} = await provider.fetchGooglePhotosPhotos(
+                         /*itemId=*/ null, /*albumId=*/ null, resumeToken) as
+      {response: FetchGooglePhotosPhotosResponse};
+  if (Array.isArray(response.photos)) {
     photos.push(...response.photos);
-    resumeToken = response.resumeToken;
-  } while (resumeToken);
+    resumeToken = response.resumeToken ?? null;
+  } else {
+    console.warn('Failed to fetch Google Photos photos');
+    photos = null;
+    // NOTE: `resumeToken` is intentionally *not* modified so that the request
+    // which failed can be reattempted.
+  }
 
   // Impose max resolution.
   if (photos !== null) {
@@ -169,7 +167,7 @@
         photo => ({...photo, url: appendMaxResolutionSuffix(photo.url)}));
   }
 
-  store.dispatch(action.setGooglePhotosPhotosAction(photos));
+  store.dispatch(action.appendGooglePhotosPhotosAction(photos, resumeToken));
 }
 
 /** Get list of local images from disk and save it to the store. */
@@ -360,7 +358,7 @@
     store.dispatch(action.beginLoadGooglePhotosAlbumsAction());
     store.dispatch(action.beginLoadGooglePhotosPhotosAction());
     store.dispatch(action.setGooglePhotosAlbumsAction(result));
-    store.dispatch(action.setGooglePhotosPhotosAction(result));
+    store.dispatch(action.appendGooglePhotosPhotosAction(result, null));
     store.endBatchUpdate();
     return;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
index b119f9e..47f04d3 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
@@ -135,7 +135,7 @@
 </style>
 <div class="item" aria-selected$="[[selected]]">
   <template is="dom-if" if="[[isImageVisible_(imageSrc)]]">
-    <img is="cr-auto-img" auto-src="[[imageSrc]]" with-cookies></img>
+    <img is="cr-auto-img" auto-src="[[imageSrc]]" clear-src with-cookies></img>
   </template>
   <template is="dom-if" if="[[isTextVisible_(primaryText, secondaryText)]]">
     <div class="text">
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_reducers.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_reducers.ts
index 9512b333..4bf700df 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_reducers.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_reducers.ts
@@ -192,7 +192,7 @@
           photos: true,
         },
       };
-    case WallpaperActionName.SET_GOOGLE_PHOTOS_PHOTOS:
+    case WallpaperActionName.APPEND_GOOGLE_PHOTOS_PHOTOS:
       assert(state.googlePhotos.photos === true);
       return {
         ...state,
@@ -363,14 +363,40 @@
         count: action.count,
       };
     case WallpaperActionName.BEGIN_LOAD_GOOGLE_PHOTOS_PHOTOS:
-      // The list of photos should be loaded only once.
-      assert(state.photos === undefined);
+      // The list of photos should be loaded only while additional photos exist.
+      assert(state.photos === undefined || state.resumeTokens.photos);
       return state;
-    case WallpaperActionName.SET_GOOGLE_PHOTOS_PHOTOS:
+    case WallpaperActionName.APPEND_GOOGLE_PHOTOS_PHOTOS:
       assert(action.photos !== undefined);
+      // Case: First batch of photos.
+      if (!Array.isArray(state.photos)) {
+        return {
+          ...state,
+          photos: action.photos,
+          resumeTokens: {
+            ...state.resumeTokens,
+            photos: action.resumeToken,
+          },
+        };
+      }
+      // Case: Subsequent batches of photos.
+      if (Array.isArray(action.photos)) {
+        return {
+          ...state,
+          photos: [...state.photos, ...action.photos],
+          resumeTokens: {
+            ...state.resumeTokens,
+            photos: action.resumeToken,
+          },
+        };
+      }
+      // Case: Error.
       return {
         ...state,
-        photos: action.photos,
+        resumeTokens: {
+          ...state.resumeTokens,
+          photos: action.resumeToken,
+        },
       };
     default:
       return state;
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_state.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_state.ts
index 364854c..6cc4009 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_state.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_state.ts
@@ -31,6 +31,7 @@
   albums: GooglePhotosAlbum[]|null|undefined;
   photos: GooglePhotosPhoto[]|null|undefined;
   photosByAlbumId: Record<string, GooglePhotosPhoto[]|null|undefined>;
+  resumeTokens: {photos: string|null};
 }
 
 /**
@@ -115,6 +116,7 @@
       albums: undefined,
       photos: undefined,
       photosByAlbumId: {},
+      resumeTokens: {photos: null},
     },
   };
 }
diff --git a/ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.cc b/ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.cc
index a33e662..aed3181 100644
--- a/ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.cc
+++ b/ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.cc
@@ -73,6 +73,12 @@
   std::move(callback).Run(0);
 }
 
+void FakePersonalizationAppWallpaperProvider::FetchGooglePhotosEnabled(
+    FetchGooglePhotosEnabledCallback callback) {
+  std::move(callback).Run(
+      ash::personalization_app::mojom::GooglePhotosEnablementState::kEnabled);
+}
+
 void FakePersonalizationAppWallpaperProvider::FetchGooglePhotosPhotos(
     const absl::optional<std::string>& item_id,
     const absl::optional<std::string>& album_id,
diff --git a/ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.h b/ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.h
index 7a4942e5..fd1eb00 100644
--- a/ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.h
+++ b/ash/webui/personalization_app/test/fake_personalization_app_wallpaper_provider.h
@@ -50,6 +50,9 @@
 
   void FetchGooglePhotosCount(FetchGooglePhotosCountCallback callback) override;
 
+  void FetchGooglePhotosEnabled(
+      FetchGooglePhotosEnabledCallback callback) override;
+
   void FetchGooglePhotosPhotos(
       const absl::optional<std::string>& item_id,
       const absl::optional<std::string>& album_id,
diff --git a/ash/webui/shimless_rma/DEPS b/ash/webui/shimless_rma/DEPS
index b8d5aff..c72c12ae 100644
--- a/ash/webui/shimless_rma/DEPS
+++ b/ash/webui/shimless_rma/DEPS
@@ -4,8 +4,6 @@
   "+chromeos/dbus/rmad",
   "+chromeos/dbus/update_engine",
   "+chromeos/dbus/util",
-  "+chromeos/login/login_state",
-  "+chromeos/services/network_config",
   "+ui/resources",
   "+ui/web_dialogs",
   "+ui/chromeos/strings",
diff --git a/ash/webui/shimless_rma/backend/BUILD.gn b/ash/webui/shimless_rma/backend/BUILD.gn
index b97da59..dfd5a5dc 100644
--- a/ash/webui/shimless_rma/backend/BUILD.gn
+++ b/ash/webui/shimless_rma/backend/BUILD.gn
@@ -26,7 +26,6 @@
     "//chromeos/dbus/update_engine:proto",
     "//chromeos/dbus/util",
     "//chromeos/network:network",
-    "//chromeos/services/network_config:in_process_instance",
     "//chromeos/services/network_config/public/mojom:mojom",
     "//components/qr_code_generator",
   ]
@@ -50,7 +49,6 @@
     "//chromeos/dbus/rmad",
     "//chromeos/dbus/rmad:rmad_proto",
     "//chromeos/dbus/update_engine:update_engine",
-    "//chromeos/login/login_state:login_state",
     "//chromeos/network:network",
     "//chromeos/network:test_support",
     "//chromeos/services/network_config/public/cpp:test_support",
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.cc b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
index 1369315..0b5bff7 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
@@ -15,7 +15,6 @@
 #include "ash/webui/shimless_rma/mojom/shimless_rma.mojom.h"
 #include "ash/webui/shimless_rma/mojom/shimless_rma_mojom_traits.h"
 #include "base/bind.h"
-#include "base/containers/contains.h"
 #include "base/logging.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/dbus/rmad/rmad.pb.h"
@@ -23,8 +22,6 @@
 #include "chromeos/dbus/util/version_loader.h"
 #include "chromeos/network/network_state.h"
 #include "chromeos/network/network_state_handler.h"
-#include "chromeos/services/network_config/in_process_instance.h"
-#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 #include "components/qr_code_generator/qr_code_generator.h"
 
 using chromeos::network_config::mojom::ConnectionStateType;
@@ -38,8 +35,6 @@
 
 namespace {
 
-namespace network_mojom = ::chromeos::network_config::mojom;
-
 mojom::State RmadStateToMojo(rmad::RmadState::StateCase rmadState) {
   return mojo::EnumTraits<ash::shimless_rma::mojom::State,
                           rmad::RmadState::StateCase>::ToMojom(rmadState);
@@ -84,23 +79,12 @@
   qr_code->data.assign(qr_data->data.begin(), qr_data->data.end());
   return qr_code;
 }
-
-chromeos::network_config::mojom::NetworkFilterPtr GetNetworkFilter() {
-  return network_mojom::NetworkFilter::New(
-      network_mojom::FilterType::kConfigured, network_mojom::NetworkType::kWiFi,
-      network_mojom::kNoLimit);
-}
-
 }  // namespace
 
 ShimlessRmaService::ShimlessRmaService(
     std::unique_ptr<ShimlessRmaDelegate> shimless_rma_delegate)
     : shimless_rma_delegate_(std::move(shimless_rma_delegate)) {
   chromeos::RmadClient::Get()->AddObserver(this);
-
-  network_config::BindToInProcessInstance(
-      remote_cros_network_config_.BindNewPipeAndPassReceiver());
-
   version_updater_.SetOsUpdateStatusCallback(
       base::BindRepeating(&ShimlessRmaService::OnOsUpdateStatusCallback,
                           weak_ptr_factory_.GetWeakPtr()));
@@ -183,59 +167,6 @@
   }
 }
 
-void ShimlessRmaService::TrackConfiguredNetworks() {
-  // Only populate `existing_saved_network_guids_` once to avoid treating a new
-  // network like an existing network. TrackConfiguredNetworks() can potentially
-  // be called twice if the user navigates back to the networking page.
-  if (!existing_saved_network_guids_.empty()) {
-    LOG(WARNING) << "Already captured configured networks.";
-    return;
-  }
-
-  remote_cros_network_config_->GetNetworkStateList(
-      GetNetworkFilter(),
-      base::BindOnce(&ShimlessRmaService::OnTrackConfiguredNetworks,
-                     base::Unretained(this)));
-}
-
-void ShimlessRmaService::OnTrackConfiguredNetworks(
-    std::vector<network_mojom::NetworkStatePropertiesPtr> networks) {
-  for (auto& network : networks) {
-    existing_saved_network_guids_.push_back(std::move(network->guid));
-  }
-}
-
-void ShimlessRmaService::ForgetNewNetworkConnections() {
-  remote_cros_network_config_->GetNetworkStateList(
-      GetNetworkFilter(),
-      base::BindOnce(&ShimlessRmaService::OnForgetNewNetworkConnections,
-                     base::Unretained(this)));
-}
-
-void ShimlessRmaService::OnForgetNewNetworkConnections(
-    std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
-        networks) {
-  for (auto& network : networks) {
-    const std::string& guid = network->guid;
-    const bool found_network_guid =
-        base::Contains(existing_saved_network_guids_, guid);
-
-    // If `network` did not exist before RMA, attempt to forget it.
-    if (!found_network_guid) {
-      remote_cros_network_config_->ForgetNetwork(
-          guid, base::BindOnce(&ShimlessRmaService::OnForgetNetwork,
-                               base::Unretained(this), guid));
-    }
-  }
-}
-
-void ShimlessRmaService::OnForgetNetwork(const std::string& guid,
-                                         bool success) {
-  if (!success) {
-    LOG(ERROR) << "Failed to forget saved network configuration GUID: " << guid;
-  }
-}
-
 void ShimlessRmaService::NetworkSelectionComplete(
     NetworkSelectionCompleteCallback callback) {
   if (state_proto_.state_case() != rmad::RmadState::kWelcome ||
@@ -918,9 +849,6 @@
                             rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
     return;
   }
-
-  ForgetNewNetworkConnections();
-
   state_proto_.mutable_repair_complete()->set_shutdown(
       rmad::RepairCompleteState::RMAD_REPAIR_COMPLETE_REBOOT);
   TransitionNextStateGeneric(std::move(callback));
@@ -935,9 +863,6 @@
                             rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
     return;
   }
-
-  ForgetNewNetworkConnections();
-
   state_proto_.mutable_repair_complete()->set_shutdown(
       rmad::RepairCompleteState::RMAD_REPAIR_COMPLETE_SHUTDOWN);
   TransitionNextStateGeneric(std::move(callback));
@@ -953,9 +878,6 @@
                             rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
     return;
   }
-
-  ForgetNewNetworkConnections();
-
   state_proto_.mutable_repair_complete()->set_shutdown(
       rmad::RepairCompleteState::RMAD_REPAIR_COMPLETE_BATTERY_CUTOFF);
   TransitionNextStateGeneric(std::move(callback));
@@ -1192,7 +1114,6 @@
     return;
   }
 
-  ForgetNewNetworkConnections();
   // Send status before shutting down or restarting Chrome session.
   std::move(callback).Run(rmad::RMAD_ERROR_OK);
 
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.h b/ash/webui/shimless_rma/backend/shimless_rma_service.h
index aa340a5..f909a85 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.h
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.h
@@ -7,14 +7,12 @@
 
 #include <memory>
 #include <string>
-#include <vector>
 
 #include "ash/webui/shimless_rma/backend/version_updater.h"
 #include "ash/webui/shimless_rma/mojom/shimless_rma.mojom.h"
 #include "chromeos/dbus/rmad/rmad.pb.h"
 #include "chromeos/dbus/rmad/rmad_client.h"
 #include "chromeos/dbus/update_engine/update_engine.pb.h"
-#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
@@ -43,7 +41,6 @@
 
   void BeginFinalization(BeginFinalizationCallback callback) override;
 
-  void TrackConfiguredNetworks();
   void NetworkSelectionComplete(
       NetworkSelectionCompleteCallback callback) override;
 
@@ -210,23 +207,6 @@
   void OsUpdateOrNextRmadStateCallback(TransitionStateCallback callback,
                                        const std::string& version);
 
-  // Saves existing configured networks to `existing_saved_networks_`.
-  void OnTrackConfiguredNetworks(
-      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
-          networks);
-
-  // Fetches the list of configured networks on RMA completion/exit.
-  void ForgetNewNetworkConnections();
-
-  // Compares the saved and current list of configured networks and attempts to
-  // drop any new network configurations.
-  void OnForgetNewNetworkConnections(
-      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
-          networks);
-
-  // Confirms if the network was dropped.
-  void OnForgetNetwork(const std::string& guid, bool success);
-
   rmad::RmadState state_proto_;
   bool can_abort_ = false;
   bool can_go_back_ = false;
@@ -259,14 +239,6 @@
   mojo::Remote<mojom::UpdateRoFirmwareObserver> update_ro_firmware_observer_;
   mojo::Receiver<mojom::ShimlessRmaService> receiver_{this};
 
-  // Remote for sending requests to the CrosNetworkConfig service.
-  mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>
-      remote_cros_network_config_;
-
-  // The GUIDs of the saved network configurations prior to starting RMA. Needed
-  // to track network connections added during RMA.
-  std::vector<std::string> existing_saved_network_guids_;
-
   VersionUpdater version_updater_;
   base::OnceCallback<void(const std::string& version)> check_os_callback_;
 
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
index 6a17cd95..4b31679e 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
@@ -12,7 +12,6 @@
 
 #include "ash/webui/shimless_rma/backend/shimless_rma_delegate.h"
 #include "ash/webui/shimless_rma/mojom/shimless_rma.mojom.h"
-#include "base/callback_helpers.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
@@ -20,12 +19,8 @@
 #include "chromeos/dbus/rmad/fake_rmad_client.h"
 #include "chromeos/dbus/rmad/rmad_client.h"
 #include "chromeos/dbus/update_engine/update_engine.pb.h"
-#include "chromeos/login/login_state/login_state.h"
-#include "chromeos/network/managed_network_configuration_handler.h"
 #include "chromeos/network/network_configuration_handler.h"
-#include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test_helper.h"
-#include "chromeos/network/network_type_pattern.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 "testing/gtest/include/gtest/gtest.h"
@@ -92,7 +87,11 @@
 
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
-    SetupFakeNetwork();
+    cros_network_config_test_helper_ =
+        std::make_unique<network_config::CrosNetworkConfigTestHelper>(false);
+    cros_network_config_test_helper().Initialize(nullptr);
+    NetworkHandler::Initialize();
+
     FakeRmadClientForTest::Initialize();
     rmad_client_ = chromeos::RmadClient::Get();
     // ShimlessRmaService has to be created after RmadClient or there will be a
@@ -112,32 +111,9 @@
     chromeos::RmadClient::Shutdown();
     NetworkHandler::Shutdown();
     cros_network_config_test_helper_.reset();
-    chromeos::LoginState::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
-  void SetupFakeNetwork() {
-    chromeos::LoginState::Initialize();
-
-    cros_network_config_test_helper_ =
-        std::make_unique<network_config::CrosNetworkConfigTestHelper>(false);
-    network_configuration_handler_ =
-        NetworkConfigurationHandler::InitializeForTest(
-            network_state_helper().network_state_handler(),
-            cros_network_config_test_helper().network_device_handler());
-    managed_network_configuration_handler_ =
-        ManagedNetworkConfigurationHandler::InitializeForTesting(
-            /*network_state_handler=*/nullptr,
-            /*network_profile_handler=*/nullptr,
-            /*network_device_handler=*/nullptr,
-            network_configuration_handler_.get(),
-            /*ui_proxy_config_service=*/nullptr);
-    cros_network_config_test_helper().Initialize(
-        managed_network_configuration_handler_.get());
-
-    NetworkHandler::Initialize();
-  }
-
   rmad::RmadState* CreateState(rmad::RmadState::StateCase state_case) {
     rmad::RmadState* state = new rmad::RmadState();
     switch (state_case) {
@@ -239,13 +215,6 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  void GetCurrentlyConfiguredWifiNetworks(
-      NetworkStateHandler::NetworkStateList* list) {
-    network_state_handler()->GetNetworkListByType(
-        NetworkTypePattern::WiFi(), /*configured_only=*/true,
-        /*visible_only=*/true, /*no_limit=*/0, list);
-  }
-
  protected:
   network_config::CrosNetworkConfigTestHelper&
   cros_network_config_test_helper() {
@@ -256,19 +225,12 @@
     return cros_network_config_test_helper_->network_state_helper();
   }
 
-  chromeos::NetworkStateHandler* network_state_handler() {
-    return network_state_helper().network_state_handler();
-  }
-
   std::unique_ptr<ShimlessRmaService> shimless_rma_provider_;
   chromeos::RmadClient* rmad_client_ = nullptr;  // Unowned convenience pointer.
 
  private:
   std::unique_ptr<network_config::CrosNetworkConfigTestHelper>
       cros_network_config_test_helper_;
-  std::unique_ptr<ManagedNetworkConfigurationHandler>
-      managed_network_configuration_handler_;
-  std::unique_ptr<NetworkConfigurationHandler> network_configuration_handler_;
   base::test::TaskEnvironment task_environment_;
 };
 
@@ -457,68 +419,6 @@
   run_loop.Run();
 }
 
-TEST_F(ShimlessRmaServiceTest, ConfiguredNetworksKeepExistingNetworks) {
-  const std::vector<rmad::GetStateReply> fake_states = {
-      CreateStateReply(rmad::RmadState::kRepairComplete, rmad::RMAD_ERROR_OK)};
-  fake_rmad_client_()->SetFakeStateReplies(std::move(fake_states));
-
-  base::RunLoop run_loop;
-  shimless_rma_provider_->GetCurrentState(base::DoNothing());
-  run_loop.RunUntilIdle();
-
-  // Simulate 2 saved networks existing before the start of RMA.
-  SetupWiFiNetwork("WiFi 1");
-  SetupWiFiNetwork("WiFi 2");
-  NetworkStateHandler::NetworkStateList configured_networks;
-  GetCurrentlyConfiguredWifiNetworks(&configured_networks);
-  EXPECT_EQ(2u, configured_networks.size());
-
-  // Snapshot the saved networks before connecting to a network during RMA.
-  shimless_rma_provider_->TrackConfiguredNetworks();
-  run_loop.RunUntilIdle();
-
-  // End RMA and expect no networks to be removed from the saved networks.
-  shimless_rma_provider_->EndRmaAndReboot(base::DoNothing());
-  run_loop.RunUntilIdle();
-
-  GetCurrentlyConfiguredWifiNetworks(&configured_networks);
-  EXPECT_EQ(2u, configured_networks.size());
-}
-
-TEST_F(ShimlessRmaServiceTest, ConfiguredNetworksDropNewNetworks) {
-  const std::vector<rmad::GetStateReply> fake_states = {
-      CreateStateReply(rmad::RmadState::kRepairComplete, rmad::RMAD_ERROR_OK)};
-  fake_rmad_client_()->SetFakeStateReplies(std::move(fake_states));
-
-  base::RunLoop run_loop;
-  shimless_rma_provider_->GetCurrentState(base::DoNothing());
-  run_loop.RunUntilIdle();
-
-  // Simulate a saved network existing before the start of RMA.
-  const std::string saved_network_guid = "WiFi 1";
-  SetupWiFiNetwork(saved_network_guid);
-  NetworkStateHandler::NetworkStateList configured_networks;
-  GetCurrentlyConfiguredWifiNetworks(&configured_networks);
-  EXPECT_EQ(1u, configured_networks.size());
-
-  // Snapshot the saved networks before connecting to a network during RMA.
-  shimless_rma_provider_->TrackConfiguredNetworks();
-  run_loop.RunUntilIdle();
-
-  // Simulate connecting to a new network on the RMA `kConfigureNetwork` page.
-  SetupWiFiNetwork("WiFi 2");
-  GetCurrentlyConfiguredWifiNetworks(&configured_networks);
-  EXPECT_EQ(2u, configured_networks.size());
-
-  // End RMA and expect the new network to be removed from the saved networks.
-  shimless_rma_provider_->EndRmaAndReboot(base::DoNothing());
-  run_loop.RunUntilIdle();
-
-  GetCurrentlyConfiguredWifiNetworks(&configured_networks);
-  EXPECT_EQ(1u, configured_networks.size());
-  EXPECT_EQ(saved_network_guid, configured_networks[0]->guid());
-}
-
 // TODO(gavindodd): Add tests of transitions back from rmad states through
 // the mojom chrome update and network selection states when implemented.
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 3a64f694..c30006fc 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2837,6 +2837,10 @@
     deps += [ "//testing/android/native_test:native_test_native_code" ]
     shard_timeout = 600
   }
+  if (is_fuchsia) {
+    # TODO(crbug.com/1306335): Switch to CFv2 once infrastructure supports it.
+    use_cfv2 = false
+  }
 
   if (!is_official_build) {
     # The extra data tables required by stack traces are turned off for official
diff --git a/base/allocator/partition_allocator/partition_alloc_perftest.cc b/base/allocator/partition_allocator/partition_alloc_perftest.cc
index 5a15b3b60..840d08a 100644
--- a/base/allocator/partition_allocator/partition_alloc_perftest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_perftest.cc
@@ -22,8 +22,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/perf/perf_result_reporter.h"
 
-#if BUILDFLAG(IS_ANDROID) || defined(ARCH_CPU_32_BITS) || \
-    (BUILDFLAG(IS_FUCHSIA) && defined(ARCH_CPU_ARM64))
+#if BUILDFLAG(IS_ANDROID) || defined(ARCH_CPU_32_BITS) || BUILDFLAG(IS_FUCHSIA)
 // Some tests allocate many GB of memory, which can cause issues on Android and
 // address-space exhaustion for any 32-bit process.
 #define MEMORY_CONSTRAINED
diff --git a/build/config/OWNERS b/build/config/OWNERS
index 8766231..580fa2e 100644
--- a/build/config/OWNERS
+++ b/build/config/OWNERS
@@ -1,5 +1,3 @@
-scottmg@chromium.org
-
 per-file ozone.gni=file://ui/ozone/OWNERS
 per-file ozone_extra.gni=file://ui/ozone/OWNERS
 per-file rust.gni=file://build/rust/OWNERS
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index ca9a807..219fb42 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-7.20220310.2.2
+7.20220315.2.1
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 253e464..219fb42 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-7.20220315.1.1
+7.20220315.2.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index ca9a807..253e464 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-7.20220310.2.2
+7.20220315.1.1
diff --git a/build/toolchain/OWNERS b/build/toolchain/OWNERS
index e1b5385..3575950 100644
--- a/build/toolchain/OWNERS
+++ b/build/toolchain/OWNERS
@@ -1,4 +1,2 @@
-scottmg@chromium.org
-
 # Code Coverage.
 per-file *code_coverage*=liaoyuke@chromium.org
diff --git a/build/toolchain/cros/BUILD.gn b/build/toolchain/cros/BUILD.gn
index 2dfe045..6d3d94f 100644
--- a/build/toolchain/cros/BUILD.gn
+++ b/build/toolchain/cros/BUILD.gn
@@ -211,6 +211,20 @@
 
     toolchain_args = {
       forward_variables_from(lacros_args, "*")
+
+      # TODO(crbug.com/1298821) Change to a better way to set gn args.
+      # The following gn args are present in ash config like
+      # //build/args/chromeos/atlas.gni but not in
+      # //build/args/chromeos/amd64-generic-crostoolchain.gni.
+      # So we need to reset them to the default value where Lacros needs.
+      # Starts from here.
+      ozone_auto_platforms = true
+      ozone_platform = ""
+      ozone_platform_gbm = -1
+      ozone_platform_headless = false
+
+      # Ends here.
+
       cros_v8_snapshot_sysroot = lacros_v8_snapshot_sysroot
       current_os = "chromeos"
       target_os = "chromeos"
diff --git a/build/toolchain/fuchsia/OWNERS b/build/toolchain/fuchsia/OWNERS
index 3f809e82..e7034ea 100644
--- a/build/toolchain/fuchsia/OWNERS
+++ b/build/toolchain/fuchsia/OWNERS
@@ -1 +1 @@
-scottmg@chromium.org
+file://build/fuchsia/OWNERS
diff --git a/chrome/VERSION b/chrome/VERSION
index 6a6053a0..b6b950a 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=101
 MINOR=0
-BUILD=4946
+BUILD=4947
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index a8d48a9..30ca73e55 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -246,6 +246,7 @@
     "//components/browser_ui/settings/android:java_resources",
     "//components/browser_ui/strings/android:browser_ui_strings_grd",
     "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/theme/android:java_resources",
     "//components/browser_ui/widget/android:java_resources",
     "//components/find_in_page/android:java_resources",
     "//components/javascript_dialogs/android:java_resources",
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrCoreInstallUtils.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrCoreInstallUtils.java
index 2194f66..f285a292a 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrCoreInstallUtils.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrCoreInstallUtils.java
@@ -25,6 +25,7 @@
 import org.chromium.components.messages.MessageDispatcherProvider;
 import org.chromium.components.messages.MessageIdentifier;
 import org.chromium.components.messages.MessageScopeType;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -223,6 +224,7 @@
                                             new Intent(Intent.ACTION_VIEW,
                                                     Uri.parse(VR_CORE_MARKET_URI)),
                                             VR_SERVICES_UPDATE_RESULT);
+                                    return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
                                 })
                         .with(MessageBannerProperties.ON_DISMISSED, this::onMessageDismissed)
                         .build();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
index 86e8a8b4..a2c1514 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
@@ -51,6 +51,7 @@
 import org.chromium.components.messages.MessageDispatcherProvider;
 import org.chromium.components.messages.MessageIdentifier;
 import org.chromium.components.messages.MessageScopeType;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.content_public.browser.LoadCommittedDetails;
@@ -555,20 +556,23 @@
         // Save url for #onMessageDismissed. mDistillerUrl may have been changed and became
         // different from the url when message is enqueued.
         GURL url = mDistillerUrl;
-        mMessageModel =
-                new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
-                        .with(MessageBannerProperties.MESSAGE_IDENTIFIER,
-                                MessageIdentifier.READER_MODE)
-                        .with(MessageBannerProperties.TITLE,
-                                resources.getString(R.string.reader_mode_message_title))
-                        .with(MessageBannerProperties.ICON_RESOURCE_ID,
-                                R.drawable.infobar_mobile_friendly)
-                        .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT,
-                                resources.getString(R.string.reader_mode_message_button))
-                        .with(MessageBannerProperties.ON_PRIMARY_ACTION, this::activateReaderMode)
-                        .with(MessageBannerProperties.ON_DISMISSED,
-                                (reason) -> onMessageDismissed(url, reason))
-                        .build();
+        mMessageModel = new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
+                                .with(MessageBannerProperties.MESSAGE_IDENTIFIER,
+                                        MessageIdentifier.READER_MODE)
+                                .with(MessageBannerProperties.TITLE,
+                                        resources.getString(R.string.reader_mode_message_title))
+                                .with(MessageBannerProperties.ICON_RESOURCE_ID,
+                                        R.drawable.infobar_mobile_friendly)
+                                .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT,
+                                        resources.getString(R.string.reader_mode_message_button))
+                                .with(MessageBannerProperties.ON_PRIMARY_ACTION,
+                                        () -> {
+                                            activateReaderMode();
+                                            return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
+                                        })
+                                .with(MessageBannerProperties.ON_DISMISSED,
+                                        (reason) -> onMessageDismissed(url, reason))
+                                .build();
         messageDispatcher.enqueueMessage(
                 mMessageModel, mTab.getWebContents(), MessageScopeType.NAVIGATION, false);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
index 26e2cee8..fdf9073 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
@@ -33,6 +33,7 @@
 import org.chromium.components.messages.MessageBannerProperties;
 import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.components.offline_items_collection.ContentId;
 import org.chromium.components.offline_items_collection.LegacyHelpers;
 import org.chromium.components.offline_items_collection.OfflineContentProvider;
@@ -887,7 +888,8 @@
         mNotificationIds.remove(contentId);
     }
 
-    private void onPrimaryAction(ContentId itemId, final OfflineItemSchedule schedule) {
+    private @PrimaryActionClickBehavior int onPrimaryAction(
+            ContentId itemId, final OfflineItemSchedule schedule) {
         OfflineItem offlineItem = mTrackedItems.remove(itemId);
         removeNotification(itemId);
         if (itemId != null && schedule != null) {
@@ -905,6 +907,7 @@
                     getOTRProfileIDForTrackedItems(), DownloadOpenSource.DOWNLOAD_PROGRESS_MESSAGE);
             recordLinkClicked(false /*openItem*/);
         }
+        return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
     }
 
     private OTRProfileID getOTRProfileIDForTrackedItems() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegate.java
index ecdf340e..40edaea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegate.java
@@ -18,6 +18,7 @@
 import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.messages.MessageIdentifier;
 import org.chromium.components.messages.MessageScopeType;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.components.webapps.WebappsIconUtils;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -91,9 +92,10 @@
     /**
      * Open the Instant App when the message primary button is clicked.
      */
-    private void handlePrimaryAction() {
+    private @PrimaryActionClickBehavior int handlePrimaryAction() {
         InstantAppsMessageDelegateJni.get().onPrimaryAction(mData.isInstantAppDefault());
         InstantAppsHandler.getInstance().launchFromBanner(mData);
+        return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java b/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
index 2055f9d..16ff3112 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
@@ -50,6 +50,7 @@
 import org.chromium.components.messages.MessageBannerProperties;
 import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 
@@ -279,7 +280,10 @@
                             .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT,
                                     resources.getString(R.string.chrome_survey_message_button))
                             .with(MessageBannerProperties.ON_PRIMARY_ACTION,
-                                    () -> showSurvey(siteId))
+                                    () -> {
+                                        showSurvey(siteId);
+                                        return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
+                                    })
                             .with(MessageBannerProperties.ON_DISMISSED,
                                     this::recordSurveyPromptMetrics)
                             .build();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncErrorMessage.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncErrorMessage.java
index 201d60fe..97109b3a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncErrorMessage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncErrorMessage.java
@@ -26,6 +26,7 @@
 import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.messages.MessageDispatcherProvider;
 import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -117,9 +118,10 @@
         }
     }
 
-    private void onAccepted() {
+    private @PrimaryActionClickBehavior int onAccepted() {
         SyncErrorPromptUtils.onUserAccepted(mType);
         recordHistogram(SyncErrorPromptAction.BUTTON_CLICKED);
+        return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
     }
 
     private void onDismissed(@DismissReason int reason) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillUpstreamTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillUpstreamTest.java
index 3658826c..da774ac 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillUpstreamTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillUpstreamTest.java
@@ -122,7 +122,7 @@
                         .get(0);
         PropertyModel model = MessagesTestHelper.getCurrentMessage(handler);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            model.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
+            model.get(MessageBannerProperties.ON_PRIMARY_ACTION).get();
             dispatcher.dismissMessage(model, DismissReason.PRIMARY_ACTION);
         });
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
index 5007afb..ffd7eda 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
@@ -96,6 +96,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Random;
@@ -312,6 +313,8 @@
         // Choose a fixed, "random" port to create stable screenshots.
         mTestServerRule.setServerPort(424242);
         mTestServerRule.setServerUsesHttps(true);
+
+        PageInfoAdPersonalizationController.setTopicsForTesting(Arrays.asList("Testing topic"));
     }
 
     @After
@@ -330,6 +333,7 @@
         clearPermissions();
         HistoryContentManager.setProviderForTests(null);
         PageInfoHistoryController.setProviderForTests(null);
+        PageInfoAdPersonalizationController.setTopicsForTesting(null);
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerIntegrationTest.java
index a6188e2..ee2d759e7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerIntegrationTest.java
@@ -151,8 +151,8 @@
         Assert.assertNotNull("Message should not be null.", message);
 
         // Simulate the message primary button click.
-        Runnable primaryActionCallback = message.get(MessageBannerProperties.ON_PRIMARY_ACTION);
-        TestThreadUtils.runOnUiThreadBlocking(primaryActionCallback);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { message.get(MessageBannerProperties.ON_PRIMARY_ACTION).get(); });
         // Simulate message dismissal on primary button click.
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> mMessageDispatcher.dismissMessage(message, DismissReason.PRIMARY_ACTION));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
index 9fed331..75b7f3f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
@@ -55,6 +55,7 @@
 import org.chromium.components.sync.ModelType;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
+import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -605,8 +606,7 @@
     public void testAdvancedSyncFlowTopView() throws Exception {
         mSyncTestRule.setUpAccountAndEnableSyncForTesting();
         final ManageSyncSettings fragment = startManageSyncPreferences();
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(fragment.getView(), "advanced_sync_flow_top_view");
+        render(fragment, "advanced_sync_flow_top_view");
     }
 
     @Test
@@ -622,8 +622,7 @@
             recyclerView.setVerticalScrollBarEnabled(false);
             recyclerView.scrollToPosition(recyclerView.getAdapter().getItemCount() - 1);
         });
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(fragment.getView(), "advanced_sync_flow_bottom_view");
+        render(fragment, "advanced_sync_flow_bottom_view");
     }
 
     @Test
@@ -632,8 +631,7 @@
     public void testAdvancedSyncFlowFromSyncConsentTopView() throws Exception {
         mSyncTestRule.setUpAccountAndEnableSyncForTesting();
         final ManageSyncSettings fragment = startManageSyncPreferencesFromSyncConsentFlow();
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(fragment.getView(), "advanced_sync_flow_top_view_from_sync_consent");
+        render(fragment, "advanced_sync_flow_top_view_from_sync_consent");
     }
 
     @Test
@@ -649,9 +647,7 @@
             recyclerView.setVerticalScrollBarEnabled(false);
             recyclerView.scrollToPosition(recyclerView.getAdapter().getItemCount() - 1);
         });
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(
-                fragment.getView(), "advanced_sync_flow_bottom_view_from_sync_consent");
+        render(fragment, "advanced_sync_flow_bottom_view_from_sync_consent");
     }
 
     @Test
@@ -661,8 +657,7 @@
     public void testAdvancedSyncFlowTopViewForChildUser() throws Exception {
         mSyncTestRule.setUpChildAccountAndEnableSyncForTesting();
         final ManageSyncSettings fragment = startManageSyncPreferences();
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(fragment.getView(), "advanced_sync_flow_top_view_child");
+        render(fragment, "advanced_sync_flow_top_view_child");
     }
 
     @Test
@@ -679,8 +674,7 @@
             recyclerView.setVerticalScrollBarEnabled(false);
             recyclerView.scrollToPosition(recyclerView.getAdapter().getItemCount() - 1);
         });
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(fragment.getView(), "advanced_sync_flow_bottom_view_child");
+        render(fragment, "advanced_sync_flow_bottom_view_child");
     }
 
     @Test
@@ -690,9 +684,7 @@
     public void testAdvancedSyncFlowFromSyncConsentTopViewForChildUser() throws Exception {
         mSyncTestRule.setUpChildAccountAndEnableSyncForTesting();
         final ManageSyncSettings fragment = startManageSyncPreferencesFromSyncConsentFlow();
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(
-                fragment.getView(), "advanced_sync_flow_top_view_from_sync_consent_child");
+        render(fragment, "advanced_sync_flow_top_view_from_sync_consent_child");
     }
 
     @Test
@@ -709,9 +701,7 @@
             recyclerView.setVerticalScrollBarEnabled(false);
             recyclerView.scrollToPosition(recyclerView.getAdapter().getItemCount() - 1);
         });
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        mRenderTestRule.render(
-                fragment.getView(), "advanced_sync_flow_bottom_view_from_sync_consent_child");
+        render(fragment, "advanced_sync_flow_bottom_view_from_sync_consent_child");
     }
 
     private ManageSyncSettings startManageSyncPreferences() {
@@ -829,4 +819,12 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> textView.setError(null));
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
+
+    private void render(ManageSyncSettings fragment, String skiaGoldId) throws IOException {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // Sanitize the view, in particular to ensure the presence of scroll bars do not cause
+        // image diffs.
+        ChromeRenderTestRule.sanitize(fragment.getView());
+        mRenderTestRule.render(fragment.getView(), skiaGoldId);
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegateTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegateTest.java
index d1bab053..cbb64ab 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegateTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegateTest.java
@@ -124,7 +124,7 @@
         mDelegate.showMessage();
         PropertyModel message = mDelegate.getMessageForTesting();
 
-        message.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
+        message.get(MessageBannerProperties.ON_PRIMARY_ACTION).get();
         Mockito.verify(mNativeMock).onPrimaryAction(mData.isInstantAppDefault());
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
index 3a36798..b6a99a1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
@@ -68,6 +68,7 @@
 import org.chromium.components.messages.MessageBannerProperties;
 import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -573,7 +574,9 @@
         Assert.assertNotNull("Message dismissal action is null.",
                 messageModel.get(MessageBannerProperties.ON_DISMISSED));
 
-        messageModel.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
+        Assert.assertEquals("Message ON_PRIMARY_ACTION should return DISMISS_IMMEDIATELY.",
+                PrimaryActionClickBehavior.DISMISS_IMMEDIATELY,
+                messageModel.get(MessageBannerProperties.ON_PRIMARY_ACTION).get().intValue());
         Assert.assertEquals("showSurvey should be called.", 1,
                 mTestSurveyController.showSurveyIfAvailableCallback.getCallCount());
     }
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 95b8cf3b..bf0af51 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-101.0.4940.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-101.0.4943.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index acd34d23..60dab288 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -10983,11 +10983,6 @@
          Your organization doesn't allow you to share this content. If you need help, contact your administrator.
       </message>
 
-      <!--Printer registration message for local discovery and Print Preview-->
-      <message name="IDS_CLOUD_PRINT_REGISTER_PRINTER_INFORMATION" desc="Information about registering printers with Google Cloud Print shown to the user before they confirm registration.">
-        Documents are <ph name="BEGIN_LINK_HELP">&lt;a is="action-link" href="https://support.google.com/cloudprint/answer/2541843" target="_blank"&gt;</ph>sent to Google<ph name="END_LINK_HELP">&lt;/a&gt;</ph> to prepare them for printing. View, edit and manage your printers and printer history on the <ph name="BEGIN_LINK_DASHBOARD">&lt;a is="action-link" href="https://www.google.com/cloudprint#jobs" target="_blank"&gt;</ph>Google Cloud Print dashboard<ph name="END_LINK_DASHBOARD">&lt;/a&gt;</ph>.
-      </message>
-
       <!--Tab alert tooltip strings-->
       <message name="IDS_TOOLTIP_TAB_ALERT_STATE_MEDIA_RECORDING" desc="Extra tool tip text, when the tab is recording media.">
         This tab is using your camera or microphone.
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 41bb31c..3d77b83 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5012,10 +5012,10 @@
       "policy/browser_signin_policy_handler.h",
       "policy/cloud/user_cloud_policy_manager_builder.cc",
       "policy/cloud/user_cloud_policy_manager_builder.h",
-      "policy/cloud/user_policy_signin_service_base.cc",
-      "policy/cloud/user_policy_signin_service_base.h",
       "policy/cloud/user_policy_signin_service_factory.cc",
       "policy/cloud/user_policy_signin_service_factory.h",
+      "policy/cloud/user_policy_signin_service_util.cc",
+      "policy/cloud/user_policy_signin_service_util.h",
       "profiles/gaia_info_update_service.cc",
       "profiles/gaia_info_update_service.h",
       "profiles/gaia_info_update_service_factory.cc",
@@ -5143,6 +5143,8 @@
       "lacros/account_manager/profile_account_manager.h",
       "lacros/account_manager/profile_account_manager_factory.cc",
       "lacros/account_manager/profile_account_manager_factory.h",
+      "lacros/account_manager/web_signin_helper_lacros.cc",
+      "lacros/account_manager/web_signin_helper_lacros.h",
       "lacros/app_mode/kiosk_session_service_lacros.cc",
       "lacros/app_mode/kiosk_session_service_lacros.h",
       "lacros/arc/arc_icon_cache.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 9ee7487..c1b5de5 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -7255,8 +7255,9 @@
      // multiple related features.
      SINGLE_VALUE_TYPE_AND_VALUE(
          switches::kEnableFeatures,
-         "PrivacySandboxSettings3:disable-dialog-for-"
-         "testing/true,EnableFetchingAccountCapabilities,InterestGroupStorage,"
+         "PrivacySandboxSettings3:"
+         "disable-dialog-for-testing/true/show-sample-data/true,"
+         "EnableFetchingAccountCapabilities,InterestGroupStorage,"
          "AdInterestGroupAPI,Fledge,FencedFrames")},
 
     {"privacy-sandbox-v3-android", flag_descriptions::kPrivacySandboxV3Name,
@@ -7265,8 +7266,9 @@
      // multiple related features when they are available.
      SINGLE_VALUE_TYPE_AND_VALUE(
          switches::kEnableFeatures,
-         "PrivacySandboxSettings3:disable-dialog-for-testing/"
-         "true,EnableFetchingAccountCapabilities")},
+         "PrivacySandboxSettings3:"
+         "disable-dialog-for-testing/true/show-sample-data/true,"
+         "EnableFetchingAccountCapabilities")},
 
     {"animated-image-resume", flag_descriptions::kAnimatedImageResumeName,
      flag_descriptions::kAnimatedImageResumeDescription, kOsAll,
diff --git a/chrome/browser/ash/crosapi/url_handler_ash.cc b/chrome/browser/ash/crosapi/url_handler_ash.cc
index 3efc8463..f686ee6 100644
--- a/chrome/browser/ash/crosapi/url_handler_ash.cc
+++ b/chrome/browser/ash/crosapi/url_handler_ash.cc
@@ -86,6 +86,18 @@
   GURL target_url = crosapi::gurl_os_handler_utils::SanitizeAshURL(url);
   GURL short_target_url = crosapi::gurl_os_handler_utils::SanitizeAshURL(
       url, /*include_path=*/false);
+  // Change os://settings/* into chrome://os-settings/* which will be the long
+  // term home for our OS-settings.
+  if (short_target_url == GURL(chrome::kOsUISettingsURL)) {
+    // This converts the os (GURL lib unusable) address into a chrome
+    // (GURL lib usable) address.
+    target_url =
+        crosapi::gurl_os_handler_utils::GetChromeUrlFromSystemUrl(target_url);
+    GURL::Replacements replacements;
+    replacements.SetHostStr(chrome::kChromeUIOSSettingsHost);
+    target_url = target_url.ReplaceComponents(replacements);
+    short_target_url = GURL(chrome::kChromeUIOSSettingsURL);
+  }
   // Settings will be handled.
   if (short_target_url == GURL(chrome::kChromeUIOSSettingsURL)) {
     chrome::SettingsWindowManager* settings_window_manager =
diff --git a/chrome/browser/ash/login/app_mode/kiosk_browsertest.cc b/chrome/browser/ash/login/app_mode/kiosk_browsertest.cc
index 0669a2c..ae4ca5f 100644
--- a/chrome/browser/ash/login/app_mode/kiosk_browsertest.cc
+++ b/chrome/browser/ash/login/app_mode/kiosk_browsertest.cc
@@ -15,7 +15,6 @@
 #include "ash/public/cpp/login_screen_test_api.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/shelf_test_api.h"
-#include "ash/public/cpp/wallpaper/wallpaper_controller_observer.h"
 #include "base/barrier_closure.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
@@ -39,6 +38,7 @@
 #include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/ash/file_manager/fake_disk_mount_manager.h"
 #include "chrome/browser/ash/login/app_mode/kiosk_launch_controller.h"
+#include "chrome/browser/ash/login/login_wizard.h"
 #include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
 #include "chrome/browser/ash/login/test/fake_gaia_mixin.h"
@@ -72,7 +72,6 @@
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
-#include "chrome/browser/ui/ash/wallpaper_controller_client_impl.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
@@ -685,11 +684,10 @@
 
   void EnableConsumerKioskMode() {
     bool locked = false;
-    scoped_refptr<content::MessageLoopRunner> runner =
-        new content::MessageLoopRunner;
+    base::RunLoop loop;
     KioskAppManager::Get()->EnableConsumerKioskAutoLaunch(base::BindOnce(
-        &ConsumerKioskModeAutoStartLockCheck, &locked, runner->QuitClosure()));
-    runner->Run();
+        &ConsumerKioskModeAutoStartLockCheck, &locked, loop.QuitClosure()));
+    loop.Run();
     EXPECT_TRUE(locked);
   }
 
@@ -1147,20 +1145,37 @@
             KioskAppLaunchError::Get());
 }
 
-IN_PROC_BROWSER_TEST_F(KioskTest, AutolaunchWarningCancel) {
+class KioskConsumerTest : public KioskTest {
+ public:
+  KioskConsumerTest() { login_manager_.AppendRegularUsers(1); }
+
+  // KioskTest:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    KioskTest::SetUpCommandLine(command_line);
+    // Postpone login screen creation.
+    command_line->RemoveSwitch(switches::kForceLoginManagerInTests);
+  }
+  void SetUpInProcessBrowserTestFixture() override {
+    KioskTest::SetUpInProcessBrowserTestFixture();
+    // Postpone login screen creation.
+    base::CommandLine::ForCurrentProcess()->RemoveSwitch(
+        switches::kForceLoginManagerInTests);
+  }
+
+  bool ShouldWaitForOobeUI() override { return false; }
+
+ private:
+  LoginManagerMixin login_manager_{&mixin_host_};
+};
+
+IN_PROC_BROWSER_TEST_F(KioskConsumerTest, AutolaunchWarningCancel) {
   EnableConsumerKioskMode();
-
-  WizardController::SkipPostLoginScreensForTesting();
-  auto* wizard_controller = WizardController::default_controller();
-  ASSERT_TRUE(wizard_controller);
-
-  // Start login screen after configuring auto launch app since the warning
-  // is triggered when switching to login screen.
-  wizard_controller->AdvanceToScreen(WelcomeView::kScreenId);
   ReloadAutolaunchKioskApps();
   EXPECT_FALSE(KioskAppManager::Get()->GetAutoLaunchApp().empty());
   EXPECT_FALSE(KioskAppManager::Get()->IsAutoLaunchEnabled());
-  wizard_controller->SkipToLoginForTesting();
+
+  ShowLoginWizard(OobeScreen::SCREEN_UNKNOWN);
+  OobeScreenWaiter(KioskAutolaunchScreenView::kScreenId).Wait();
 
   // Wait for the auto launch warning come up.
   WaitForAutoLaunchWarning(/*visibility=*/true);
@@ -1172,20 +1187,14 @@
   EXPECT_FALSE(KioskAppManager::Get()->IsAutoLaunchEnabled());
 }
 
-IN_PROC_BROWSER_TEST_F(KioskTest, AutolaunchWarningConfirm) {
+IN_PROC_BROWSER_TEST_F(KioskConsumerTest, AutolaunchWarningConfirm) {
   EnableConsumerKioskMode();
-
-  WizardController::SkipPostLoginScreensForTesting();
-  auto* wizard_controller = WizardController::default_controller();
-  ASSERT_TRUE(wizard_controller);
-
-  // Start login screen after configuring auto launch app since the warning
-  // is triggered when switching to login screen.
-  wizard_controller->AdvanceToScreen(WelcomeView::kScreenId);
   ReloadAutolaunchKioskApps();
   EXPECT_FALSE(KioskAppManager::Get()->GetAutoLaunchApp().empty());
   EXPECT_FALSE(KioskAppManager::Get()->IsAutoLaunchEnabled());
-  wizard_controller->SkipToLoginForTesting();
+
+  ShowLoginWizard(OobeScreen::SCREEN_UNKNOWN);
+  OobeScreenWaiter(KioskAutolaunchScreenView::kScreenId).Wait();
 
   // Wait for the auto launch warning come up.
   WaitForAutoLaunchWarning(/*visibility=*/true);
@@ -1329,16 +1338,12 @@
 
 // Verifies that a consumer device does not auto-launch kiosk mode when cros
 // settings are untrusted.
-IN_PROC_BROWSER_TEST_F(KioskTest, NoConsumerAutoLaunchWhenUntrusted) {
+IN_PROC_BROWSER_TEST_F(KioskConsumerTest, NoConsumerAutoLaunchWhenUntrusted) {
   EnableConsumerKioskMode();
-
-  // Wait for and confirm the auto-launch warning.
-  WizardController::SkipPostLoginScreensForTesting();
-  auto* wizard_controller = WizardController::default_controller();
-  ASSERT_TRUE(wizard_controller);
-  wizard_controller->AdvanceToScreen(WelcomeView::kScreenId);
   ReloadAutolaunchKioskApps();
-  wizard_controller->SkipToLoginForTesting();
+  ShowLoginWizard(OobeScreen::SCREEN_UNKNOWN);
+  OobeScreenWaiter(KioskAutolaunchScreenView::kScreenId).Wait();
+
   WaitForAutoLaunchWarning(/*visibility=*/true);
 
   // Make cros settings untrusted.
@@ -2775,73 +2780,6 @@
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
 
-// Specialized test fixture for testing kiosk mode on the
-// hidden WebUI initialization flow for slow hardware.
-class KioskHiddenWebUITest : public KioskTest,
-                             public WallpaperControllerObserver {
- public:
-  KioskHiddenWebUITest() = default;
-
-  KioskHiddenWebUITest(const KioskHiddenWebUITest&) = delete;
-  KioskHiddenWebUITest& operator=(const KioskHiddenWebUITest&) = delete;
-
-  // KioskTest:
-  void SetUpOnMainThread() override {
-    LoginDisplayHostWebUI::DisableRestrictiveProxyCheckForTest();
-
-    KioskTest::SetUpOnMainThread();
-    WallpaperControllerClientImpl::Get()->AddObserver(this);
-  }
-  void TearDownOnMainThread() override {
-    WallpaperControllerClientImpl::Get()->RemoveObserver(this);
-    KioskTest::TearDownOnMainThread();
-  }
-
-  void WaitForWallpaper() {
-    if (!wallpaper_loaded_) {
-      runner_ = new content::MessageLoopRunner;
-      runner_->Run();
-    }
-  }
-
-  bool wallpaper_loaded() const { return wallpaper_loaded_; }
-
-  // WallpaperControllerObserver:
-  void OnWallpaperChanged() override {
-    wallpaper_loaded_ = true;
-    if (runner_.get())
-      runner_->Quit();
-  }
-
- private:
-  bool wallpaper_loaded_ = false;
-  scoped_refptr<content::MessageLoopRunner> runner_;
-};
-
-IN_PROC_BROWSER_TEST_F(KioskHiddenWebUITest, AutolaunchWarning) {
-  // Set kiosk app to autolaunch.
-  EnableConsumerKioskMode();
-  WizardController::SkipPostLoginScreensForTesting();
-  auto* wizard_controller = WizardController::default_controller();
-  ASSERT_TRUE(wizard_controller);
-
-  // Start login screen after configuring auto launch app since the warning
-  // is triggered when switching to login screen.
-  wizard_controller->AdvanceToScreen(WelcomeView::kScreenId);
-  ReloadAutolaunchKioskApps();
-  wizard_controller->SkipToLoginForTesting();
-
-  EXPECT_FALSE(KioskAppManager::Get()->GetAutoLaunchApp().empty());
-  EXPECT_FALSE(KioskAppManager::Get()->IsAutoLaunchEnabled());
-
-  // Wait for the auto launch warning come up.
-  WaitForAutoLaunchWarning(/*visibility=*/true);
-
-  // Wait for the wallpaper to load.
-  WaitForWallpaper();
-  EXPECT_TRUE(wallpaper_loaded());
-}
-
 class KioskAutoLaunchViewsTest : public OobeBaseTest,
                                  public LocalStateMixin::Delegate {
  public:
diff --git a/chrome/browser/ash/login/enrollment/hands_off_enrollment_browsertest.cc b/chrome/browser/ash/login/enrollment/hands_off_enrollment_browsertest.cc
index 1255a96..444be8c 100644
--- a/chrome/browser/ash/login/enrollment/hands_off_enrollment_browsertest.cc
+++ b/chrome/browser/ash/login/enrollment/hands_off_enrollment_browsertest.cc
@@ -47,6 +47,7 @@
     MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(
         switches::kEnterpriseEnableZeroTouchEnrollment, "hands-off");
+    command_line->AppendSwitch(switches::kLoginManager);
   }
 
   void SetUpOnMainThread() override {
diff --git a/chrome/browser/ash/login/error_screen_browsertest.cc b/chrome/browser/ash/login/error_screen_browsertest.cc
index 4b74fb8..6628794 100644
--- a/chrome/browser/ash/login/error_screen_browsertest.cc
+++ b/chrome/browser/ash/login/error_screen_browsertest.cc
@@ -77,6 +77,7 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitch(
         switches::kDisableOOBEChromeVoxHintTimerForTesting);
+    command_line->AppendSwitch(switches::kLoginManager);
   }
 
   void ShowErrorScreenWithNetworkList() {
diff --git a/chrome/browser/ash/login/existing_user_controller.cc b/chrome/browser/ash/login/existing_user_controller.cc
index 02e77c5..67871e0 100644
--- a/chrome/browser/ash/login/existing_user_controller.cc
+++ b/chrome/browser/ash/login/existing_user_controller.cc
@@ -686,10 +686,6 @@
       weak_factory_.GetWeakPtr()));
 }
 
-void ExistingUserController::OnStartKioskAutolaunchScreen() {
-  ShowKioskAutolaunchScreen();
-}
-
 void ExistingUserController::SetDisplayEmail(const std::string& email) {
   display_email_ = email;
 }
@@ -729,10 +725,6 @@
   GetLoginDisplayHost()->StartWizard(KioskEnableScreenView::kScreenId);
 }
 
-void ExistingUserController::ShowKioskAutolaunchScreen() {
-  GetLoginDisplayHost()->StartWizard(KioskAutolaunchScreenView::kScreenId);
-}
-
 void ExistingUserController::ShowEncryptionMigrationScreen(
     const UserContext& user_context,
     EncryptionMigrationMode migration_mode) {
diff --git a/chrome/browser/ash/login/existing_user_controller.h b/chrome/browser/ash/login/existing_user_controller.h
index 4f3e2eb0..3607c18 100644
--- a/chrome/browser/ash/login/existing_user_controller.h
+++ b/chrome/browser/ash/login/existing_user_controller.h
@@ -99,7 +99,6 @@
   void Login(const UserContext& user_context,
              const SigninSpecifics& specifics) override;
   void OnStartKioskEnableScreen() override;
-  void OnStartKioskAutolaunchScreen() override;
   void ResetAutoLoginTimer() override;
 
   void CompleteLogin(const UserContext& user_context);
@@ -212,9 +211,6 @@
   // Shows kiosk feature enable screen.
   void ShowKioskEnableScreen();
 
-  // Shows "kiosk auto-launch permission" screen.
-  void ShowKioskAutolaunchScreen();
-
   // Shows "filesystem encryption migration" screen.
   void ShowEncryptionMigrationScreen(const UserContext& user_context,
                                      EncryptionMigrationMode migration_mode);
diff --git a/chrome/browser/ash/login/screens/network_screen.cc b/chrome/browser/ash/login/screens/network_screen.cc
index 07b3eb1..fc460db 100644
--- a/chrome/browser/ash/login/screens/network_screen.cc
+++ b/chrome/browser/ash/login/screens/network_screen.cc
@@ -77,20 +77,9 @@
 }
 
 bool NetworkScreen::MaybeSkip(WizardContext* context) {
-  if (!first_time_shown_)
-    return false;
-  first_time_shown_ = false;
-
-  if (features::IsOobeNetworkScreenSkipEnabled() &&
-      network_state_helper_->IsConnectedToEthernet()) {
-    if (chromeos::features::IsOobeConsolidatedConsentEnabled())
-      exit_callback_.Run(Result::NOT_APPLICABLE_CONSOLIDATED_CONSENT);
-    else
-      exit_callback_.Run(Result::NOT_APPLICABLE);
-    return true;
-  }
-
-  return false;
+  // Skip this screen if the device is connected to Ethernet for the first time
+  // in this session.
+  return UpdateStatusIfConnectedToEthernet();
 }
 
 void NetworkScreen::ShowImpl() {
@@ -211,6 +200,11 @@
 
   network_id_ = network_id;
 
+  // Automatically continue if the device is connected to Ethernet for the first
+  // time in this session.
+  if (UpdateStatusIfConnectedToEthernet())
+    return;
+
   // Automatically continue if we are using Zero-Touch Hands-Off Enrollment.
   if (is_connected && continue_attempts_ == 0 &&
       WizardController::IsZeroTouchHandsOffOobeFlow()) {
@@ -252,4 +246,36 @@
   continue_pressed_ = true;
   WaitForConnection(network_id_);
 }
+
+bool NetworkScreen::UpdateStatusIfConnectedToEthernet() {
+  if (!features::IsOobeNetworkScreenSkipEnabled())
+    return false;
+
+  if (!first_ethernet_connection_)
+    return false;
+
+  if (!network_state_helper_->IsConnectedToEthernet())
+    return false;
+
+  first_ethernet_connection_ = false;
+
+  if (is_hidden()) {
+    // Screen not shown yet: skipping it.
+    if (chromeos::features::IsOobeConsolidatedConsentEnabled()) {
+      exit_callback_.Run(Result::NOT_APPLICABLE_CONSOLIDATED_CONSENT);
+    } else {
+      exit_callback_.Run(Result::NOT_APPLICABLE);
+    }
+  } else {
+    // Screen already shown: automatically continuing.
+    if (chromeos::features::IsOobeConsolidatedConsentEnabled()) {
+      exit_callback_.Run(Result::CONNECTED_REGULAR_CONSOLIDATED_CONSENT);
+    } else {
+      exit_callback_.Run(Result::CONNECTED_REGULAR);
+    }
+  }
+
+  return true;
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/login/screens/network_screen.h b/chrome/browser/ash/login/screens/network_screen.h
index 797ac3a..cc59a498 100644
--- a/chrome/browser/ash/login/screens/network_screen.h
+++ b/chrome/browser/ash/login/screens/network_screen.h
@@ -69,8 +69,10 @@
   friend class DemoSetupTest;
   FRIEND_TEST_ALL_PREFIXES(NetworkScreenTest, CanConnect);
   FRIEND_TEST_ALL_PREFIXES(NetworkScreenTest, Timeout);
-  FRIEND_TEST_ALL_PREFIXES(NetworkScreenTest, HandsOffCanConnect);
-  FRIEND_TEST_ALL_PREFIXES(NetworkScreenTest, HandsOffTimeout);
+  FRIEND_TEST_ALL_PREFIXES(NetworkScreenTest, HandsOffCanConnect_Skipped);
+  FRIEND_TEST_ALL_PREFIXES(NetworkScreenTest, HandsOffTimeout_NotSkipped);
+  FRIEND_TEST_ALL_PREFIXES(NetworkScreenTest,
+                           DelayedEthernetConnection_Skipped);
   FRIEND_TEST_ALL_PREFIXES(NetworkScreenUnitTest, ContinuesAutomatically);
   FRIEND_TEST_ALL_PREFIXES(NetworkScreenUnitTest, ContinuesOnlyOnce);
 
@@ -120,6 +122,10 @@
   // Called when continue button is clicked.
   void OnContinueButtonClicked();
 
+  // Skip this screen or automatically continue if the device is connected to
+  // Ethernet for the first time in this session.
+  bool UpdateStatusIfConnectedToEthernet();
+
   // True if subscribed to network change notification.
   bool is_network_subscribed_ = false;
 
@@ -136,8 +142,9 @@
   // Indicates that we should proceed with OOBE as soon as we are connected.
   bool continue_pressed_ = false;
 
-  // Indicates whether screen has been shown already or not.
-  bool first_time_shown_ = true;
+  // Indicates whether the device has already been connected to Ethernet in this
+  // session or not.
+  bool first_ethernet_connection_ = true;
 
   // Timer for connection timeout.
   base::OneShotTimer connection_timer_;
diff --git a/chrome/browser/ash/login/screens/network_screen_browsertest.cc b/chrome/browser/ash/login/screens/network_screen_browsertest.cc
index c3f580a..8e3b141 100644
--- a/chrome/browser/ash/login/screens/network_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/network_screen_browsertest.cc
@@ -152,11 +152,8 @@
   // EXPECT_FALSE(view_->IsContinueEnabled());
   network_screen()->UpdateStatus();
 
-  // Expecting 2 calls: one to decide if the device should
-  // `StopWaitingForConnection`; and one to decide if it should automatically
-  // continue (in case of zero-touch hands-off enrollment).
   EXPECT_CALL(*network_state_helper(), IsConnected())
-      .Times(2)
+      .Times(AnyNumber())
       .WillRepeatedly(Return(true));
   // TODO(nkostylev): Add integration with WebUI view http://crosbug.com/22570
   // EXPECT_FALSE(view_->IsContinueEnabled());
@@ -173,11 +170,8 @@
   // EXPECT_FALSE(view_->IsContinueEnabled());
   network_screen()->UpdateStatus();
 
-  // Expecting 2 calls: one to decide if the device should automatically
-  // continue (in case of zero-touch hands-off enrollment); and one to decide if
-  // it should show the error bubble.
   EXPECT_CALL(*network_state_helper(), IsConnected())
-      .Times(2)
+      .Times(AnyNumber())
       .WillRepeatedly(Return(false));
   // TODO(nkostylev): Add integration with WebUI view http://crosbug.com/22570
   // EXPECT_FALSE(view_->IsContinueEnabled());
@@ -192,7 +186,7 @@
 
 // The network screen should be skipped if the device can connect and it's using
 // zero-touch hands-off enrollment.
-IN_PROC_BROWSER_TEST_F(NetworkScreenTest, HandsOffCanConnect) {
+IN_PROC_BROWSER_TEST_F(NetworkScreenTest, HandsOffCanConnect_Skipped) {
   // Configure the UI to use Hands-Off Enrollment flow. This cannot be done in
   // the `SetUpCommandLine` method, because the welcome screen would also be
   // skipped, causing the network screen to be shown before we could set up this
@@ -206,12 +200,8 @@
 
   network_screen()->UpdateStatus();
 
-  // Expecting 3 calls: one to decide if the device should
-  // `StopWaitingForConnection`; one to decide if it should automatically
-  // continue (in case of zero-touch hands-off enrollment); and one to decide if
-  // this screens should `NotifyOnConnection`.
   EXPECT_CALL(*network_state_helper(), IsConnected())
-      .Times(3)
+      .Times(AnyNumber())
       .WillRepeatedly(Return(true));
 
   network_screen()->UpdateStatus();
@@ -222,7 +212,7 @@
 
 // The network screen should NOT be skipped if the connection times out, even if
 // it's using zero-touch hands-off enrollment.
-IN_PROC_BROWSER_TEST_F(NetworkScreenTest, HandsOffTimeout) {
+IN_PROC_BROWSER_TEST_F(NetworkScreenTest, HandsOffTimeout_NotSkipped) {
   // Configure the UI to use Hands-Off Enrollment flow. This cannot be done in
   // the `SetUpCommandLine` method, because the welcome screen would also be
   // skipped, causing the network screen to be shown before we could set up this
@@ -236,20 +226,21 @@
 
   network_screen()->UpdateStatus();
 
-  // Expecting 2 calls: one to decide if the device should automatically
-  // continue (in case of zero-touch hands-off enrollment); and one to decide if
-  // it should show the error bubble.
   EXPECT_CALL(*network_state_helper(), IsConnected())
-      .Times(2)
+      .Times(AnyNumber())
       .WillRepeatedly(Return(false));
 
   network_screen()->OnConnectionTimeout();
 }
 
-IN_PROC_BROWSER_TEST_F(NetworkScreenTest, SkippedEthernetConnected) {
+IN_PROC_BROWSER_TEST_F(NetworkScreenTest, EthernetConnection_Skipped) {
+  EXPECT_CALL(*network_state_helper(), IsConnected())
+      .Times(AnyNumber())
+      .WillRepeatedly(Return(true));
   EXPECT_CALL(*network_state_helper(), IsConnectedToEthernet())
       .Times(AnyNumber())
       .WillRepeatedly((Return(true)));
+
   ShowNetworkScreen();
   WaitForScreenExit();
 
@@ -276,4 +267,31 @@
   WaitForScreenShown();
 }
 
+IN_PROC_BROWSER_TEST_F(NetworkScreenTest, DelayedEthernetConnection_Skipped) {
+  ShowNetworkScreen();
+
+  EXPECT_CALL(*network_state_helper(), IsConnecting()).WillOnce((Return(true)));
+
+  network_screen()->UpdateStatus();
+
+  EXPECT_CALL(*network_state_helper(), IsConnected())
+      .Times(AnyNumber())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*network_state_helper(), IsConnectedToEthernet())
+      .Times(AnyNumber())
+      .WillRepeatedly((Return(true)));
+
+  network_screen()->UpdateStatus();
+  WaitForScreenExit();
+
+  if (chromeos::features::IsOobeConsolidatedConsentEnabled())
+    CheckResult(NetworkScreen::Result::CONNECTED_REGULAR_CONSOLIDATED_CONSENT);
+  else
+    CheckResult(NetworkScreen::Result::CONNECTED_REGULAR);
+
+  // Showing screen again to test skip doesn't work now.
+  ShowNetworkScreen();
+  WaitForScreenShown();
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/login/session/chrome_session_manager.cc b/chrome/browser/ash/login/session/chrome_session_manager.cc
index 72da93af..236afde 100644
--- a/chrome/browser/ash/login/session/chrome_session_manager.cc
+++ b/chrome/browser/ash/login/session/chrome_session_manager.cc
@@ -286,14 +286,13 @@
     return;
   }
 
-  if (parsed_command_line.HasSwitch(switches::kLoginManager) &&
-      (!is_running_test || force_login_screen_in_test)) {
-    VLOG(1) << "Starting Chrome with login/oobe screen.";
+  if (parsed_command_line.HasSwitch(switches::kLoginManager)) {
     oobe_configuration_->CheckConfiguration();
+    if (is_running_test && !force_login_screen_in_test)
+      return;
+    VLOG(1) << "Starting Chrome with login/oobe screen.";
     StartLoginOobeSession();
     return;
-  } else if (is_running_test) {
-    oobe_configuration_->CheckConfiguration();
   }
 
   VLOG(1) << "Starting Chrome with a user session.";
diff --git a/chrome/browser/ash/login/ui/login_display.h b/chrome/browser/ash/login/ui/login_display.h
index 4943d44..fac2797c 100644
--- a/chrome/browser/ash/login/ui/login_display.h
+++ b/chrome/browser/ash/login/ui/login_display.h
@@ -35,9 +35,6 @@
     // Called when the user requests kiosk enable screen.
     virtual void OnStartKioskEnableScreen() = 0;
 
-    // Called when the owner permission for kiosk app auto launch is requested.
-    virtual void OnStartKioskAutolaunchScreen() = 0;
-
     // Restarts the auto-login timer if it is running.
     virtual void ResetAutoLoginTimer() = 0;
 
diff --git a/chrome/browser/ash/login/ui/login_display_mojo.cc b/chrome/browser/ash/login/ui/login_display_mojo.cc
index a420238..2f088f2 100644
--- a/chrome/browser/ash/login/ui/login_display_mojo.cc
+++ b/chrome/browser/ash/login/ui/login_display_mojo.cc
@@ -149,10 +149,6 @@
   return false;
 }
 
-void LoginDisplayMojo::ShowKioskAutolaunchScreen() {
-  NOTIMPLEMENTED();
-}
-
 bool LoginDisplayMojo::IsUserSigninCompleted() const {
   return is_signin_completed();
 }
diff --git a/chrome/browser/ash/login/ui/login_display_mojo.h b/chrome/browser/ash/login/ui/login_display_mojo.h
index 9f89161..8844f54 100644
--- a/chrome/browser/ash/login/ui/login_display_mojo.h
+++ b/chrome/browser/ash/login/ui/login_display_mojo.h
@@ -40,7 +40,6 @@
   void Login(const UserContext& user_context,
              const SigninSpecifics& specifics) override;
   bool IsSigninInProgress() const override;
-  void ShowKioskAutolaunchScreen() override;
   bool IsUserSigninCompleted() const override;
 
   // user_manager::UserManager::Observer:
diff --git a/chrome/browser/ash/login/ui/login_display_webui.cc b/chrome/browser/ash/login/ui/login_display_webui.cc
index f88bdd1..c86513a 100644
--- a/chrome/browser/ash/login/ui/login_display_webui.cc
+++ b/chrome/browser/ash/login/ui/login_display_webui.cc
@@ -67,11 +67,6 @@
     delegate_->Login(user_context, specifics);
 }
 
-void LoginDisplayWebUI::ShowKioskAutolaunchScreen() {
-  if (delegate_)
-    delegate_->OnStartKioskAutolaunchScreen();
-}
-
 bool LoginDisplayWebUI::IsSigninInProgress() const {
   return delegate_->IsSigninInProgress();
 }
diff --git a/chrome/browser/ash/login/ui/login_display_webui.h b/chrome/browser/ash/login/ui/login_display_webui.h
index 89183d7..30d72c4 100644
--- a/chrome/browser/ash/login/ui/login_display_webui.h
+++ b/chrome/browser/ash/login/ui/login_display_webui.h
@@ -33,7 +33,6 @@
   void Login(const UserContext& user_context,
              const SigninSpecifics& specifics) override;
   bool IsSigninInProgress() const override;
-  void ShowKioskAutolaunchScreen() override;
   bool IsUserSigninCompleted() const override;
 
   // ui::UserActivityDetector implementation:
diff --git a/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.cc b/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.cc
index cdcaf2c..4e49542 100644
--- a/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.cc
+++ b/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.cc
@@ -55,6 +55,25 @@
 
 MockGooglePhotosCountFetcher::~MockGooglePhotosCountFetcher() = default;
 
+MockGooglePhotosEnabledFetcher::MockGooglePhotosEnabledFetcher(Profile* profile)
+    : GooglePhotosEnabledFetcher(profile) {
+  ON_CALL(*this, AddRequestAndStartIfNecessary)
+      .WillByDefault(
+          [](base::OnceCallback<void(GooglePhotosEnablementState)> callback) {
+            base::SequencedTaskRunnerHandle::Get()->PostTask(
+                FROM_HERE,
+                base::BindOnce(std::move(callback),
+                               GooglePhotosEnablementState::kEnabled));
+          });
+
+  ON_CALL(*this, ParseResponse)
+      .WillByDefault([this](const base::Value::Dict* response) {
+        return GooglePhotosEnabledFetcher::ParseResponse(response);
+      });
+}
+
+MockGooglePhotosEnabledFetcher::~MockGooglePhotosEnabledFetcher() = default;
+
 MockGooglePhotosPhotosFetcher::MockGooglePhotosPhotosFetcher(Profile* profile)
     : GooglePhotosPhotosFetcher(profile) {
   using ash::personalization_app::mojom::FetchGooglePhotosPhotosResponse;
diff --git a/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h b/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h
index 87e9ffc..c43b6f24 100644
--- a/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h
+++ b/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h
@@ -61,6 +61,31 @@
               (override));
 };
 
+// Fetcher that claims the user is allowed to access Google Photos data. Used to
+// avoid network requests in unit tests.
+class MockGooglePhotosEnabledFetcher : public GooglePhotosEnabledFetcher {
+ public:
+  explicit MockGooglePhotosEnabledFetcher(Profile* profile);
+
+  MockGooglePhotosEnabledFetcher(const MockGooglePhotosEnabledFetcher&) =
+      delete;
+  MockGooglePhotosEnabledFetcher& operator=(
+      const MockGooglePhotosEnabledFetcher&) = delete;
+
+  ~MockGooglePhotosEnabledFetcher() override;
+
+  // GooglePhotosEnabledFetcher:
+  MOCK_METHOD(void,
+              AddRequestAndStartIfNecessary,
+              (base::OnceCallback<void(GooglePhotosEnablementState)> callback),
+              (override));
+
+  MOCK_METHOD(GooglePhotosEnablementState,
+              ParseResponse,
+              (const base::Value::Dict* response),
+              (override));
+};
+
 // Fetcher that returns an empty photo list and no resume token in response to a
 // request for photos from the user's Google Photos library. Used to avoid
 // network requests in unit tests.
diff --git a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
index 178e3c9d..0ffde54 100644
--- a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
+++ b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
@@ -125,6 +125,32 @@
           "Not implemented, considered not necessary."
       })");
 
+// The URL to download whether the user is allowed to access Google Photos data.
+constexpr char kGooglePhotosEnabledUrl[] =
+    "https://photosfirstparty-pa.googleapis.com/v1/chromeos/userenabled:read";
+
+constexpr net::NetworkTrafficAnnotationTag
+    kGooglePhotosEnabledTrafficAnnotation =
+        net::DefineNetworkTrafficAnnotation("wallpaper_google_photos_enabled",
+                                            R"(
+      semantics {
+        sender: "ChromeOS Wallpaper Picker"
+        description:
+          "The ChromeOS Wallpaper Picker displays a tile to view and pick from "
+          "a user's Google Photos library. This tile should not display any "
+          "user data if there is an enterprise setting preventing the user "
+          "from accessing Google Photos."
+        trigger: "When the user opens the ChromeOS Wallpaper Picker app."
+        data: "OAuth credentials for the user's Google Photos account."
+        destination: GOOGLE_OWNED_SERVICE
+      }
+      policy {
+        cookies_allowed: NO
+        setting: "N/A"
+        policy_exception_justification:
+          "Not implemented, considered not necessary."
+      })");
+
 // The URL to download a photo from a user's Google Photos library.
 constexpr char kGooglePhotosPhotoUrl[] =
     "https://photosfirstparty-pa.googleapis.com/v1/chromeos/itemById:read";
@@ -718,6 +744,36 @@
   return base::saturated_cast<int>(count);
 }
 
+GooglePhotosEnabledFetcher::GooglePhotosEnabledFetcher(Profile* profile)
+    : GooglePhotosFetcher(profile, kGooglePhotosEnabledTrafficAnnotation) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+GooglePhotosEnabledFetcher::~GooglePhotosEnabledFetcher() = default;
+
+void GooglePhotosEnabledFetcher::AddRequestAndStartIfNecessary(
+    base::OnceCallback<void(GooglePhotosEnablementState)> callback) {
+  GooglePhotosFetcher::AddRequestAndStartIfNecessary(
+      GURL(kGooglePhotosEnabledUrl), std::move(callback));
+}
+
+GooglePhotosEnablementState GooglePhotosEnabledFetcher::ParseResponse(
+    const base::Value::Dict* response) {
+  if (!response)
+    return GooglePhotosEnablementState::kError;
+
+  const auto* state = response->FindStringByDottedPath("status.userState");
+
+  if (!state)
+    return GooglePhotosEnablementState::kError;
+
+  return *state == "USER_PERMITTED"
+             ? GooglePhotosEnablementState::kEnabled
+             : *state == "USER_DASHER_DISABLED"
+                   ? GooglePhotosEnablementState::kDisabled
+                   : GooglePhotosEnablementState::kError;
+}
+
 GooglePhotosPhotosFetcher::GooglePhotosPhotosFetcher(Profile* profile)
     : GooglePhotosFetcher(profile, kGooglePhotosPhotosTrafficAnnotation) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h
index 5ebf5cd..f562a51f 100644
--- a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h
+++ b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h
@@ -253,6 +253,28 @@
   int ParseResponse(const base::Value::Dict* response) override;
 };
 
+using ash::personalization_app::mojom::GooglePhotosEnablementState;
+// Downloads whether the user is allowed to access Google Photos data.
+class GooglePhotosEnabledFetcher
+    : public GooglePhotosFetcher<GooglePhotosEnablementState> {
+ public:
+  explicit GooglePhotosEnabledFetcher(Profile* profile);
+
+  GooglePhotosEnabledFetcher(const GooglePhotosEnabledFetcher&) = delete;
+  GooglePhotosEnabledFetcher& operator=(const GooglePhotosEnabledFetcher&) =
+      delete;
+
+  ~GooglePhotosEnabledFetcher() override;
+
+  virtual void AddRequestAndStartIfNecessary(
+      base::OnceCallback<void(GooglePhotosEnablementState)> callback);
+
+ protected:
+  // GooglePhotosFetcher:
+  GooglePhotosEnablementState ParseResponse(
+      const base::Value::Dict* response) override;
+};
+
 using GooglePhotosPhotosCbkArgs =
     ash::personalization_app::mojom::FetchGooglePhotosPhotosResponsePtr;
 // Downloads visible photos from a user's Google Photos library.
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.cc
index ec92503..cbf6d42 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.cc
@@ -208,6 +208,26 @@
       std::move(callback));
 }
 
+void PersonalizationAppWallpaperProviderImpl::FetchGooglePhotosEnabled(
+    FetchGooglePhotosEnabledCallback callback) {
+  if (!ash::features::IsWallpaperGooglePhotosIntegrationEnabled()) {
+    mojo::ReportBadMessage(
+        "Cannot call `FetchGooglePhotosEnabled()` without Google Photos "
+        "Wallpaper integration enabled.");
+    std::move(callback).Run(
+        ash::personalization_app::mojom::GooglePhotosEnablementState::kError);
+    return;
+  }
+
+  if (!google_photos_enabled_fetcher_) {
+    google_photos_enabled_fetcher_ =
+        std::make_unique<wallpaper_handlers::GooglePhotosEnabledFetcher>(
+            profile_);
+  }
+  google_photos_enabled_fetcher_->AddRequestAndStartIfNecessary(
+      std::move(callback));
+}
+
 void PersonalizationAppWallpaperProviderImpl::FetchGooglePhotosPhotos(
     const absl::optional<std::string>& item_id,
     const absl::optional<std::string>& album_id,
@@ -505,6 +525,13 @@
   return google_photos_count_fetcher_.get();
 }
 
+wallpaper_handlers::GooglePhotosEnabledFetcher*
+PersonalizationAppWallpaperProviderImpl::SetGooglePhotosEnabledFetcherForTest(
+    std::unique_ptr<wallpaper_handlers::GooglePhotosEnabledFetcher> fetcher) {
+  google_photos_enabled_fetcher_ = std::move(fetcher);
+  return google_photos_enabled_fetcher_.get();
+}
+
 wallpaper_handlers::GooglePhotosPhotosFetcher*
 PersonalizationAppWallpaperProviderImpl::SetGooglePhotosPhotosFetcherForTest(
     std::unique_ptr<wallpaper_handlers::GooglePhotosPhotosFetcher> fetcher) {
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.h b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.h
index 35357e50..5c0c7d0 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.h
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.h
@@ -48,6 +48,7 @@
 class BackdropImageInfoFetcher;
 class GooglePhotosAlbumsFetcher;
 class GooglePhotosCountFetcher;
+class GooglePhotosEnabledFetcher;
 class GooglePhotosPhotosFetcher;
 }  // namespace wallpaper_handlers
 
@@ -94,6 +95,9 @@
 
   void FetchGooglePhotosCount(FetchGooglePhotosCountCallback callback) override;
 
+  void FetchGooglePhotosEnabled(
+      FetchGooglePhotosEnabledCallback callback) override;
+
   void FetchGooglePhotosPhotos(
       const absl::optional<std::string>& item_id,
       const absl::optional<std::string>& album_id,
@@ -154,6 +158,10 @@
   SetGooglePhotosCountFetcherForTest(
       std::unique_ptr<wallpaper_handlers::GooglePhotosCountFetcher> fetcher);
 
+  wallpaper_handlers::GooglePhotosEnabledFetcher*
+  SetGooglePhotosEnabledFetcherForTest(
+      std::unique_ptr<wallpaper_handlers::GooglePhotosEnabledFetcher> fetcher);
+
   wallpaper_handlers::GooglePhotosPhotosFetcher*
   SetGooglePhotosPhotosFetcherForTest(
       std::unique_ptr<wallpaper_handlers::GooglePhotosPhotosFetcher> fetcher);
@@ -244,6 +252,13 @@
   std::unique_ptr<wallpaper_handlers::GooglePhotosCountFetcher>
       google_photos_count_fetcher_;
 
+  // Fetches the state of the user's permission to access Google Photos data.
+  // Constructed lazily at the time of the first request and then persists for
+  // the rest of the delegate's lifetime, unless preemptively or subsequently
+  // replaced by a mock in a test.
+  std::unique_ptr<wallpaper_handlers::GooglePhotosEnabledFetcher>
+      google_photos_enabled_fetcher_;
+
   // Fetches visible photos from the user's Google Photos library. Constructed
   // lazily at the time of the first request and then persists for the rest of
   // the delegate's lifetime, unless preemptively or subsequently replaced by a
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
index cd7292d..c6979feb 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
@@ -490,6 +490,32 @@
   wallpaper_provider_remote()->FlushForTesting();
 }
 
+TEST_P(PersonalizationAppWallpaperProviderImplGooglePhotosTest, FetchEnabled) {
+  using ash::personalization_app::mojom::GooglePhotosEnablementState;
+
+  // Mock a fetcher for the enablement state query.
+  auto* const google_photos_enabled_fetcher = static_cast<
+      ::testing::NiceMock<wallpaper_handlers::MockGooglePhotosEnabledFetcher>*>(
+      delegate()->SetGooglePhotosEnabledFetcherForTest(
+          std::make_unique<::testing::NiceMock<
+              wallpaper_handlers::MockGooglePhotosEnabledFetcher>>(profile())));
+
+  // Simulate the client making multiple requests for the same information to
+  // test that all callbacks for that query are called.
+  EXPECT_CALL(*google_photos_enabled_fetcher, AddRequestAndStartIfNecessary)
+      .Times(GooglePhotosEnabled() ? kNumFetches : 0);
+
+  for (size_t i = 0; i < kNumFetches; ++i) {
+    wallpaper_provider_remote()->get()->FetchGooglePhotosEnabled(
+        base::BindLambdaForTesting([this](GooglePhotosEnablementState state) {
+          EXPECT_EQ(state, GooglePhotosEnabled()
+                               ? GooglePhotosEnablementState::kEnabled
+                               : GooglePhotosEnablementState::kError);
+        }));
+  }
+  wallpaper_provider_remote()->FlushForTesting();
+}
+
 TEST_P(PersonalizationAppWallpaperProviderImplGooglePhotosTest, FetchPhotos) {
   // Mock a fetcher for the photos query.
   auto* const google_photos_photos_fetcher = static_cast<
@@ -650,6 +676,41 @@
   EXPECT_EQ(1, google_photos_count_fetcher->ParseResponse(&response));
 }
 
+TEST_P(PersonalizationAppWallpaperProviderImplGooglePhotosTest, ParseEnabled) {
+  using ash::personalization_app::mojom::GooglePhotosEnablementState;
+
+  // Mock a fetcher to parse constructed responses.
+  auto* const google_photos_enabled_fetcher = static_cast<
+      ::testing::NiceMock<wallpaper_handlers::MockGooglePhotosEnabledFetcher>*>(
+      delegate()->SetGooglePhotosEnabledFetcherForTest(
+          std::make_unique<::testing::NiceMock<
+              wallpaper_handlers::MockGooglePhotosEnabledFetcher>>(profile())));
+
+  // Parse an absent response (simulating a fetching error).
+  EXPECT_EQ(GooglePhotosEnablementState::kError,
+            google_photos_enabled_fetcher->ParseResponse(nullptr));
+
+  // Parse a response without an enabled state.
+  base::Value::Dict response;
+  EXPECT_EQ(GooglePhotosEnablementState::kError,
+            google_photos_enabled_fetcher->ParseResponse(&response));
+
+  // Parse a response with an unknown enabled state.
+  response.SetByDottedPath("status.userState", "UNKNOWN_STATUS_STATE");
+  EXPECT_EQ(GooglePhotosEnablementState::kError,
+            google_photos_enabled_fetcher->ParseResponse(&response));
+
+  // Parse a response indicating that the user cannot access Google Photos data.
+  response.SetByDottedPath("status.userState", "USER_DASHER_DISABLED");
+  EXPECT_EQ(GooglePhotosEnablementState::kDisabled,
+            google_photos_enabled_fetcher->ParseResponse(&response));
+
+  // Parse a response indicating that the user can access Google Photos data.
+  response.SetByDottedPath("status.userState", "USER_PERMITTED");
+  EXPECT_EQ(GooglePhotosEnablementState::kEnabled,
+            google_photos_enabled_fetcher->ParseResponse(&response));
+}
+
 TEST_P(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
        ParsePhotosAbsentPhoto) {
   using ash::personalization_app::mojom::FetchGooglePhotosPhotosResponse;
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java
index 35c31d8..cd34390 100644
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustMessageViewModel.java
@@ -23,6 +23,7 @@
 import org.chromium.components.messages.DismissReason;
 import org.chromium.components.messages.MessageBannerProperties;
 import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.lang.annotation.Retention;
@@ -87,9 +88,11 @@
                 .with(MessageBannerProperties.ON_DISMISSED,
                         (reason) -> actionsHandler.onMessageDismissed(reason, messageAssociatedUrl))
                 .with(MessageBannerProperties.ON_PRIMARY_ACTION,
-                        ()
-                                -> actionsHandler.onMessagePrimaryAction(
-                                        trustSignals, messageAssociatedUrl))
+                        () -> {
+                            actionsHandler.onMessagePrimaryAction(
+                                    trustSignals, messageAssociatedUrl);
+                            return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
+                        })
                 .build();
     }
 
@@ -181,4 +184,4 @@
         assert titleUI == MessageTitleUI.VIEW_STORE_INFO : "Invalid title UI";
         return R.string.merchant_viewer_message_title;
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/key_persistence_delegate.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/key_persistence_delegate.h
index 65b5fc2..0537fe3 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/key_persistence_delegate.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/key_persistence_delegate.h
@@ -22,6 +22,10 @@
   using KeyInfo = std::pair<KeyTrustLevel, std::vector<uint8_t>>;
   virtual ~KeyPersistenceDelegate() = default;
 
+  // Validates that the current context has sufficient permissions to perform a
+  // key rotation operation.
+  virtual bool CheckRotationPermissions() = 0;
+
   // Stores the trust level and wrapped key in a platform specific location.
   // This method requires elevation since it writes to a location that is
   // shared by all OS users of the device.  Returns true on success.
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.cc
index f16a233..2de57cb 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.cc
@@ -4,15 +4,20 @@
 
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.h"
 
+#include <grp.h>
+#include <sys/stat.h>
+
 #include <string>
 #include <vector>
 
 #include "base/base64.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/notreached.h"
+#include "base/syslog_logging.h"
 #include "base/values.h"
 #include "build/branding_buildflags.h"
 #include "chrome/browser/enterprise/connectors/device_trust/key_management/core/shared_command_constants.h"
@@ -26,6 +31,12 @@
 
 namespace {
 
+// Mode the signing key file should have.
+constexpr int kFileMode = 0664;
+
+// Group name the signing key file should have.
+constexpr char kGroupName[] = "chromemgmt";
+
 // Path to the signing key file differs based on chrome/chromium build.
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 base::FilePath::CharType kDirPolicyPath[] =
@@ -35,14 +46,51 @@
     FILE_PATH_LITERAL("/etc/chromium/policies");
 #endif
 
-// Returns the created signing key file with the specified file `flags`.
-base::File OpenSigningKeyFile(uint32_t flags) {
+base::FilePath GetSigningKeyFilePath() {
   base::FilePath path(kDirPolicyPath);
-  return base::File(path.Append(constants::kSigningKeyFilePath), flags);
+  return path.Append(constants::kSigningKeyFilePath);
+}
+
+base::File OpenSigningKeyFile(uint32_t flags) {
+  return base::File(GetSigningKeyFilePath(), flags);
 }
 
 }  // namespace
 
+bool LinuxKeyPersistenceDelegate::CheckRotationPermissions() {
+  auto signing_key_path = GetSigningKeyFilePath();
+  auto file = base::File(signing_key_path,
+                         base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+
+  if (!file.IsValid() ||
+      (file.Lock(base::File::LockMode::kExclusive) != base::File::FILE_OK)) {
+    SYSLOG(ERROR) << "Device trust key rotation failed. Could not acquire a "
+                     "lock on the signing key storage.";
+    return false;
+  }
+
+  int mode;
+  if (!base::GetPosixFilePermissions(signing_key_path, &mode)) {
+    SYSLOG(ERROR)
+        << "Device trust key rotation failed. Could not get permissions "
+           "for the signing key storage.";
+    return false;
+  }
+
+  struct stat st;
+  stat(signing_key_path.value().c_str(), &st);
+  gid_t signing_key_file_gid = st.st_gid;
+  struct group* chrome_mgmt_group = getgrnam(kGroupName);
+
+  if (!chrome_mgmt_group || signing_key_file_gid != chrome_mgmt_group->gr_gid ||
+      mode != kFileMode) {
+    SYSLOG(ERROR) << "Device trust key rotation failed. Incorrect permissions "
+                     "for signing key storage.";
+    return false;
+  }
+  return true;
+}
+
 LinuxKeyPersistenceDelegate::~LinuxKeyPersistenceDelegate() = default;
 const int kMaxBufferSize = 2048;
 const char kSigningKeyName[] = "signingKey";
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.h
index 764d8a6..9e49f09 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/linux_key_persistence_delegate.h
@@ -15,6 +15,7 @@
   ~LinuxKeyPersistenceDelegate() override;
 
   // KeyPersistenceDelegate:
+  bool CheckRotationPermissions() override;
   bool StoreKeyPair(KeyPersistenceDelegate::KeyTrustLevel trust_level,
                     std::vector<uint8_t> wrapped) override;
   KeyPersistenceDelegate::KeyInfo LoadKeyPair() override;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.cc
index cbb07cef..10abe59e 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.cc
@@ -10,6 +10,11 @@
 
 MacKeyPersistenceDelegate::~MacKeyPersistenceDelegate() = default;
 
+bool MacKeyPersistenceDelegate::CheckRotationPermissions() {
+  NOTIMPLEMENTED();
+  return false;
+}
+
 bool MacKeyPersistenceDelegate::StoreKeyPair(KeyTrustLevel trust_level,
                                              std::vector<uint8_t> wrapped) {
   NOTIMPLEMENTED();
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.h
index cc7b780d..1465e92 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.h
@@ -15,6 +15,7 @@
   ~MacKeyPersistenceDelegate() override;
 
   // KeyPersistenceDelegate:
+  bool CheckRotationPermissions() override;
   bool StoreKeyPair(KeyPersistenceDelegate::KeyTrustLevel trust_level,
                     std::vector<uint8_t> wrapped) override;
   KeyPersistenceDelegate::KeyInfo LoadKeyPair() override;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mock_key_persistence_delegate.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mock_key_persistence_delegate.h
index a5fce03..e2e8fe99 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mock_key_persistence_delegate.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mock_key_persistence_delegate.h
@@ -18,6 +18,7 @@
   ~MockKeyPersistenceDelegate() override;
 
   // KeyPersistenceDelegate:
+  MOCK_METHOD(bool, CheckRotationPermissions, (), (override));
   MOCK_METHOD(bool,
               StoreKeyPair,
               (KeyPersistenceDelegate::KeyTrustLevel, std::vector<uint8_t>),
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.cc
index ea763ea..0572498 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.cc
@@ -14,6 +14,10 @@
 
 WinKeyPersistenceDelegate::~WinKeyPersistenceDelegate() = default;
 
+bool WinKeyPersistenceDelegate::CheckRotationPermissions() {
+  return true;
+}
+
 bool WinKeyPersistenceDelegate::StoreKeyPair(
     KeyPersistenceDelegate::KeyTrustLevel trust_level,
     std::vector<uint8_t> wrapped) {
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.h
index dc7c0ac..a41361e 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.h
@@ -15,6 +15,7 @@
   ~WinKeyPersistenceDelegate() override;
 
   // KeyPersistenceDelegate:
+  bool CheckRotationPermissions() override;
   bool StoreKeyPair(KeyPersistenceDelegate::KeyTrustLevel trust_level,
                     std::vector<uint8_t> wrapped) override;
   KeyPersistenceDelegate::KeyInfo LoadKeyPair() override;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_impl.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_impl.cc
index 4645ce8..9f7a677 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_impl.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_impl.cc
@@ -68,6 +68,12 @@
     return false;
   }
 
+  if (!persistence_delegate_->CheckRotationPermissions()) {
+    RecordRotationStatus(nonce,
+                         RotationStatus::FAILURE_INCORRECT_FILE_PERMISSIONS);
+    return false;
+  }
+
   // Create a new key pair.  First try creating a TPM-backed key.  If that does
   // not work, try a less secure type.
   KeyTrustLevel new_trust_level = BPKUR::KEY_TRUST_LEVEL_UNSPECIFIED;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_unittest.cc
index 07adadb..ec52c369 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_unittest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_unittest.cc
@@ -96,6 +96,8 @@
   // The mocked delegate is already set-up to return a working TPM key and
   // provider.
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair());
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider()).Times(2);
   EXPECT_CALL(
       *mock_persistence_delegate,
@@ -149,6 +151,8 @@
   // provider. Force it to not return a key.
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair())
       .WillOnce(Return(CreateEmptyKeyPair()));
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider());
   EXPECT_CALL(*mock_persistence_delegate,
               StoreKeyPair(BPKUR::CHROME_BROWSER_TPM_KEY, _))
@@ -179,6 +183,8 @@
       std::make_unique<MockKeyPersistenceDelegate>();
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair())
       .WillOnce(Return(CreateEmptyKeyPair()));
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider());
   EXPECT_CALL(*mock_persistence_delegate,
               StoreKeyPair(BPKUR::CHROME_BROWSER_OS_KEY, _))
@@ -218,6 +224,9 @@
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair())
       .WillOnce(Return(CreateEmptyKeyPair()));
 
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
+
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider());
   EXPECT_CALL(*mock_persistence_delegate,
               StoreKeyPair(BPKUR::CHROME_BROWSER_TPM_KEY, _))
@@ -266,6 +275,9 @@
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair())
       .WillOnce(Return(CreateEmptyKeyPair()));
 
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
+
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider());
   EXPECT_CALL(*mock_persistence_delegate,
               StoreKeyPair(BPKUR::CHROME_BROWSER_TPM_KEY, _))
@@ -305,6 +317,8 @@
   auto mock_persistence_delegate = scoped_factory_.CreateMockedECDelegate();
 
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair());
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider());
   EXPECT_CALL(*mock_persistence_delegate,
               StoreKeyPair(BPKUR::CHROME_BROWSER_OS_KEY, _))
@@ -337,6 +351,8 @@
   auto original_key_wrapped = scoped_factory_.ec_wrapped_key();
 
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair());
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider());
   EXPECT_CALL(
       *mock_persistence_delegate,
@@ -369,6 +385,8 @@
   auto original_key_wrapped = scoped_factory_.ec_wrapped_key();
 
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair());
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider());
   EXPECT_CALL(
       *mock_persistence_delegate,
@@ -410,6 +428,8 @@
   InSequence s;
 
   EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair());
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(true));
   EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider());
   EXPECT_CALL(
       *mock_persistence_delegate,
@@ -439,6 +459,41 @@
   histogram_tester.ExpectTotalCount(opposite_status_histogram_name(), 0);
 }
 
+// Tests a success key rotation flow when incorrect permissions were set
+// on the signing key file.
+TEST_P(KeyRotationManagerTest,
+       RotateWithAdminRights_StoreFailed_InvalidFilePermissions) {
+  base::HistogramTester histogram_tester;
+
+  auto mock_persistence_delegate =
+      std::make_unique<MockKeyPersistenceDelegate>();
+  EXPECT_CALL(*mock_persistence_delegate, CheckRotationPermissions())
+      .WillOnce(Return(false));
+  EXPECT_CALL(*mock_persistence_delegate, LoadKeyPair());
+  EXPECT_CALL(*mock_persistence_delegate, GetTpmBackedKeyProvider()).Times(0);
+  EXPECT_CALL(*mock_persistence_delegate,
+              StoreKeyPair(BPKUR::CHROME_BROWSER_OS_KEY, _))
+      .Times(0);
+
+  GURL dm_server_url(kDmServerUrl);
+  auto mock_network_delegate = std::make_unique<MockKeyNetworkDelegate>();
+  EXPECT_CALL(*mock_network_delegate,
+              SendPublicKeyToDmServerSync(dm_server_url, kDmToken, _))
+      .Times(0);
+
+  auto manager = KeyRotationManager::CreateForTesting(
+      std::move(mock_network_delegate), std::move(mock_persistence_delegate));
+
+  EXPECT_FALSE(
+      manager->RotateWithAdminRights(dm_server_url, kDmToken, nonce()));
+
+  // Should expect one successful attempt to rotate a key.
+  histogram_tester.ExpectUniqueSample(
+      status_histogram_name(),
+      RotationStatus::FAILURE_INCORRECT_FILE_PERMISSIONS, 1);
+  histogram_tester.ExpectTotalCount(opposite_status_histogram_name(), 0);
+}
+
 INSTANTIATE_TEST_SUITE_P(, KeyRotationManagerTest, testing::Bool());
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/management_service/rotate_util_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/management_service/rotate_util_unittest.cc
index 07c1df47..31c04eb 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/management_service/rotate_util_unittest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/management_service/rotate_util_unittest.cc
@@ -87,6 +87,9 @@
 
 // Tests when the chrome management services key rotation was successful.
 TEST_F(RotateUtilTest, RotateDTKeySuccess) {
+  EXPECT_CALL(*mock_persistence_delegate_, CheckRotationPermissions())
+      .WillOnce(Return(true));
+
   EXPECT_CALL(*mock_persistence_delegate_, StoreKeyPair(_, _))
       .WillOnce(Return(true));
 
@@ -102,15 +105,6 @@
 }
 
 // Tests when the chrome management services key rotation failed due to
-// incorrect signing key file permissions.
-TEST_F(RotateUtilTest, RotateDTKeyFailure_IncorrectPermissions) {
-  EXPECT_FALSE(RotateDeviceTrustKey(
-      std::move(key_rotation_manager_),
-      GetCommandLine(kEncodedFakeDMToken, kEncodedNonce, kFakeDmServerUrl),
-      version_info::Channel::STABLE));
-}
-
-// Tests when the chrome management services key rotation failed due to
 // an invalid dm token.
 TEST_F(RotateUtilTest, RotateDTKeyFailure_InvalidDmToken) {
   EXPECT_FALSE(RotateDeviceTrustKey(
@@ -147,8 +141,23 @@
 }
 
 // Tests when the chrome management services key rotation failed due to
+// incorrect signing key permissions.
+TEST_F(RotateUtilTest, RotateDTKeyFailure_PermissionsFailed) {
+  EXPECT_CALL(*mock_persistence_delegate_, CheckRotationPermissions())
+      .WillOnce(Return(false));
+
+  EXPECT_FALSE(RotateDeviceTrustKey(
+      std::move(key_rotation_manager_),
+      GetCommandLine(kEncodedFakeDMToken, kEncodedNonce, kFakeDmServerUrl),
+      version_info::Channel::STABLE));
+}
+
+// Tests when the chrome management services key rotation failed due to
 // an store key failure.
 TEST_F(RotateUtilTest, RotateDTKeyFailure_StoreKeyFailed) {
+  EXPECT_CALL(*mock_persistence_delegate_, CheckRotationPermissions())
+      .WillOnce(Return(true));
+
   EXPECT_CALL(*mock_persistence_delegate_, StoreKeyPair(_, _))
       .WillOnce(Return(false));
 
@@ -161,6 +170,9 @@
 // Tests when the chrome management services key rotation failed due to
 // an upload key failure.
 TEST_F(RotateUtilTest, RotateDTKeyFailure_UploadKeyFailed) {
+  EXPECT_CALL(*mock_persistence_delegate_, CheckRotationPermissions())
+      .WillOnce(Return(true));
+
   EXPECT_CALL(*mock_persistence_delegate_, StoreKeyPair(_, _))
       .Times(2)
       .WillRepeatedly(Return(true));
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/metrics_util.h b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/metrics_util.h
index 97b82126..deb56f1 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/metrics_util.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/metrics_util.h
@@ -20,7 +20,8 @@
   FAILURE_CANNOT_UPLOAD_KEY_TRIES_EXHAUSTED,
   FAILURE_CANNOT_UPLOAD_KEY_RESTORE_FAILED,
   FAILURE_CANNOT_UPLOAD_KEY_TRIES_EXHAUSTED_RESTORE_FAILED,
-  kMaxValue = FAILURE_CANNOT_UPLOAD_KEY_TRIES_EXHAUSTED_RESTORE_FAILED,
+  FAILURE_INCORRECT_FILE_PERMISSIONS,
+  kMaxValue = FAILURE_INCORRECT_FILE_PERMISSIONS,
 };
 
 // Metrics for the RotateWithAdminRights() result. `nonce` is the
diff --git a/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc b/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc
index 441237f..0befa520 100644
--- a/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc
+++ b/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.cc
@@ -115,13 +115,40 @@
     const std::string& extension_id,
     const PortId& receiver_port_id,
     content::WebContents* receiver_contents,
-    int receiver_frame_id) {
+    int receiver_frame_id,
+    const std::string& receiver_document_id) {
   // Frame ID -1 is every frame in the tab.
-  bool include_child_frames = receiver_frame_id == -1;
-  content::RenderFrameHost* receiver_rfh =
-      include_child_frames ? receiver_contents->GetMainFrame()
-                           : ExtensionApiFrameIdMap::GetRenderFrameHostById(
-                                 receiver_contents, receiver_frame_id);
+  bool include_child_frames =
+      receiver_frame_id == -1 && receiver_document_id.empty();
+
+  content::RenderFrameHost* receiver_rfh = nullptr;
+  if (include_child_frames) {
+    // The target is the active outermost main frame of the WebContents.
+    receiver_rfh = receiver_contents->GetMainFrame();
+  } else if (!receiver_document_id.empty()) {
+    ExtensionApiFrameIdMap::DocumentId document_id =
+        ExtensionApiFrameIdMap::DocumentIdFromString(receiver_document_id);
+
+    // Return early for invalid documentIds.
+    if (!document_id)
+      return nullptr;
+
+    receiver_rfh =
+        ExtensionApiFrameIdMap::Get()->GetRenderFrameHostByDocumentId(
+            document_id);
+
+    // If both |document_id| and |receiver_frame_id| are provided they
+    // should find the same RenderFrameHost, if not return early.
+    if (receiver_frame_id != -1 &&
+        ExtensionApiFrameIdMap::GetRenderFrameHostById(
+            receiver_contents, receiver_frame_id) != receiver_rfh) {
+      return nullptr;
+    }
+  } else {
+    DCHECK_GT(receiver_frame_id, -1);
+    receiver_rfh = ExtensionApiFrameIdMap::GetRenderFrameHostById(
+        receiver_contents, receiver_frame_id);
+  }
   if (!receiver_rfh)
     return nullptr;
 
diff --git a/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.h b/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.h
index b5942ac..58f4fda 100644
--- a/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.h
+++ b/chrome/browser/extensions/api/messaging/chrome_messaging_delegate.h
@@ -33,7 +33,8 @@
       const std::string& extension_id,
       const PortId& receiver_port_id,
       content::WebContents* receiver_contents,
-      int receiver_frame_id) override;
+      int receiver_frame_id,
+      const std::string& receiver_document_id) override;
   std::unique_ptr<MessagePort> CreateReceiverForNativeApp(
       content::BrowserContext* browser_context,
       base::WeakPtr<MessagePort::ChannelDelegate> channel_delegate,
diff --git a/chrome/browser/feed/android/BUILD.gn b/chrome/browser/feed/android/BUILD.gn
index 9aeb4da..05032a2 100644
--- a/chrome/browser/feed/android/BUILD.gn
+++ b/chrome/browser/feed/android/BUILD.gn
@@ -324,7 +324,7 @@
     "//chrome/test/android:chrome_java_test_support",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/browser_ui/settings/android:java",
-    "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/theme/android:java_resources",
     "//components/browser_ui/widget/android:java",
     "//components/embedder_support/android:junit_test_support",
     "//components/favicon/android:java",
diff --git a/chrome/browser/lacros/account_manager/web_signin_helper_lacros.cc b/chrome/browser/lacros/account_manager/web_signin_helper_lacros.cc
new file mode 100644
index 0000000..5a223444
--- /dev/null
+++ b/chrome/browser/lacros/account_manager/web_signin_helper_lacros.cc
@@ -0,0 +1,106 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/lacros/account_manager/web_signin_helper_lacros.h"
+
+#include "base/callback.h"
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "chrome/browser/lacros/account_manager/account_manager_util.h"
+#include "chrome/browser/ui/profile_picker.h"
+#include "components/signin/public/base/signin_switches.h"
+#include "components/signin/public/identity_manager/account_info.h"
+
+WebSigninHelperLacros::WebSigninHelperLacros(
+    const base::FilePath& profile_path,
+    AccountProfileMapper* account_profile_mapper,
+    signin::IdentityManager* identity_manager,
+    signin::ConsistencyCookieManager* consistency_cookie_manager,
+    base::OnceClosure callback)
+    : callback_(std::move(callback)),
+      profile_path_(profile_path),
+      account_profile_mapper_(account_profile_mapper),
+      identity_manager_(identity_manager) {
+  if (base::FeatureList::IsEnabled(switches::kLacrosNonSyncingProfiles)) {
+    DCHECK(consistency_cookie_manager);
+    scoped_account_update_ =
+        std::make_unique<signin::ConsistencyCookieManager::ScopedAccountUpdate>(
+            consistency_cookie_manager->CreateScopedAccountUpdate());
+  }
+
+  GetAccountsAvailableAsSecondary(
+      account_profile_mapper, profile_path,
+      base::BindOnce(
+          &WebSigninHelperLacros::OnAccountsAvailableAsSecondaryFetched,
+          weak_factory_.GetWeakPtr()));
+}
+
+WebSigninHelperLacros::~WebSigninHelperLacros() {
+  Finalize();
+}
+
+void WebSigninHelperLacros::OnAccountsAvailableAsSecondaryFetched(
+    const std::vector<account_manager::Account>& accounts) {
+  if (!accounts.empty()) {
+    // Pass in the current profile to signal that the user wants to select a
+    // _secondary_ account for this particular profile.
+    ProfilePicker::Show(ProfilePicker::Params::ForLacrosSelectAvailableAccount(
+        profile_path_, base::BindOnce(&WebSigninHelperLacros::OnAccountPicked,
+                                      weak_factory_.GetWeakPtr())));
+    return;
+  }
+
+  account_profile_mapper_->ShowAddAccountDialog(
+      profile_path_,
+      account_manager::AccountManagerFacade::AccountAdditionSource::
+          kOgbAddAccount,
+      base::BindOnce(&WebSigninHelperLacros::OnAccountAdded,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void WebSigninHelperLacros::OnAccountAdded(
+    const absl::optional<AccountProfileMapper::AddAccountResult>& result) {
+  std::string gaia_id;
+  if (result.has_value() && result->account.key.account_type() ==
+                                account_manager::AccountType::kGaia) {
+    gaia_id = result->account.key.id();
+  }
+  OnAccountPicked(gaia_id);
+}
+
+void WebSigninHelperLacros::OnAccountPicked(const std::string& gaia_id) {
+  if (gaia_id.empty()) {
+    Finalize();
+    return;
+  }
+
+  std::vector<CoreAccountInfo> accounts_in_tokens =
+      identity_manager_->GetAccountsWithRefreshTokens();
+  if (base::Contains(accounts_in_tokens, gaia_id,
+                     [](const CoreAccountInfo& info) { return info.gaia; })) {
+    // Account is already in tokens.
+    Finalize();
+    return;
+  }
+
+  // Wait for account to be in tokens.
+  account_added_to_mapping_ = gaia_id;
+  identity_manager_observervation_.Observe(identity_manager_);
+}
+
+void WebSigninHelperLacros::Finalize() {
+  identity_manager_observervation_.Reset();
+  scoped_account_update_.reset();
+  if (callback_)
+    std::move(callback_).Run();
+  // `this` may be deleted.
+}
+
+void WebSigninHelperLacros::OnRefreshTokenUpdatedForAccount(
+    const CoreAccountInfo& account_info) {
+  if (account_added_to_mapping_ == account_info.gaia) {
+    Finalize();
+    return;
+  }
+}
diff --git a/chrome/browser/lacros/account_manager/web_signin_helper_lacros.h b/chrome/browser/lacros/account_manager/web_signin_helper_lacros.h
new file mode 100644
index 0000000..2c5bb755
--- /dev/null
+++ b/chrome/browser/lacros/account_manager/web_signin_helper_lacros.h
@@ -0,0 +1,86 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_WEB_SIGNIN_HELPER_LACROS_H_
+#define CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_WEB_SIGNIN_HELPER_LACROS_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/lacros/account_manager/account_profile_mapper.h"
+#include "components/signin/core/browser/consistency_cookie_manager.h"
+#include "components/signin/public/identity_manager/account_info.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class AccountProfileMapper;
+
+// Handles the signin flow starting from the web (when receiving the
+// `GAIA_SERVICE_TYPE_ADDSESSION` parameter). Maintains a `ScopedAccountUpdate`
+// for the whole duration of the flow. The steps are:
+// 1) Call `GetAccountsAvailableAsSecondary()` to check whether there are
+//    available accounts in the OS that the user could pick.
+// 2) If there are available accounts in the OS, show the account picker in
+//    Chrome. If the user picks an existing account then skip to step 4.
+// 3) Call `AccountProfileMapper::ShowAddAccountDialog()` to add a new account
+//    to the OS.
+// 4) Once the account is added to the profile, wait until the `Identitymanager`
+//    picks it up.
+class WebSigninHelperLacros : public signin::IdentityManager::Observer {
+ public:
+  WebSigninHelperLacros(
+      const base::FilePath& profile_path,
+      AccountProfileMapper* account_profile_mapper,
+      signin::IdentityManager* identity_manager,
+      signin::ConsistencyCookieManager* consistency_cookie_manager,
+      base::OnceClosure callback);
+
+  ~WebSigninHelperLacros() override;
+
+  WebSigninHelperLacros(const WebSigninHelperLacros&) = delete;
+  WebSigninHelperLacros& operator=(const WebSigninHelperLacros&) = delete;
+
+ private:
+  // Callback for `GetAccountsAvailableAsSecondary()`.
+  void OnAccountsAvailableAsSecondaryFetched(
+      const std::vector<account_manager::Account>& accounts);
+
+  // Callback for `AccountProfileMapper::ShowAddAccountDialog()`.
+  void OnAccountAdded(
+      const absl::optional<AccountProfileMapper::AddAccountResult>& result);
+
+  // Called once the user has picked an account (either from the picker or by
+  // adding it to the OS directly).
+  void OnAccountPicked(const std::string& gaia_id);
+
+  // Unregisters the identity manager observation, releases
+  // the `ScopedAccountUpdate`, and calls `callback_`.
+  void Finalize();
+
+  // signin::IdentityManager::Observer:
+  void OnRefreshTokenUpdatedForAccount(
+      const CoreAccountInfo& account_info) override;
+
+  // Sets the consistency cookie to "Updating".
+  std::unique_ptr<signin::ConsistencyCookieManager::ScopedAccountUpdate>
+      scoped_account_update_;
+
+  base::OnceClosure callback_;
+  base::FilePath profile_path_;
+  AccountProfileMapper* const account_profile_mapper_;
+
+  signin::IdentityManager* const identity_manager_;
+  base::ScopedObservation<signin::IdentityManager,
+                          signin::IdentityManager::Observer>
+      identity_manager_observervation_{this};
+
+  // Gaia ID returned by `AccountProfileMapper::ShowAddAccountDialog()`.
+  std::string account_added_to_mapping_;
+
+  base::WeakPtrFactory<WebSigninHelperLacros> weak_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_WEB_SIGNIN_HELPER_LACROS_H_
diff --git a/chrome/browser/lacros/account_manager/web_signin_helper_lacros_unittest.cc b/chrome/browser/lacros/account_manager/web_signin_helper_lacros_unittest.cc
new file mode 100644
index 0000000..a745cf43
--- /dev/null
+++ b/chrome/browser/lacros/account_manager/web_signin_helper_lacros_unittest.cc
@@ -0,0 +1,273 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/lacros/account_manager/web_signin_helper_lacros.h"
+
+#include <memory>
+#include <string>
+
+#include "base/containers/flat_set.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/lacros/account_manager/account_profile_mapper.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "components/account_manager_core/account.h"
+#include "components/account_manager_core/account_addition_result.h"
+#include "components/account_manager_core/account_manager_facade.h"
+#include "components/account_manager_core/mock_account_manager_facade.h"
+#include "components/signin/core/browser/account_reconcilor.h"
+#include "components/signin/core/browser/account_reconcilor_delegate.h"
+#include "components/signin/core/browser/consistency_cookie_manager.h"
+#include "components/signin/core/browser/mirror_landing_account_reconcilor_delegate.h"
+#include "components/signin/public/base/signin_switches.h"
+#include "components/signin/public/base/test_signin_client.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
+#include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "services/network/test/test_cookie_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class MockCookieManager
+    : public testing::StrictMock<network::TestCookieManager> {
+ public:
+  MOCK_METHOD4(SetCanonicalCookie,
+               void(const net::CanonicalCookie& cookie,
+                    const GURL& source_url,
+                    const net::CookieOptions& cookie_options,
+                    SetCanonicalCookieCallback callback));
+};
+
+class TestAccountReconcilor : public AccountReconcilor {
+ public:
+  TestAccountReconcilor(signin::IdentityManager* identity_manager,
+                        SigninClient* client)
+      : AccountReconcilor(
+            identity_manager,
+            client,
+            std::make_unique<
+                signin::MirrorLandingAccountReconcilorDelegate>()) {}
+
+  void SimulateSetCookiesFinished() {
+    OnSetAccountsInCookieCompleted(signin::SetAccountsInCookieResult::kSuccess);
+  }
+};
+
+}  // namespace
+
+class WebSigninHelperLacrosTest : public testing::Test {
+ public:
+  WebSigninHelperLacrosTest() {
+    CHECK(testing_profile_manager_.SetUp());
+
+    profile_path_ =
+        testing_profile_manager_.profile_manager()->user_data_dir().AppendASCII(
+            "Default");
+    ProfileAttributesInitParams params;
+    params.profile_path = profile_path();
+    params.profile_name = u"ProfileName";
+    storage()->AddProfile(std::move(params));
+
+    // No account in the facade.
+    EXPECT_CALL(mock_facade_, GetAccounts(testing::_))
+        .Times(testing::AtLeast(1))
+        .WillRepeatedly(
+            [](base::OnceCallback<void(
+                   const std::vector<account_manager::Account>&)> callback) {
+              std::move(callback).Run({});
+            });
+
+    testing_profile_manager_.SetAccountProfileMapper(
+        std::make_unique<AccountProfileMapper>(
+            &mock_facade_, storage(),
+            testing_profile_manager_.local_state()->Get()));
+    std::unique_ptr<MockCookieManager> mock_cookie_manager =
+        std::make_unique<MockCookieManager>();
+    cookie_manager_ = mock_cookie_manager.get();
+    signin_client_.set_cookie_manager(std::move(mock_cookie_manager));
+    identity_test_env_.WaitForRefreshTokensLoaded();
+    identity_test_env_.SetCookieAccounts({});
+    reconcilor_.Initialize(/*start_reconcile_if_tokens_available=*/true);
+    WaitForConsistentReconcilorState();
+    ExpectCookieSet("Consistent");
+    consistency_cookie_manager_ =
+        std::make_unique<signin::ConsistencyCookieManager>(&signin_client_,
+                                                           &reconcilor_);
+  }
+
+  ~WebSigninHelperLacrosTest() override { reconcilor_.Shutdown(); }
+
+  // Returns a `WebSigninHelperLacros` instance. The `AccountProfileMapper` and
+  // the `IdentityManager` are not connected to each other, and must be managed
+  // separately.
+  std::unique_ptr<WebSigninHelperLacros> CreateWebSigninHelperLacros(
+      base::OnceClosure closure) {
+    return std::make_unique<WebSigninHelperLacros>(
+        profile_path_,
+        testing_profile_manager_.profile_manager()->GetAccountProfileMapper(),
+        identity_test_env_.identity_manager(),
+        consistency_cookie_manager_.get(), std::move(closure));
+  }
+
+  void ExpectCookieSet(const std::string& value) {
+    EXPECT_CALL(
+        *cookie_manager_,
+        SetCanonicalCookie(
+            testing::AllOf(
+                testing::Property(&net::CanonicalCookie::Name,
+                                  "CHROME_ID_CONSISTENCY_STATE"),
+                testing::Property(&net::CanonicalCookie::Value, value)),
+            GaiaUrls::GetInstance()->gaia_url(), testing::_, testing::_))
+        .Times(1);
+  }
+
+  MockCookieManager* cookie_manager() { return cookie_manager_; }
+
+  account_manager::MockAccountManagerFacade* mock_facade() {
+    return &mock_facade_;
+  }
+
+  const base::FilePath& profile_path() const { return profile_path_; }
+
+  void WaitForConsistentReconcilorState() {
+    while (reconcilor_.GetState() != signin_metrics::ACCOUNT_RECONCILOR_OK)
+      base::RunLoop().RunUntilIdle();
+  }
+
+  void SimulateSetCookiesFinished() {
+    reconcilor_.SimulateSetCookiesFinished();
+  }
+
+  ProfileAttributesStorage* storage() {
+    return &testing_profile_manager_.profile_manager()
+                ->GetProfileAttributesStorage();
+  }
+
+  AccountProfileMapper* mapper() {
+    return testing_profile_manager_.profile_manager()
+        ->GetAccountProfileMapper();
+  }
+
+  signin::IdentityTestEnvironment* identity_test_env() {
+    return &identity_test_env_;
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_{
+      switches::kLacrosNonSyncingProfiles};
+
+  base::test::TaskEnvironment task_environment;
+  account_manager::MockAccountManagerFacade mock_facade_;
+  sync_preferences::TestingPrefServiceSyncable prefs_;
+  base::FilePath profile_path_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
+
+  TestingProfileManager testing_profile_manager_{
+      TestingBrowserProcess::GetGlobal()};
+
+  TestSigninClient signin_client_{&prefs_, &test_url_loader_factory_};
+
+  signin::IdentityTestEnvironment identity_test_env_{
+      /*test_url_loader_factory=*/nullptr, &prefs_,
+      signin::AccountConsistencyMethod::kDisabled, &signin_client_};
+
+  TestAccountReconcilor reconcilor_{identity_test_env_.identity_manager(),
+                                    &signin_client_};
+
+  MockCookieManager* cookie_manager_ = nullptr;  // Owned by `signin_client_`.
+
+  std::unique_ptr<signin::ConsistencyCookieManager> consistency_cookie_manager_;
+};
+
+// Checks that creating a deleting the helper updates the cookie and that the
+// destruction callback is run.
+TEST_F(WebSigninHelperLacrosTest, DeletionCallback) {
+  testing::StrictMock<base::MockOnceClosure> helper_deleted;
+
+  // Create the helper.
+  ExpectCookieSet("Updating");
+  std::unique_ptr<WebSigninHelperLacros> web_signin_helper =
+      CreateWebSigninHelperLacros(helper_deleted.Get());
+
+  // Delete the helper.
+  EXPECT_CALL(helper_deleted, Run).Times(1);
+  ExpectCookieSet("Consistent");
+  web_signin_helper.reset();
+}
+
+// Checks that an account can be added through the OS when there is no available
+// account.
+TEST_F(WebSigninHelperLacrosTest, NoAccountAvailable) {
+  testing::StrictMock<base::MockOnceClosure> helper_complete;
+
+  const std::string email("testemail");
+  std::string gaia_id = signin::GetTestGaiaIdForEmail(email);
+  account_manager::Account new_account{
+      {gaia_id, account_manager::AccountType::kGaia}, email};
+
+  // Create the helper, this should trigger a call to `ShowAddAccountDialog()`.
+  EXPECT_CALL(*mock_facade(),
+              ShowAddAccountDialog(account_manager::AccountManagerFacade::
+                                       AccountAdditionSource::kOgbAddAccount,
+                                   testing::_))
+      .Times(1)
+      .WillRepeatedly(
+          [new_account](
+              account_manager::AccountManagerFacade::AccountAdditionSource,
+              base::OnceCallback<void(
+                  const account_manager::AccountAdditionResult& result)>
+                  callback) {
+            std::move(callback).Run(
+                account_manager::AccountAdditionResult::FromAccount(
+                    new_account));
+          });
+  ExpectCookieSet("Updating");
+  std::unique_ptr<WebSigninHelperLacros> web_signin_helper =
+      CreateWebSigninHelperLacros(helper_complete.Get());
+  testing::Mock::VerifyAndClearExpectations(cookie_manager());
+  testing::Mock::VerifyAndClearExpectations(mock_facade());
+
+  // Simmulate the mapper adding the account to the profile.
+  identity_test_env()->MakeAccountAvailable(email);
+
+  EXPECT_CALL(helper_complete, Run).Times(1);
+
+  // `AccountProfileMapper` expects a call of `OnAccountUpserted()` before
+  // completing the account addition.
+  EXPECT_CALL(*mock_facade(), GetAccounts(testing::_))
+      .Times(testing::AtLeast(1))
+      .WillRepeatedly(
+          [new_account](
+              base::OnceCallback<void(
+                  const std::vector<account_manager::Account>&)> callback) {
+            std::move(callback).Run({new_account});
+          });
+  mapper()->OnAccountUpserted(new_account);
+
+  // Account mapping is complete, check that the account was added to the right
+  // profile.
+  EXPECT_EQ(
+      storage()->GetProfileAttributesWithPath(profile_path())->GetGaiaIds(),
+      base::flat_set<std::string>{gaia_id});
+
+  // The `AccountReconcilor` stops running, cookie is reset.
+  ExpectCookieSet("Consistent");
+  SimulateSetCookiesFinished();
+
+  testing::Mock::VerifyAndClearExpectations(cookie_manager());
+  testing::Mock::VerifyAndClearExpectations(&helper_complete);
+
+  // Delete the helper, nothing happens.
+  web_signin_helper.reset();
+}
diff --git a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewAccessibilityTest.java b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewAccessibilityTest.java
index b1b3010..6205977 100644
--- a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewAccessibilityTest.java
+++ b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewAccessibilityTest.java
@@ -19,7 +19,6 @@
 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.flags.ChromeSwitches;
 import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabService;
 import org.chromium.chrome.browser.tab.Tab;
@@ -60,7 +59,6 @@
      */
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1287526")
     public void smokeTest() throws ExecutionException, TimeoutException {
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
         TabbedPaintPreview tabbedPaintPreview =
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.cc b/chrome/browser/policy/cloud/user_policy_signin_service.cc
index 6a07536..260637b 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.cc
@@ -12,17 +12,24 @@
 #include "base/callback_helpers.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/enterprise/util/managed_browser_utils.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service_internal.h"
+#include "chrome/browser/policy/cloud/user_policy_signin_service_util.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/browser/signin/signin_util.h"
+#include "chrome/common/chrome_content_client.h"
 #include "chrome/common/pref_names.h"
+#include "components/policy/core/browser/cloud/user_policy_signin_service_util.h"
 #include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/identity_manager/account_info.h"
+#include "components/signin/public/identity_manager/primary_account_change_event.h"
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_source.h"
 #include "content/public/browser/storage_partition.h"
@@ -34,6 +41,24 @@
 bool g_force_prohibit_signout_for_tests = false;
 }
 
+ProfileManagerObserverBridge::ProfileManagerObserverBridge(
+    UserPolicySigninService* user_policy_signin_service)
+    : user_policy_signin_service_(user_policy_signin_service) {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  if (profile_manager)
+    profile_manager->AddObserver(this);
+}
+
+void ProfileManagerObserverBridge::OnProfileAdded(Profile* profile) {
+  user_policy_signin_service_->OnProfileReady(profile);
+}
+
+ProfileManagerObserverBridge::~ProfileManagerObserverBridge() {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  if (profile_manager)
+    profile_manager->RemoveObserver(this);
+}
+
 UserPolicySigninService::UserPolicySigninService(
     Profile* profile,
     PrefService* local_state,
@@ -41,16 +66,16 @@
     UserCloudPolicyManager* policy_manager,
     signin::IdentityManager* identity_manager,
     scoped_refptr<network::SharedURLLoaderFactory> system_url_loader_factory)
-    : UserPolicySigninServiceBase(profile,
-                                  local_state,
+    : UserPolicySigninServiceBase(local_state,
                                   device_management_service,
                                   policy_manager,
                                   identity_manager,
-                                  system_url_loader_factory) {
+                                  system_url_loader_factory),
+      profile_(profile) {
   // IdentityManager should not yet have loaded its tokens since this
   // happens in the background after PKS initialization - so this service
   // should always be created before the oauth token is available.
-  DCHECK(!CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true));
+  DCHECK(!CanApplyPolicies(/*check_for_refresh_token=*/true));
   // Some tests don't have a profile manager.
   if (g_browser_process->profile_manager()) {
     observed_profile_.Observe(
@@ -66,9 +91,8 @@
   // in the destructor because we want to shutdown the registration helper
   // before UserCloudPolicyManager shuts down the CloudPolicyClient.
   registration_helper_.reset();
-  if (g_browser_process->profile_manager()) {
+  if (g_browser_process->profile_manager())
     observed_profile_.Reset();
-  }
 
   UserPolicySigninServiceBase::PrepareForUserCloudPolicyManagerShutdown();
 }
@@ -116,17 +140,19 @@
 
 void UserPolicySigninService::OnPrimaryAccountChanged(
     const signin::PrimaryAccountChangeEvent& event) {
-  UserPolicySigninServiceBase::OnPrimaryAccountChanged(event);
-
-  if (event.GetEventTypeFor(signin::ConsentLevel::kSync) !=
-          signin::PrimaryAccountChangeEvent::Type::kSet &&
-      event.GetEventTypeFor(signin::ConsentLevel::kSignin) !=
-          signin::PrimaryAccountChangeEvent::Type::kSet) {
-    return;
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  if (profile_manager && IsSignoutEvent(event)) {
+    UpdateProfileAttributesWhenSignout(profile_, profile_manager);
+    ShutdownUserCloudPolicyManager();
+  } else if (IsTurnOffSyncEvent(event)) {
+    ShutdownUserCloudPolicyManager();
   }
 
+  if (!IsAnySigninEvent(event))
+    return;
+
   DCHECK(identity_manager()->HasPrimaryAccount(consent_level()));
-  if (!CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true))
+  if (!CanApplyPolicies(/*check_for_refresh_token=*/true))
     return;
 
   // IdentityManager has a refresh token for the primary account, so initialize
@@ -139,7 +165,7 @@
   // Ignore OAuth tokens or those for any account but the primary one.
   if (account_info.account_id !=
           identity_manager()->GetPrimaryAccountId(consent_level()) ||
-      !CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true)) {
+      !CanApplyPolicies(/*check_for_refresh_token=*/true)) {
     return;
   }
 
@@ -149,7 +175,7 @@
 }
 
 void UserPolicySigninService::TryInitializeForSignedInUser() {
-  DCHECK(CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true));
+  DCHECK(CanApplyPolicies(/*check_for_refresh_token=*/true));
 
   // If using a TestingProfile with no UserCloudPolicyManager, skip
   // initialization.
@@ -163,8 +189,7 @@
   InitializeForSignedInUser(
       AccountIdFromAccountInfo(
           identity_manager()->GetPrimaryAccountInfo(consent_level())),
-      profile()
-          ->GetDefaultStoragePartition()
+      profile_->GetDefaultStoragePartition()
           ->GetURLLoaderFactoryForBrowserProcess());
 }
 
@@ -176,18 +201,24 @@
   ProhibitSignoutIfNeeded();
 }
 
+void UserPolicySigninService::Shutdown() {
+  if (identity_manager())
+    identity_manager()->RemoveObserver(this);
+  UserPolicySigninServiceBase::Shutdown();
+}
+
 void UserPolicySigninService::ShutdownUserCloudPolicyManager() {
   UserCloudPolicyManager* manager = policy_manager();
   // Allow the user to signout again.
   if (manager)
-    signin_util::SetUserSignoutAllowedForProfile(profile(), true);
+    signin_util::SetUserSignoutAllowedForProfile(profile_, true);
 
   UserPolicySigninServiceBase::ShutdownUserCloudPolicyManager();
 }
 
 void UserPolicySigninService::OnProfileUserManagementAcceptanceChanged(
     const base::FilePath& profile_path) {
-  if (CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true))
+  if (CanApplyPolicies(/*check_for_refresh_token=*/true))
     TryInitializeForSignedInUser();
 }
 
@@ -200,7 +231,7 @@
   DVLOG_IF(1, manager->IsClientRegistered())
       << "Client already registered - not fetching DMToken";
   if (!manager->IsClientRegistered()) {
-    if (!CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true)) {
+    if (!CanApplyPolicies(/*check_for_refresh_token=*/true)) {
       // No token yet - this class listens for OnRefreshTokenUpdatedForAccount()
       // and will re-attempt registration once the token is available.
       DLOG(WARNING) << "No OAuth Refresh Token - delaying policy download";
@@ -240,8 +271,50 @@
   if (policy_manager()->IsClientRegistered() ||
       internal::g_force_prohibit_signout_for_tests) {
     DVLOG(1) << "User is registered for policy - prohibiting signout";
-    signin_util::SetUserSignoutAllowedForProfile(profile(), false);
+    signin_util::SetUserSignoutAllowedForProfile(profile_, false);
   }
 }
 
+void UserPolicySigninService::OnProfileReady(Profile* profile) {
+  if (profile && profile == profile_)
+    InitializeOnProfileReady(profile);
+}
+
+void UserPolicySigninService::InitializeOnProfileReady(Profile* profile) {
+  DCHECK_EQ(profile, profile_);
+
+  // If using a TestingProfile with no IdentityManager or
+  // UserCloudPolicyManager, skip initialization.
+  if (!policy_manager() || !identity_manager()) {
+    DVLOG(1) << "Skipping initialization for tests due to missing components.";
+    return;
+  }
+
+  // Shutdown the UserCloudPolicyManager when the user signs out. We start
+  // observing the IdentityManager here because we don't want to get signout
+  // notifications until after the profile has started initializing
+  // (http://crbug.com/316229).
+  identity_manager()->AddObserver(this);
+
+  AccountId account_id = AccountIdFromAccountInfo(
+      identity_manager()->GetPrimaryAccountInfo(consent_level()));
+  if (!CanApplyPolicies(/*check_for_refresh_token=*/false)) {
+    ShutdownUserCloudPolicyManager();
+  } else {
+    InitializeForSignedInUser(account_id,
+                              profile->GetDefaultStoragePartition()
+                                  ->GetURLLoaderFactoryForBrowserProcess());
+  }
+}
+
+bool UserPolicySigninService::CanApplyPolicies(bool check_for_refresh_token) {
+  if (!CanApplyPoliciesForSignedInUser(check_for_refresh_token,
+                                       identity_manager())) {
+    return false;
+  }
+
+  return (profile_can_be_managed_for_testing_ ||
+          chrome::enterprise_util::ProfileCanBeManaged(profile_));
+}
+
 }  // namespace policy
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.h b/chrome/browser/policy/cloud/user_policy_signin_service.h
index 08f96d1..2ef08e6 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.h
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.h
@@ -10,8 +10,9 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/scoped_observation.h"
-#include "chrome/browser/policy/cloud/user_policy_signin_service_base.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager_observer.h"
+#include "components/policy/core/browser/cloud/user_policy_signin_service_base.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
@@ -26,10 +27,31 @@
 
 class CloudPolicyClientRegistrationHelper;
 
+class UserPolicySigninService;
+
+// Observer bridge for UserPolicySigninService to observe profile manager
+// events.
+class ProfileManagerObserverBridge : public ProfileManagerObserver {
+ public:
+  explicit ProfileManagerObserverBridge(
+      UserPolicySigninService* user_policy_signin_service);
+  ProfileManagerObserverBridge(const ProfileManagerObserverBridge&) = delete;
+  ProfileManagerObserverBridge& operator=(const ProfileManagerObserverBridge&) =
+      delete;
+  ~ProfileManagerObserverBridge() override;
+
+  // ProfileManagerObserver implementation:
+  void OnProfileAdded(Profile* profile) override;
+
+ private:
+  UserPolicySigninService* user_policy_signin_service_;
+};
+
 // A specialization of the UserPolicySigninServiceBase for the desktop
 // platforms (Windows, Mac and Linux).
 class UserPolicySigninService : public UserPolicySigninServiceBase,
-                                public ProfileAttributesStorage::Observer {
+                                public ProfileAttributesStorage::Observer,
+                                public signin::IdentityManager::Observer {
  public:
   // Creates a UserPolicySigninService associated with the passed
   // |policy_manager| and |identity_manager|.
@@ -54,7 +76,6 @@
       PolicyRegistrationCallback callback);
 
   // signin::IdentityManager::Observer implementation:
-  // UserPolicySigninServiceBase is already an observer of IdentityManager.
   void OnPrimaryAccountChanged(
       const signin::PrimaryAccountChangeEvent& event_details) override;
   void OnRefreshTokenUpdatedForAccount(
@@ -71,6 +92,13 @@
   void OnProfileUserManagementAcceptanceChanged(
       const base::FilePath& profile_path) override;
 
+  // Handler for when the profile is ready.
+  void OnProfileReady(Profile* profile);
+
+  void set_profile_can_be_managed_for_testing(bool can_be_managed) {
+    profile_can_be_managed_for_testing_ = can_be_managed;
+  }
+
  protected:
   // UserPolicySigninServiceBase implementation:
   void InitializeUserCloudPolicyManager(
@@ -80,6 +108,9 @@
   void PrepareForUserCloudPolicyManagerShutdown() override;
 
  private:
+  // KeyedService implementation:
+  void Shutdown() override;
+
   // Fetches an OAuth token to allow the cloud policy service to register with
   // the cloud policy server. |oauth_login_token| should contain an OAuth login
   // refresh token that can be downscoped to get an access token for the
@@ -102,7 +133,29 @@
   void CallPolicyRegistrationCallback(std::unique_ptr<CloudPolicyClient> client,
                                       PolicyRegistrationCallback callback);
 
+  // Initializes the UserPolicySigninService once its owning Profile becomes
+  // ready. If the Profile has a signed-in account associated with it at startup
+  // then this initializes the cloud policy manager by calling
+  // InitializeForSignedInUser(); otherwise it clears any stored policies.
+  void InitializeOnProfileReady(Profile* profile);
+
+  // Returns true when policies can be applied for the profile. The profile has
+  // to be at least tied to an account.
+  bool CanApplyPolicies(bool check_for_refresh_token);
+
+  // True when the profile can be managed for testing purpose. Has to be set
+  // from the test fixture. This is used to bypass the check on the profile
+  // attributes entry.
+  bool profile_can_be_managed_for_testing_ = false;
+
   std::unique_ptr<CloudPolicyClientRegistrationHelper> registration_helper_;
+
+  // Parent profile for this service.
+  raw_ptr<Profile> profile_;
+
+  // Observer bridge for profile added events.
+  ProfileManagerObserverBridge profile_manager_observer_bridge_{this};
+
   base::ScopedObservation<ProfileAttributesStorage,
                           ProfileAttributesStorage::Observer>
       observed_profile_{this};
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_mobile.cc b/chrome/browser/policy/cloud/user_policy_signin_service_mobile.cc
index bf04a68..6193a21 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_mobile.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_mobile.cc
@@ -15,14 +15,26 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/enterprise/util/managed_browser_utils.h"
+#include "chrome/browser/policy/cloud/user_policy_signin_service_util.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/signin/account_id_from_account_info.h"
+#include "chrome/common/chrome_content_client.h"
 #include "chrome/common/pref_names.h"
+#include "components/policy/core/browser/cloud/user_policy_signin_service_util.h"
 #include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
 #include "components/policy/core/common/policy_switches.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/primary_account_change_event.h"
+#include "content/public/browser/storage_partition.h"
 #include "net/base/network_change_notifier.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -48,13 +60,16 @@
     UserCloudPolicyManager* policy_manager,
     signin::IdentityManager* identity_manager,
     scoped_refptr<network::SharedURLLoaderFactory> system_url_loader_factory)
-    : UserPolicySigninServiceBase(profile,
-                                  local_state,
+    : UserPolicySigninServiceBase(local_state,
                                   device_management_service,
                                   policy_manager,
                                   identity_manager,
                                   system_url_loader_factory),
-      profile_prefs_(profile->GetPrefs()) {}
+      profile_prefs_(profile->GetPrefs()),
+      profile_(profile) {
+  if (g_browser_process->profile_manager())
+    g_browser_process->profile_manager()->AddObserver(this);
+}
 
 UserPolicySigninService::~UserPolicySigninService() {}
 
@@ -99,6 +114,11 @@
 }
 
 void UserPolicySigninService::Shutdown() {
+  // Don't handle ProfileManager when testing because it is null.
+  if (g_browser_process->profile_manager())
+    g_browser_process->profile_manager()->RemoveObserver(this);
+  if (identity_manager())
+    identity_manager()->RemoveObserver(this);
   CancelPendingRegistration();
   UserPolicySigninServiceBase::Shutdown();
 }
@@ -171,4 +191,57 @@
   registration_helper_.reset();
 }
 
+void UserPolicySigninService::OnPrimaryAccountChanged(
+    const signin::PrimaryAccountChangeEvent& event) {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  if (profile_manager && IsSignoutEvent(event)) {
+    UpdateProfileAttributesWhenSignout(profile_, profile_manager);
+    ShutdownUserCloudPolicyManager();
+  } else if (IsTurnOffSyncEvent(event)) {
+    ShutdownUserCloudPolicyManager();
+  }
+}
+
+void UserPolicySigninService::OnProfileAdded(Profile* profile) {
+  if (profile && profile == profile_)
+    InitializeOnProfileReady(profile);
+}
+
+void UserPolicySigninService::InitializeOnProfileReady(Profile* profile) {
+  DCHECK_EQ(profile, profile_);
+
+  // If using a TestingProfile with no IdentityManager or
+  // UserCloudPolicyManager, skip initialization.
+  if (!policy_manager() || !identity_manager()) {
+    DVLOG(1) << "Skipping initialization for tests due to missing components.";
+    return;
+  }
+
+  // Shutdown the UserCloudPolicyManager when the user signs out. We start
+  // observing the IdentityManager here because we don't want to get signout
+  // notifications until after the profile has started initializing
+  // (http://crbug.com/316229).
+  identity_manager()->AddObserver(this);
+
+  AccountId account_id = AccountIdFromAccountInfo(
+      identity_manager()->GetPrimaryAccountInfo(consent_level()));
+  if (!CanApplyPolicies(/*check_for_refresh_token=*/false)) {
+    ShutdownUserCloudPolicyManager();
+  } else {
+    InitializeForSignedInUser(account_id,
+                              profile->GetDefaultStoragePartition()
+                                  ->GetURLLoaderFactoryForBrowserProcess());
+  }
+}
+
+bool UserPolicySigninService::CanApplyPolicies(bool check_for_refresh_token) {
+  if (!CanApplyPoliciesForSignedInUser(check_for_refresh_token,
+                                       identity_manager())) {
+    return false;
+  }
+
+  return (profile_can_be_managed_for_testing_ ||
+          chrome::enterprise_util::ProfileCanBeManaged(profile_));
+}
+
 }  // namespace policy
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_mobile.h b/chrome/browser/policy/cloud/user_policy_signin_service_mobile.h
index 7c7f162..796c303 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_mobile.h
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_mobile.h
@@ -13,7 +13,9 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "build/build_config.h"
-#include "chrome/browser/policy/cloud/user_policy_signin_service_base.h"
+#include "chrome/browser/profiles/profile_manager_observer.h"
+#include "components/policy/core/browser/cloud/user_policy_signin_service_base.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 
 class Profile;
 
@@ -29,8 +31,10 @@
 
 class CloudPolicyClientRegistrationHelper;
 
-// A specialization of UserPolicySigninServiceBase for the Android.
-class UserPolicySigninService : public UserPolicySigninServiceBase {
+// A specialization of UserPolicySigninServiceBase for Android.
+class UserPolicySigninService : public UserPolicySigninServiceBase,
+                                public ProfileManagerObserver,
+                                public signin::IdentityManager::Observer {
  public:
   // Creates a UserPolicySigninService associated with the passed |profile|.
   UserPolicySigninService(
@@ -55,10 +59,21 @@
                                       const CoreAccountId& account_id,
                                       PolicyRegistrationCallback callback);
 
-  // Overridden from UserPolicySigninServiceBase to cancel the pending delayed
-  // registration.
+  // Overridden from UserPolicySigninServiceForProfile to cancel the pending
+  // delayed registration.
   void ShutdownUserCloudPolicyManager() override;
 
+  // signin::IdentityManager::Observer implementation:
+  void OnPrimaryAccountChanged(
+      const signin::PrimaryAccountChangeEvent& event) override;
+
+  // ProfileManagerObserver implementation.
+  void OnProfileAdded(Profile* profile) override;
+
+  void set_profile_can_be_managed_for_testing(bool can_be_managed) {
+    profile_can_be_managed_for_testing_ = can_be_managed;
+  }
+
  private:
   void CallPolicyRegistrationCallback(std::unique_ptr<CloudPolicyClient> client,
                                       PolicyRegistrationCallback callback);
@@ -77,11 +92,29 @@
 
   void OnRegistrationDone();
 
+  // Initializes the UserPolicySigninService once its owning Profile becomes
+  // ready. If the Profile has a signed-in account associated with it at startup
+  // then this initializes the cloud policy manager by calling
+  // InitializeForSignedInUser(); otherwise it clears any stored policies.
+  void InitializeOnProfileReady(Profile* profile);
+
+  // Returns true when policies can be applied for the profile. The profile has
+  // to be at least tied to an account.
+  bool CanApplyPolicies(bool check_for_refresh_token);
+
+  // True when the profile can be managed for testing purpose. Has to be set
+  // from the test fixture. This is used to bypass the check on the profile
+  // attributes entry.
+  bool profile_can_be_managed_for_testing_ = false;
+
   std::unique_ptr<CloudPolicyClientRegistrationHelper> registration_helper_;
 
   // The PrefService associated with the profile.
   raw_ptr<PrefService> profile_prefs_;
 
+  // Parent profile for this service.
+  raw_ptr<Profile> profile_;
+
   base::WeakPtrFactory<UserPolicySigninService> weak_factory_{this};
 };
 
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc b/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
index 30f4816..b9cda1a 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
@@ -195,10 +195,13 @@
     EXPECT_CALL(*mock_store_, Clear());
 
     // Let the SigninService know that the profile has been created.
-    content::NotificationService::current()->Notify(
-        chrome::NOTIFICATION_PROFILE_ADDED,
-        content::Source<Profile>(profile_.get()),
-        content::NotificationService::NoDetails());
+#if BUILDFLAG(IS_ANDROID)
+    UserPolicySigninServiceFactory::GetForProfile(profile_.get())
+        ->OnProfileAdded(profile_.get());
+#else
+    UserPolicySigninServiceFactory::GetForProfile(profile_.get())
+        ->OnProfileReady(profile_.get());
+#endif  // BUILDFLAG(IS_ANDROID)
   }
 
   bool IsRequestActive() {
@@ -322,10 +325,13 @@
                                            signin::ConsentLevel::kSync);
 
     // Let the SigninService know that the profile has been created.
-    content::NotificationService::current()->Notify(
-        chrome::NOTIFICATION_PROFILE_ADDED,
-        content::Source<Profile>(profile_.get()),
-        content::NotificationService::NoDetails());
+#if BUILDFLAG(IS_ANDROID)
+    UserPolicySigninServiceFactory::GetForProfile(profile_.get())
+        ->OnProfileAdded(profile_.get());
+#else
+    UserPolicySigninServiceFactory::GetForProfile(profile_.get())
+        ->OnProfileReady(profile_.get());
+#endif  // BUILDFLAG(IS_ANDROID)
   }
 };
 
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_util.cc b/chrome/browser/policy/cloud/user_policy_signin_service_util.cc
new file mode 100644
index 0000000..42e0546c
--- /dev/null
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_util.cc
@@ -0,0 +1,29 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/policy/cloud/user_policy_signin_service_util.h"
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
+
+namespace policy {
+
+void UpdateProfileAttributesWhenSignout(Profile* profile,
+                                        ProfileManager* profile_manager) {
+  if (!profile_manager) {
+    // Skip the update there isn't a profile manager which can happen when
+    // testing.
+    return;
+  }
+
+  ProfileAttributesEntry* entry =
+      profile_manager->GetProfileAttributesStorage()
+          .GetProfileAttributesWithPath(profile->GetPath());
+  if (entry)
+    entry->SetUserAcceptedAccountManagement(false);
+}
+
+}  // namespace policy
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_util.h b/chrome/browser/policy/cloud/user_policy_signin_service_util.h
new file mode 100644
index 0000000..180ee095
--- /dev/null
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_util.h
@@ -0,0 +1,19 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
+#define CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
+
+class Profile;
+class ProfileManager;
+
+namespace policy {
+
+// Update the Profile attributes for when the account is signed out.
+void UpdateProfileAttributesWhenSignout(Profile* profile,
+                                        ProfileManager* profile_manager);
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
diff --git a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
index 16c4f76..2c075d92 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_handler_impl_unittest.cc
@@ -324,7 +324,12 @@
   auto test_records = BuildTestRecordsVector(kNumTestRecords, kGenerationId);
   const auto force_confirm_by_server = force_confirm();
 
-  EXPECT_CALL(*client_, UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
+  EXPECT_CALL(
+      *client_,
+      UploadEncryptedReport(
+          IsDataUploadRequestValid(DataUploadRequestValidityMatcher::Settings()
+                                       .SetCheckRecordDetails(false)),
+          _, _))
       .WillOnce(WithArgs<0, 2>(
           Invoke([&force_confirm_by_server](
                      base::Value::Dict request,
@@ -399,8 +404,8 @@
               FailedResponseFromRequest(request, response);
               std::move(callback).Run(std::move(response));
             })));
-    EXPECT_CALL(*client_,
-                UploadEncryptedReport(IsDataUploadRequestValid(), _, _))
+    // TODO(b/214040998: Add a matcher for the gap upload)
+    EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
         .WillOnce(WithArgs<0, 2>(
             Invoke([&force_confirm_by_server](
                        base::Value::Dict request,
diff --git a/chrome/browser/policy/messaging_layer/util/test.cc b/chrome/browser/policy/messaging_layer/util/test.cc
index f5e81fca..76a8ca1 100644
--- a/chrome/browser/policy/messaging_layer/util/test.cc
+++ b/chrome/browser/policy/messaging_layer/util/test.cc
@@ -9,12 +9,85 @@
 
 namespace reporting {
 
+// Return true if s a properly formatted positive integer, i.e., is not empty,
+// contains digits only and does not start with 0.
+static bool IsPositiveInteger(base::StringPiece s) {
+  if (s.empty()) {
+    return false;
+  } else if (s.size() == 1) {
+    return std::isdigit(s[0]);
+  } else {
+    return s[0] != '0' &&
+           s.find_first_not_of("0123456789") == std::string::npos;
+  }
+}
+
+DataUploadRequestValidityMatcher::Settings&
+DataUploadRequestValidityMatcher::Settings::SetCheckRecordDetails(bool flag) {
+  check_record_details_ = flag;
+  return *this;
+}
+
 DataUploadRequestValidityMatcher::Settings&
 DataUploadRequestValidityMatcher::Settings::SetCheckEncryptedRecord(bool flag) {
   check_encrypted_record_ = flag;
   return *this;
 }
 
+bool DataUploadRequestValidityMatcher::CheckRecord(
+    const base::Value& record,
+    MatchResultListener* listener) const {
+  const auto* record_dict = record.GetIfDict();
+  if (record_dict == nullptr) {
+    *listener << "Record " << record << " is not a dict.";
+    return false;
+  }
+
+  if (record_dict->FindString("encryptedWrappedRecord") == nullptr) {
+    *listener << "No key named \"encryptedWrappedRecord\" or the value "
+                 "is not a string in record "
+              << record << '.';
+    return false;
+  }
+
+  {  // sequence information
+    const auto* sequence_information =
+        record_dict->FindDict("sequenceInformation");
+    if (sequence_information == nullptr) {
+      *listener << "No key named \"sequenceInformation\" or the value is "
+                   "not a dict in record "
+                << record << '.';
+      return false;
+    }
+
+    if (!sequence_information->FindInt("priority").has_value()) {
+      *listener << "No key named \"sequenceInformation/priority\" or the "
+                   "value is not an integer in record "
+                << record << '.';
+      return false;
+    }
+
+    for (const char* id : {"sequencingId", "generationId"}) {
+      const auto* id_val = sequence_information->FindString(id);
+      if (id_val == nullptr) {
+        *listener << "No key named \"sequenceInformation/" << id
+                  << "\" or the value is not a string in record " << record
+                  << '.';
+        return false;
+      }
+      if (!IsPositiveInteger(*id_val)) {
+        *listener
+            << "The value of \"sequenceInformation/" << id
+            << "\" is not a properly formatted positive integer in record "
+            << record << '.';
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
 DataUploadRequestValidityMatcher::DataUploadRequestValidityMatcher(
     const Settings& settings)
     : settings_(settings) {}
@@ -22,21 +95,29 @@
 bool DataUploadRequestValidityMatcher::MatchAndExplain(
     const base::Value::Dict& arg,
     MatchResultListener* listener) const {
-  if (settings_.check_encrypted_record_ &&
-      arg.FindList("encryptedRecord") == nullptr) {
-    *listener
-        << "No key named \"encryptedRecord\" in the argument or the value is "
-           "not a list.";
-    return false;
+  if (settings_.check_encrypted_record_) {
+    const auto* record_list = arg.FindList("encryptedRecord");
+    if (record_list == nullptr) {
+      *listener
+          << "No key named \"encryptedRecord\" in the argument or the value is "
+             "not a list.";
+      return false;
+    }
+
+    // examine each record
+    if (settings_.check_record_details_) {
+      for (const auto& record : *record_list) {
+        if (!CheckRecord(record, listener)) {
+          return false;
+        }
+      }
+    }
   }
-  // TODO: Check the validity of each record based on whether they have required
-  // fields and types.
 
   const auto* request_id = arg.FindString("requestId");
   if (request_id == nullptr) {
-    *listener
-        << "No key named \"requestId\" in the argument or the value is not a "
-           "string.";
+    *listener << "No key named \"requestId\" in the argument or the value "
+                 "is not a string.";
     return false;
   }
   if (request_id->empty()) {
@@ -81,9 +162,8 @@
     MatchResultListener* listener) const {
   const auto* record_list = arg.FindList("encryptedRecord");
   if (record_list == nullptr) {
-    *listener
-        << "No key named \"encryptedRecord\" in the argument or the value is "
-           "not a list.";
+    *listener << "No key named \"encryptedRecord\" in the argument or the "
+                 "value is not a list.";
     return false;
   }
 
@@ -107,8 +187,8 @@
     }
 
     // Match each key and value of matched_record with those of the iterated
-    // record_dict. In this way, users can specify part of a record instead of
-    // the full record.
+    // record_dict. In this way, users can specify part of a record instead
+    // of the full record.
     if (IsSubDict(*matched_record_dict, *record_dict)) {
       return true;
     }
diff --git a/chrome/browser/policy/messaging_layer/util/test.h b/chrome/browser/policy/messaging_layer/util/test.h
index a9bbbcd..5153998f 100644
--- a/chrome/browser/policy/messaging_layer/util/test.h
+++ b/chrome/browser/policy/messaging_layer/util/test.h
@@ -28,12 +28,17 @@
     Settings(const Settings& other) = default;
     Settings(Settings&& other) = default;
     // Enable or disable checking the existence of key "encryptedRecord" and
-    // that the value is a list.
+    // that the value is a list. If disabled, implies the effect of
+    // SetCheckRecordDetails(false).
     Settings& SetCheckEncryptedRecord(bool flag);
+    // Enable or disable checking the details of each record. Useful to disable
+    // if the test case includes intentionally malformed records.
+    Settings& SetCheckRecordDetails(bool flag);
 
    private:
     friend DataUploadRequestValidityMatcher;
     bool check_encrypted_record_ = true;
+    bool check_record_details_ = true;
   };
 
   DataUploadRequestValidityMatcher() = default;
@@ -45,6 +50,10 @@
 
  private:
   const Settings settings_{};
+  // Check the validity of the specified record. Return true if the record is
+  // valid.
+  bool CheckRecord(const base::Value& record,
+                   MatchResultListener* listener) const;
 };
 
 class RequestContainingRecordMatcher {
diff --git a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxBridgeTest.java b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxBridgeTest.java
index 5dd29797..0b1bb3b 100644
--- a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxBridgeTest.java
+++ b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxBridgeTest.java
@@ -28,7 +28,8 @@
  * Tests for PrivacySandboxBridge.
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+        "enable-features=PrivacySandboxSettings3:show-sample-data/true"})
 @Batch(Batch.PER_CLASS)
 public class PrivacySandboxBridgeTest {
     @ClassRule
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
index b60c918..652c62d6 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service.cc
@@ -725,27 +725,31 @@
 std::vector<privacy_sandbox::CanonicalTopic>
 PrivacySandboxService::GetCurrentTopTopics() const {
   // TODO(crbug.com/1286276): Add proper Topics implementation.
-  return std::vector<privacy_sandbox::CanonicalTopic>(
-      fake_current_topics_.begin(), fake_current_topics_.end());
+  if (privacy_sandbox::kPrivacySandboxSettings3ShowSampleDataForTesting.Get())
+    return {fake_current_topics_.begin(), fake_current_topics_.end()};
+  return {};
 }
 
 std::vector<privacy_sandbox::CanonicalTopic>
 PrivacySandboxService::GetBlockedTopics() const {
   // TODO(crbug.com/1286276): Add proper Topics implementation.
-  return std::vector<privacy_sandbox::CanonicalTopic>(
-      fake_blocked_topics_.begin(), fake_blocked_topics_.end());
+  if (privacy_sandbox::kPrivacySandboxSettings3ShowSampleDataForTesting.Get())
+    return {fake_blocked_topics_.begin(), fake_blocked_topics_.end()};
+  return {};
 }
 
 void PrivacySandboxService::SetTopicAllowed(
     privacy_sandbox::CanonicalTopic topic,
     bool allowed) {
   // TODO(crbug.com/1286276): Update preferences.
-  if (allowed) {
-    fake_current_topics_.insert(topic);
-    fake_blocked_topics_.erase(topic);
-  } else {
-    fake_current_topics_.erase(topic);
-    fake_blocked_topics_.insert(topic);
+  if (privacy_sandbox::kPrivacySandboxSettings3ShowSampleDataForTesting.Get()) {
+    if (allowed) {
+      fake_current_topics_.insert(topic);
+      fake_blocked_topics_.erase(topic);
+    } else {
+      fake_current_topics_.erase(topic);
+      fake_blocked_topics_.insert(topic);
+    }
   }
 }
 
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
index 2e70cf9..e6ff02f 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_unittest.cc
@@ -1416,6 +1416,36 @@
   EXPECT_FALSE(prefs()->GetBoolean(prefs::kPrivacySandboxApisEnabledV2));
 }
 
+TEST_F(PrivacySandboxServiceTest, TestNoFakeTopics) {
+  auto* service = privacy_sandbox_service();
+  EXPECT_THAT(service->GetCurrentTopTopics(), testing::IsEmpty());
+  EXPECT_THAT(service->GetBlockedTopics(), testing::IsEmpty());
+}
+
+TEST_F(PrivacySandboxServiceTest, TestFakeTopics) {
+  feature_list()->Reset();
+  feature_list()->InitAndEnableFeatureWithParameters(
+      privacy_sandbox::kPrivacySandboxSettings3,
+      {{privacy_sandbox::kPrivacySandboxSettings3ShowSampleDataForTesting.name,
+        "true"}});
+  CanonicalTopic topic1(Topic(1), CanonicalTopic::AVAILABLE_TAXONOMY);
+  CanonicalTopic topic2(Topic(2), CanonicalTopic::AVAILABLE_TAXONOMY);
+  CanonicalTopic topic3(Topic(3), CanonicalTopic::AVAILABLE_TAXONOMY);
+  CanonicalTopic topic4(Topic(4), CanonicalTopic::AVAILABLE_TAXONOMY);
+
+  auto* service = privacy_sandbox_service();
+  EXPECT_THAT(service->GetCurrentTopTopics(), ElementsAre(topic1, topic2));
+  EXPECT_THAT(service->GetBlockedTopics(), ElementsAre(topic3, topic4));
+
+  service->SetTopicAllowed(topic1, false);
+  EXPECT_THAT(service->GetCurrentTopTopics(), ElementsAre(topic2));
+  EXPECT_THAT(service->GetBlockedTopics(), ElementsAre(topic1, topic3, topic4));
+
+  service->SetTopicAllowed(topic4, true);
+  EXPECT_THAT(service->GetCurrentTopTopics(), ElementsAre(topic2, topic4));
+  EXPECT_THAT(service->GetBlockedTopics(), ElementsAre(topic1, topic3));
+}
+
 class PrivacySandboxServiceTestReconciliationBlocked
     : public PrivacySandboxServiceTest {
  public:
@@ -2117,25 +2147,6 @@
   histograms.ExpectTotalCount(histogram_name, 0);
 }
 
-TEST_F(PrivacySandboxServiceTestNonRegularProfile, TestFakeTopics) {
-  CanonicalTopic topic1(Topic(1), CanonicalTopic::AVAILABLE_TAXONOMY);
-  CanonicalTopic topic2(Topic(2), CanonicalTopic::AVAILABLE_TAXONOMY);
-  CanonicalTopic topic3(Topic(3), CanonicalTopic::AVAILABLE_TAXONOMY);
-  CanonicalTopic topic4(Topic(4), CanonicalTopic::AVAILABLE_TAXONOMY);
-
-  auto* service = privacy_sandbox_service();
-  EXPECT_THAT(service->GetCurrentTopTopics(), ElementsAre(topic1, topic2));
-  EXPECT_THAT(service->GetBlockedTopics(), ElementsAre(topic3, topic4));
-
-  service->SetTopicAllowed(topic1, false);
-  EXPECT_THAT(service->GetCurrentTopTopics(), ElementsAre(topic2));
-  EXPECT_THAT(service->GetBlockedTopics(), ElementsAre(topic1, topic3, topic4));
-
-  service->SetTopicAllowed(topic4, true);
-  EXPECT_THAT(service->GetCurrentTopTopics(), ElementsAre(topic2, topic4));
-  EXPECT_THAT(service->GetBlockedTopics(), ElementsAre(topic1, topic3));
-}
-
 TEST_F(PrivacySandboxServiceTestNonRegularProfile, NoDialogRequired) {
   // Non-regular profiles should never have a dialog shown.
   SetupDialogTestState(feature_list(), prefs(),
diff --git a/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js b/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
index b50798a8..88d5aa8b 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
+++ b/chrome/browser/resources/chromeos/login/screens/common/gaia_signin.js
@@ -537,8 +537,6 @@
    * @param {Object} data Screen init payload
    */
   onBeforeShow(data) {
-    chrome.send('loginUIStateChanged', ['gaia-signin', true]);
-
     // Re-enable navigation in case it was disabled before refresh.
     this.navigationEnabled_ = true;
 
@@ -586,7 +584,6 @@
    * Event handler that is invoked just before the screen is hidden.
    */
   onBeforeHide() {
-    chrome.send('loginUIStateChanged', ['gaia-signin', false]);
     this.isShown_ = false;
   }
 
diff --git a/chrome/browser/resources/feedback_webui/feedback_resources.grd b/chrome/browser/resources/feedback_webui/feedback_resources.grd
index e9f4bca..4c2b035d 100644
--- a/chrome/browser/resources/feedback_webui/feedback_resources.grd
+++ b/chrome/browser/resources/feedback_webui/feedback_resources.grd
@@ -32,7 +32,7 @@
                file="images/2x/button_butter_bar_close_hover.png" type="BINDATA" />
       <include name="IDR_FEEDBACK_BUTTON_BUTTER_BAR_CLOSE_PRESSED_2X_PNG"
                file="images/2x/button_butter_bar_close_pressed.png" type="BINDATA" />
-      <include name="IDR_FEEDBACK_SYSINFO_HTML" file="html/sys_info.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+      <include name="IDR_FEEDBACK_SYSINFO_HTML" file="html/sys_info.html" type="BINDATA" />
       <include name="IDR_FEEDBACK_SYSINFO_JS" file="js/sys_info.js" type="BINDATA" />
       <include name="IDR_FEEDBACK_SYSINFO_CSS" file="css/sys_info.css" type="BINDATA" />
       <if expr="chromeos">
diff --git a/chrome/browser/resources/feedback_webui/html/sys_info.html b/chrome/browser/resources/feedback_webui/html/sys_info.html
index 1517e3ce..e2289b5 100644
--- a/chrome/browser/resources/feedback_webui/html/sys_info.html
+++ b/chrome/browser/resources/feedback_webui/html/sys_info.html
@@ -6,7 +6,7 @@
     <title>$i18n{sysinfoPageTitle}</title>
     <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
     <link rel="stylesheet" href="chrome://resources/css/spinner.css">
-    <link rel="stylesheet" href="../../about_sys/about_sys.css">
+    <link rel="stylesheet" href="../css/about_sys.css">
     <link rel="stylesheet" href="../css/sys_info.css">
     <link rel="stylesheet" href="../css/feedback_shared_styles.css">
 
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index db7e5e1..9636d5a 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -279,6 +279,9 @@
     "chromeos/internet_page/cellular_setup_settings_delegate.js",
     "chromeos/internet_page/internet_page_browser_proxy.js",
     "chromeos/kerberos_page/kerberos_accounts_browser_proxy.js",
+    "chromeos/multidevice_page/multidevice_browser_proxy.js",
+    "chromeos/multidevice_page/multidevice_constants.js",
+    "chromeos/multidevice_page/multidevice_feature_behavior.js",
     "chromeos/nearby_share_page/nearby_account_manager_browser_proxy.js",
     "chromeos/nearby_share_page/nearby_share_receive_manager.js",
     "chromeos/nearby_share_page/types.js",
@@ -475,25 +478,22 @@
     "chromeos/kerberos_page/kerberos_add_account_dialog.js",
     "chromeos/kerberos_page/kerberos_page.js",
     "chromeos/keyboard_shortcut_banner/keyboard_shortcut_banner.js",
-    "chromeos/multidevice_page/multidevice_browser_proxy.m.js",
-    "chromeos/multidevice_page/multidevice_combined_setup_item.m.js",
-    "chromeos/multidevice_page/multidevice_constants.m.js",
-    "chromeos/multidevice_page/multidevice_feature_behavior.m.js",
-    "chromeos/multidevice_page/multidevice_feature_item.m.js",
-    "chromeos/multidevice_page/multidevice_feature_toggle.m.js",
-    "chromeos/multidevice_page/multidevice_notification_access_setup_dialog.m.js",
-    "chromeos/multidevice_page/multidevice_page.m.js",
-    "chromeos/multidevice_page/multidevice_permissions_setup_dialog.m.js",
-    "chromeos/multidevice_page/multidevice_radio_button.m.js",
-    "chromeos/multidevice_page/multidevice_screen_lock_subpage.m.js",
-    "chromeos/multidevice_page/multidevice_smartlock_subpage.m.js",
-    "chromeos/multidevice_page/multidevice_subpage.m.js",
-    "chromeos/multidevice_page/multidevice_tether_item.m.js",
-    "chromeos/multidevice_page/multidevice_task_continuation_disabled_link.m.js",
-    "chromeos/multidevice_page/multidevice_task_continuation_item.m.js",
-    "chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.m.js",
-    "chromeos/multidevice_page/multidevice_wifi_sync_item.m.js",
-    "chromeos/multidevice_page/multidevice_smartlock_item.m.js",
+    "chromeos/multidevice_page/multidevice_combined_setup_item.js",
+    "chromeos/multidevice_page/multidevice_feature_item.js",
+    "chromeos/multidevice_page/multidevice_feature_toggle.js",
+    "chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js",
+    "chromeos/multidevice_page/multidevice_page.js",
+    "chromeos/multidevice_page/multidevice_permissions_setup_dialog.js",
+    "chromeos/multidevice_page/multidevice_radio_button.js",
+    "chromeos/multidevice_page/multidevice_screen_lock_subpage.js",
+    "chromeos/multidevice_page/multidevice_smartlock_subpage.js",
+    "chromeos/multidevice_page/multidevice_subpage.js",
+    "chromeos/multidevice_page/multidevice_tether_item.js",
+    "chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js",
+    "chromeos/multidevice_page/multidevice_task_continuation_item.js",
+    "chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js",
+    "chromeos/multidevice_page/multidevice_wifi_sync_item.js",
+    "chromeos/multidevice_page/multidevice_smartlock_item.js",
     "chromeos/nearby_share_page/nearby_share_confirm_page.js",
     "chromeos/nearby_share_page/nearby_share_contact_visibility_dialog.js",
     "chromeos/nearby_share_page/nearby_share_data_usage_dialog.js",
@@ -796,7 +796,7 @@
     "internet_page:web_components",
     "kerberos_page:web_components",
     "keyboard_shortcut_banner:web_components",
-    "multidevice_page:polymer3_elements",
+    "multidevice_page:web_components",
     "nearby_share_page:web_components",
     "os_a11y_page:web_components",
     "os_about_page:web_components",
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index 686b0dd..629664cc 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -292,8 +292,8 @@
 js_library("cellular_networks_list") {
   deps = [
     ":esim_install_error_dialog",
-    "../multidevice_page:multidevice_browser_proxy.m",
-    "../multidevice_page:multidevice_constants.m",
+    "../multidevice_page:multidevice_browser_proxy",
+    "../multidevice_page:multidevice_constants",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_components/chromeos/cellular_setup:cellular_types.m",
     "//ui/webui/resources/cr_components/chromeos/cellular_setup:esim_manager_listener_behavior.m",
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
index 69d5f063..f675c4f 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
@@ -31,8 +31,8 @@
 import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from '../multidevice_page/multidevice_browser_proxy.m.js';
-import {MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, PhoneHubFeatureAccessProhibitedReason, PhoneHubFeatureAccessStatus, SmartLockSignInEnabledState} from '../multidevice_page/multidevice_constants.m.js';
+import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from '../multidevice_page/multidevice_browser_proxy.js';
+import {MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, PhoneHubFeatureAccessProhibitedReason, PhoneHubFeatureAccessStatus, SmartLockSignInEnabledState} from '../multidevice_page/multidevice_constants.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
index 0ec056a..161506b 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
@@ -3,8 +3,7 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/polymer.gni")
-import("//ui/webui/resources/tools/js_modulizer.gni")
+import("//tools/polymer/html_to_js.gni")
 import("../../../nearby_share/shared/nearby_shared.gni")
 import("../os_settings.gni")
 
@@ -12,58 +11,51 @@
   closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
-    ":multidevice_browser_proxy.m",
-    ":multidevice_combined_setup_item.m",
-    ":multidevice_constants.m",
-    ":multidevice_feature_behavior.m",
-    ":multidevice_feature_item.m",
-    ":multidevice_feature_toggle.m",
-    ":multidevice_notification_access_setup_dialog.m",
-    ":multidevice_page.m",
-    ":multidevice_permissions_setup_dialog.m",
-    ":multidevice_radio_button.m",
-    ":multidevice_screen_lock_subpage.m",
-    ":multidevice_smartlock_subpage.m",
-    ":multidevice_subpage.m",
-    ":multidevice_task_continuation_disabled_link.m",
-    ":multidevice_task_continuation_item.m",
-    ":multidevice_tether_item.m",
-    ":multidevice_wifi_sync_disabled_link.m",
-    ":multidevice_wifi_sync_item.m",
+    ":multidevice_browser_proxy",
+    ":multidevice_combined_setup_item",
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
+    ":multidevice_feature_item",
+    ":multidevice_feature_toggle",
+    ":multidevice_notification_access_setup_dialog",
+    ":multidevice_page",
+    ":multidevice_permissions_setup_dialog",
+    ":multidevice_radio_button",
+    ":multidevice_screen_lock_subpage",
+    ":multidevice_smartlock_subpage",
+    ":multidevice_subpage",
+    ":multidevice_task_continuation_disabled_link",
+    ":multidevice_task_continuation_item",
+    ":multidevice_tether_item",
+    ":multidevice_wifi_sync_disabled_link",
+    ":multidevice_wifi_sync_item",
   ]
 }
 
-js_library("multidevice_browser_proxy.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.m.js" ]
+js_library("multidevice_browser_proxy") {
   deps = [
-    ":multidevice_constants.m",
+    ":multidevice_constants",
     "//ui/webui/resources/js:cr.m",
   ]
-  extra_deps = [ ":modulize" ]
 }
 
-js_library("multidevice_constants.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.m.js" ]
+js_library("multidevice_constants") {
   deps = []
-  extra_deps = [ ":modulize" ]
 }
 
-js_library("multidevice_feature_behavior.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_behavior.m.js" ]
+js_library("multidevice_feature_behavior") {
   deps = [
-    ":multidevice_constants.m",
+    ":multidevice_constants",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
-  extra_deps = [ ":modulize" ]
 }
 
-js_library("multidevice_feature_item.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.m.js" ]
+js_library("multidevice_feature_item") {
   deps = [
-    ":multidevice_constants.m",
-    ":multidevice_feature_behavior.m",
-    ":multidevice_feature_toggle.m",
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
+    ":multidevice_feature_toggle",
     "..:os_route",
     "..:route_origin_behavior",
     "../..:router",
@@ -71,27 +63,23 @@
     "//ui/webui/resources/cr_components/localized_link:localized_link",
     "//ui/webui/resources/js:assert.m",
   ]
-  extra_deps = [ ":multidevice_feature_item_module" ]
 }
 
-js_library("multidevice_feature_toggle.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.m.js" ]
+js_library("multidevice_feature_toggle") {
   deps = [
-    ":multidevice_constants.m",
-    ":multidevice_feature_behavior.m",
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
-  extra_deps = [ ":multidevice_feature_toggle_module" ]
 }
 
-js_library("multidevice_page.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.m.js" ]
+js_library("multidevice_page") {
   deps = [
-    ":multidevice_browser_proxy.m",
-    ":multidevice_constants.m",
-    ":multidevice_feature_behavior.m",
-    ":multidevice_notification_access_setup_dialog.m",
-    ":multidevice_permissions_setup_dialog.m",
+    ":multidevice_browser_proxy",
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
+    ":multidevice_notification_access_setup_dialog",
+    ":multidevice_permissions_setup_dialog",
     "..:deep_linking_behavior",
     "..:metrics_recorder",
     "..:os_route",
@@ -104,45 +92,37 @@
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_page_module" ]
 }
 
-js_library("multidevice_notification_access_setup_dialog.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.m.js" ]
+js_library("multidevice_notification_access_setup_dialog") {
   deps = [
-    ":multidevice_constants.m",
+    ":multidevice_constants",
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_notification_access_setup_dialog_module" ]
 }
 
-js_library("multidevice_permissions_setup_dialog.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.m.js" ]
+js_library("multidevice_permissions_setup_dialog") {
   deps = [
-    ":multidevice_constants.m",
+    ":multidevice_constants",
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_permissions_setup_dialog_module" ]
 }
 
-js_library("multidevice_radio_button.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.m.js" ]
+js_library("multidevice_radio_button") {
   deps = [
     "//third_party/polymer/v1_0/components-chromium/iron-a11y-keys-behavior:iron-a11y-keys-behavior-extracted",
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button_behavior.m",
     "//ui/webui/resources/cr_elements/policy:cr_policy_indicator.m",
   ]
-  extra_deps = [ ":multidevice_radio_button_module" ]
 }
 
-js_library("multidevice_smartlock_subpage.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.m.js" ]
+js_library("multidevice_smartlock_subpage") {
   deps = [
-    ":multidevice_constants.m",
-    ":multidevice_feature_behavior.m",
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
     "..:deep_linking_behavior",
     "..:metrics_recorder",
     "..:os_route",
@@ -154,18 +134,16 @@
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_smartlock_subpage_module" ]
   externs_list = [ "$externs_path/quick_unlock_private.js" ]
 }
 
-js_library("multidevice_subpage.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.m.js" ]
+js_library("multidevice_subpage") {
   deps = [
-    ":multidevice_browser_proxy.m",
-    ":multidevice_combined_setup_item.m",
-    ":multidevice_constants.m",
-    ":multidevice_task_continuation_item.m",
-    ":multidevice_wifi_sync_item.m",
+    ":multidevice_browser_proxy",
+    ":multidevice_combined_setup_item",
+    ":multidevice_constants",
+    ":multidevice_task_continuation_item",
+    ":multidevice_wifi_sync_item",
     "..:deep_linking_behavior",
     "..:os_route",
     "..:os_settings_routes",
@@ -174,26 +152,22 @@
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_components/chromeos/network:network_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_subpage_module" ]
 }
 
-js_library("multidevice_combined_setup_item.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.m.js" ]
+js_library("multidevice_combined_setup_item") {
   deps = [
-    ":multidevice_constants.m",
-    ":multidevice_feature_behavior.m",
-    ":multidevice_feature_item.m",
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
+    ":multidevice_feature_item",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_combined_setup_item_module" ]
   externs_list = [ "../settings_controls_types.js" ]
 }
 
-js_library("multidevice_tether_item.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.m.js" ]
+js_library("multidevice_tether_item") {
   deps = [
-    ":multidevice_feature_behavior.m",
+    ":multidevice_feature_behavior",
     "..:os_route",
     "..:os_settings_routes",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -201,29 +175,25 @@
     "//ui/webui/resources/cr_components/chromeos/network:network_listener_behavior.m",
     "//ui/webui/resources/cr_components/chromeos/network:onc_mojo.m",
   ]
-  extra_deps = [ ":multidevice_tether_item_module" ]
 }
 
-js_library("multidevice_task_continuation_item.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.m.js" ]
+js_library("multidevice_task_continuation_item") {
   deps = [
-    ":multidevice_constants.m",
-    ":multidevice_feature_behavior.m",
-    ":multidevice_feature_item.m",
-    ":multidevice_task_continuation_disabled_link.m",
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
+    ":multidevice_feature_item",
+    ":multidevice_task_continuation_disabled_link",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_task_continuation_item_module" ]
   externs_list = [ "../settings_controls_types.js" ]
 }
 
-js_library("multidevice_wifi_sync_item.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.m.js" ]
+js_library("multidevice_wifi_sync_item") {
   deps = [
-    ":multidevice_feature_behavior.m",
-    ":multidevice_feature_item.m",
-    ":multidevice_wifi_sync_disabled_link.m",
+    ":multidevice_feature_behavior",
+    ":multidevice_feature_item",
+    ":multidevice_wifi_sync_disabled_link",
     "..:os_route",
     "..:route_origin_behavior",
     "../..:router",
@@ -233,12 +203,10 @@
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_wifi_sync_item_module" ]
   externs_list = [ "../settings_controls_types.js" ]
 }
 
-js_library("multidevice_task_continuation_disabled_link.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.m.js" ]
+js_library("multidevice_task_continuation_disabled_link") {
   deps = [
     "..:os_route",
     "../..:router",
@@ -246,13 +214,11 @@
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
-  extra_deps = [ ":multidevice_task_continuation_disabled_link_module" ]
 }
 
-js_library("multidevice_wifi_sync_disabled_link.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.m.js" ]
+js_library("multidevice_wifi_sync_disabled_link") {
   deps = [
-    ":multidevice_feature_behavior.m",
+    ":multidevice_feature_behavior",
     "..:os_route",
     "..:route_origin_behavior",
     "../..:router",
@@ -260,11 +226,9 @@
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
-  extra_deps = [ ":multidevice_wifi_sync_disabled_link_module" ]
 }
 
-js_library("multidevice_screen_lock_subpage.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.m.js" ]
+js_library("multidevice_screen_lock_subpage") {
   deps = [
     "../os_people_page:lock_screen_password_prompt_dialog",
     "../os_people_page:lock_state_behavior.m",
@@ -275,214 +239,38 @@
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
-  extra_deps = [ ":multidevice_screen_lock_subpage_module" ]
   externs_list = [ "$externs_path/chrome_extensions.js" ]
 }
 
-js_library("multidevice_smartlock_item.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.m.js" ]
+js_library("multidevice_smartlock_item") {
   deps = [
-    ":multidevice_browser_proxy.m",
-    ":multidevice_constants.m",
-    ":multidevice_feature_behavior.m",
-    ":multidevice_feature_item.m",
+    ":multidevice_browser_proxy",
+    ":multidevice_constants",
+    ":multidevice_feature_behavior",
+    ":multidevice_feature_item",
     "..:metrics_recorder",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":multidevice_smartlock_item_module" ]
 }
 
-group("polymer3_elements") {
-  public_deps = [
-    ":modulize",
-    ":multidevice_combined_setup_item_module",
-    ":multidevice_feature_item_module",
-    ":multidevice_feature_toggle_module",
-    ":multidevice_notification_access_setup_dialog_module",
-    ":multidevice_page_module",
-    ":multidevice_permissions_setup_dialog_module",
-    ":multidevice_radio_button_module",
-    ":multidevice_screen_lock_subpage_module",
-    ":multidevice_smartlock_item_module",
-    ":multidevice_smartlock_subpage_module",
-    ":multidevice_subpage_module",
-    ":multidevice_task_continuation_disabled_link_module",
-    ":multidevice_task_continuation_item_module",
-    ":multidevice_tether_item_module",
-    ":multidevice_wifi_sync_disabled_link_module",
-    ":multidevice_wifi_sync_item_module",
+html_to_js("web_components") {
+  js_files = [
+    "multidevice_combined_setup_item.js",
+    "multidevice_feature_item.js",
+    "multidevice_feature_toggle.js",
+    "multidevice_notification_access_setup_dialog.js",
+    "multidevice_page.js",
+    "multidevice_permissions_setup_dialog.js",
+    "multidevice_radio_button.js",
+    "multidevice_screen_lock_subpage.js",
+    "multidevice_smartlock_item.js",
+    "multidevice_smartlock_subpage.js",
+    "multidevice_subpage.js",
+    "multidevice_task_continuation_disabled_link.js",
+    "multidevice_task_continuation_item.js",
+    "multidevice_tether_item.js",
+    "multidevice_wifi_sync_disabled_link.js",
+    "multidevice_wifi_sync_item.js",
   ]
 }
-
-nearby_shared_auto_imports_closure_fix = [
-  # TODO(crbug.com/1121865): polymer.py normalizes the relative paths to shared
-  # nearby resources like ../../shared/* against
-  # c/b/r/settings/chromeos/nearby_share_page
-  # generating paths with the prefix c/b/r/settings/shared/*. It uses the
-  # normalized path to look up relative references for auto import. In order to
-  # get the auto import to match, we need to use this path prefix even though it
-  # does not exist on disk there. The actual resources are in
-  # c/b/r/nearby_share/shared and are re-hosted in the chrome://os-settings
-  # webui at the chrome://os-settings/shared/* prefix.
-  "chrome/browser/resources/settings/shared/nearby_contact_manager.html|getContactManager",
-  "chrome/browser/resources/settings/shared/nearby_share_settings.html|getNearbyShareSettings",
-  "chrome/browser/resources/settings/shared/nearby_share_settings_behavior.html|NearbyShareSettingsBehavior,NearbySettings",
-]
-
-polymer_modulizer("multidevice_feature_item") {
-  js_file = "multidevice_feature_item.js"
-  html_file = "multidevice_feature_item.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports +
-                 [ "ui/webui/resources/html/assert.html|assert" ]
-}
-
-polymer_modulizer("multidevice_feature_toggle") {
-  js_file = "multidevice_feature_toggle.js"
-  html_file = "multidevice_feature_toggle.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_page") {
-  js_file = "multidevice_page.js"
-  html_file = "multidevice_page.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites =
-      os_settings_namespace_rewrites + nearby_shared_namespace_rewrites
-  auto_imports =
-      os_settings_auto_imports + nearby_shared_auto_imports_closure_fix +
-      [ "ui/webui/resources/html/polymer.html|Polymer,html,beforeNextRender" ]
-}
-
-polymer_modulizer("multidevice_notification_access_setup_dialog") {
-  js_file = "multidevice_notification_access_setup_dialog.js"
-  html_file = "multidevice_notification_access_setup_dialog.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_permissions_setup_dialog") {
-  js_file = "multidevice_permissions_setup_dialog.js"
-  html_file = "multidevice_permissions_setup_dialog.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_radio_button") {
-  js_file = "multidevice_radio_button.js"
-  html_file = "multidevice_radio_button.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_smartlock_subpage") {
-  js_file = "multidevice_smartlock_subpage.js"
-  html_file = "multidevice_smartlock_subpage.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_subpage") {
-  js_file = "multidevice_subpage.js"
-  html_file = "multidevice_subpage.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_combined_setup_item") {
-  js_file = "multidevice_combined_setup_item.js"
-  html_file = "multidevice_combined_setup_item.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_tether_item") {
-  js_file = "multidevice_tether_item.js"
-  html_file = "multidevice_tether_item.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_task_continuation_item") {
-  js_file = "multidevice_task_continuation_item.js"
-  html_file = "multidevice_task_continuation_item.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_wifi_sync_item") {
-  js_file = "multidevice_wifi_sync_item.js"
-  html_file = "multidevice_wifi_sync_item.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_task_continuation_disabled_link") {
-  js_file = "multidevice_task_continuation_disabled_link.js"
-  html_file = "multidevice_task_continuation_disabled_link.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_wifi_sync_disabled_link") {
-  js_file = "multidevice_wifi_sync_disabled_link.js"
-  html_file = "multidevice_wifi_sync_disabled_link.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_screen_lock_subpage") {
-  js_file = "multidevice_screen_lock_subpage.js"
-  html_file = "multidevice_screen_lock_subpage.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("multidevice_smartlock_item") {
-  js_file = "multidevice_smartlock_item.js"
-  html_file = "multidevice_smartlock_item.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-js_modulizer("modulize") {
-  input_files = [
-    "multidevice_browser_proxy.js",
-    "multidevice_feature_behavior.js",
-    "multidevice_constants.js",
-  ]
-  namespace_rewrites = os_settings_namespace_rewrites
-}
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.js
index a3dd04f..03ce440 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.js
@@ -2,182 +2,172 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// clang-format off
-// #import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
-// #import {MultiDevicePageContentData, MultiDeviceFeature} from './multidevice_constants.m.js';
-// clang-format on
+import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
 
-cr.define('settings', function() {
-  /**
-   * An object containing messages for web permissisions origin
-   * and the messages multidevice feature state.
-   *
-   * @typedef {{origin: string,
-   *            enabled: boolean}}
-   */
-  /* #export */ let AndroidSmsInfo;
+import {MultiDeviceFeature, MultiDevicePageContentData} from './multidevice_constants.js';
 
-  /** @interface */
-  /* #export */ class MultiDeviceBrowserProxy {
-    showMultiDeviceSetupDialog() {}
+/**
+ * An object containing messages for web permissisions origin
+ * and the messages multidevice feature state.
+ *
+ * @typedef {{origin: string,
+ *            enabled: boolean}}
+ */
+export let AndroidSmsInfo;
 
-    /** @return {!Promise<!settings.MultiDevicePageContentData>} */
-    getPageContentData() {}
+/** @interface */
+export class MultiDeviceBrowserProxy {
+  showMultiDeviceSetupDialog() {}
 
-    /**
-     * @param {!settings.MultiDeviceFeature} feature The feature whose state
-     *     should be set.
-     * @param {boolean} enabled Whether the feature should be turned off or on.
-     * @param {string=} opt_authToken Proof that the user is authenticated.
-     *     Needed to enable Smart Lock, and Better Together Suite if the Smart
-     *     Lock user pref is enabled.
-     * @return {!Promise<boolean>} Whether the operation was successful.
-     */
-    setFeatureEnabledState(feature, enabled, opt_authToken) {}
-
-    removeHostDevice() {}
-
-    retryPendingHostSetup() {}
-
-    /**
-     * Called when the "Set Up" button is clicked to open the Android Messages
-     * PWA.
-     */
-    setUpAndroidSms() {}
-
-    /**
-     * Returns the value of the preference controlling whether Smart Lock may be
-     * used to sign-in the user (as opposed to unlocking the screen).
-     * @return {!Promise<boolean>}
-     */
-    getSmartLockSignInEnabled() {}
-
-    /**
-     * Sets the value of the preference controlling whether Smart Lock may be
-     * used to sign-in the user (as opposed to unlocking the screen).
-     * @param {boolean} enabled
-     * @param {string=} opt_authToken Authentication token used to restrict
-     *    edit access to the Smart Lock sign-in pref.
-     */
-    setSmartLockSignInEnabled(enabled, opt_authToken) {}
-
-    /**
-     * Returns the value of the preference controlling whether Smart Lock
-     * sign-in is allowed.
-     * @return {!Promise<boolean>}
-     */
-    getSmartLockSignInAllowed() {}
-
-    /**
-     * Returns android messages info with messages feature state
-     * and messages for web permissions origin.
-     * @return {!Promise<!settings.AndroidSmsInfo>} Android SMS Info
-     */
-    getAndroidSmsInfo() {}
-
-    /**
-     * Attempts the phone hub notification access setup flow.
-     */
-    attemptNotificationSetup() {}
-
-    /**
-     * Cancels the phone hub notification access setup flow.
-     */
-    cancelNotificationSetup() {}
-
-    /**
-     * Attempts the phone hub apps access setup flow.
-     */
-    attemptAppsSetup() {}
-
-    /**
-     * Cancels the phone hub apps access setup flow.
-     */
-    cancelAppsSetup() {}
-  }
+  /** @return {!Promise<!MultiDevicePageContentData>} */
+  getPageContentData() {}
 
   /**
-   * @implements {settings.MultiDeviceBrowserProxy}
+   * @param {!MultiDeviceFeature} feature The feature whose state
+   *     should be set.
+   * @param {boolean} enabled Whether the feature should be turned off or on.
+   * @param {string=} opt_authToken Proof that the user is authenticated.
+   *     Needed to enable Smart Lock, and Better Together Suite if the Smart
+   *     Lock user pref is enabled.
+   * @return {!Promise<boolean>} Whether the operation was successful.
    */
-  /* #export */ class MultiDeviceBrowserProxyImpl {
-    /** @override */
-    showMultiDeviceSetupDialog() {
-      chrome.send('showMultiDeviceSetupDialog');
-    }
+  setFeatureEnabledState(feature, enabled, opt_authToken) {}
 
-    /** @override */
-    getPageContentData() {
-      return cr.sendWithPromise('getPageContentData');
-    }
+  removeHostDevice() {}
 
-    /** @override */
-    setFeatureEnabledState(feature, enabled, opt_authToken) {
-      return cr.sendWithPromise(
-          'setFeatureEnabledState', feature, enabled, opt_authToken);
-    }
+  retryPendingHostSetup() {}
 
-    /** @override */
-    removeHostDevice() {
-      chrome.send('removeHostDevice');
-    }
+  /**
+   * Called when the "Set Up" button is clicked to open the Android Messages
+   * PWA.
+   */
+  setUpAndroidSms() {}
 
-    /** @override */
-    retryPendingHostSetup() {
-      chrome.send('retryPendingHostSetup');
-    }
+  /**
+   * Returns the value of the preference controlling whether Smart Lock may be
+   * used to sign-in the user (as opposed to unlocking the screen).
+   * @return {!Promise<boolean>}
+   */
+  getSmartLockSignInEnabled() {}
 
-    /** @override */
-    setUpAndroidSms() {
-      chrome.send('setUpAndroidSms');
-    }
+  /**
+   * Sets the value of the preference controlling whether Smart Lock may be
+   * used to sign-in the user (as opposed to unlocking the screen).
+   * @param {boolean} enabled
+   * @param {string=} opt_authToken Authentication token used to restrict
+   *    edit access to the Smart Lock sign-in pref.
+   */
+  setSmartLockSignInEnabled(enabled, opt_authToken) {}
 
-    /** @override */
-    getSmartLockSignInEnabled() {
-      return cr.sendWithPromise('getSmartLockSignInEnabled');
-    }
+  /**
+   * Returns the value of the preference controlling whether Smart Lock
+   * sign-in is allowed.
+   * @return {!Promise<boolean>}
+   */
+  getSmartLockSignInAllowed() {}
 
-    /** @override */
-    setSmartLockSignInEnabled(enabled, opt_authToken) {
-      chrome.send('setSmartLockSignInEnabled', [enabled, opt_authToken]);
-    }
+  /**
+   * Returns android messages info with messages feature state
+   * and messages for web permissions origin.
+   * @return {!Promise<!AndroidSmsInfo>} Android SMS Info
+   */
+  getAndroidSmsInfo() {}
 
-    /** @override */
-    getSmartLockSignInAllowed() {
-      return cr.sendWithPromise('getSmartLockSignInAllowed');
-    }
+  /**
+   * Attempts the phone hub notification access setup flow.
+   */
+  attemptNotificationSetup() {}
 
-    /** @override */
-    getAndroidSmsInfo() {
-      return cr.sendWithPromise('getAndroidSmsInfo');
-    }
+  /**
+   * Cancels the phone hub notification access setup flow.
+   */
+  cancelNotificationSetup() {}
 
-    /** @override */
-    attemptNotificationSetup() {
-      chrome.send('attemptNotificationSetup');
-    }
+  /**
+   * Attempts the phone hub apps access setup flow.
+   */
+  attemptAppsSetup() {}
 
-    /** @override */
-    cancelNotificationSetup() {
-      chrome.send('cancelNotificationSetup');
-    }
+  /**
+   * Cancels the phone hub apps access setup flow.
+   */
+  cancelAppsSetup() {}
+}
 
-    /** @override */
-    attemptAppsSetup() {
-      chrome.send('attemptAppsSetup');
-    }
-
-    /** @override */
-    cancelAppsSetup() {
-      chrome.send('cancelAppsSetup');
-    }
+/**
+ * @implements {MultiDeviceBrowserProxy}
+ */
+export class MultiDeviceBrowserProxyImpl {
+  /** @override */
+  showMultiDeviceSetupDialog() {
+    chrome.send('showMultiDeviceSetupDialog');
   }
 
-  cr.addSingletonGetter(MultiDeviceBrowserProxyImpl);
+  /** @override */
+  getPageContentData() {
+    return sendWithPromise('getPageContentData');
+  }
 
-  // #cr_define_end
-  return {
-    AndroidSmsInfo,
-    MultiDeviceBrowserProxy,
-    MultiDeviceBrowserProxyImpl,
-  };
-});
+  /** @override */
+  setFeatureEnabledState(feature, enabled, opt_authToken) {
+    return sendWithPromise(
+        'setFeatureEnabledState', feature, enabled, opt_authToken);
+  }
+
+  /** @override */
+  removeHostDevice() {
+    chrome.send('removeHostDevice');
+  }
+
+  /** @override */
+  retryPendingHostSetup() {
+    chrome.send('retryPendingHostSetup');
+  }
+
+  /** @override */
+  setUpAndroidSms() {
+    chrome.send('setUpAndroidSms');
+  }
+
+  /** @override */
+  getSmartLockSignInEnabled() {
+    return sendWithPromise('getSmartLockSignInEnabled');
+  }
+
+  /** @override */
+  setSmartLockSignInEnabled(enabled, opt_authToken) {
+    chrome.send('setSmartLockSignInEnabled', [enabled, opt_authToken]);
+  }
+
+  /** @override */
+  getSmartLockSignInAllowed() {
+    return sendWithPromise('getSmartLockSignInAllowed');
+  }
+
+  /** @override */
+  getAndroidSmsInfo() {
+    return sendWithPromise('getAndroidSmsInfo');
+  }
+
+  /** @override */
+  attemptNotificationSetup() {
+    chrome.send('attemptNotificationSetup');
+  }
+
+  /** @override */
+  cancelNotificationSetup() {
+    chrome.send('cancelNotificationSetup');
+  }
+
+  /** @override */
+  attemptAppsSetup() {
+    chrome.send('attemptAppsSetup');
+  }
+
+  /** @override */
+  cancelAppsSetup() {
+    chrome.send('cancelAppsSetup');
+  }
+}
+
+addSingletonGetter(MultiDeviceBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.html
index 0cb3ae9..3a97f3a 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.html
@@ -1,38 +1,25 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/localized_link/localized_link.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_item.html">
-
-<dom-module id="settings-multidevice-combined-setup-item">
-  <template>
-    <style include="settings-shared">
-      cr-button {
-        white-space: nowrap;
-      }
-    </style>
-    <settings-multidevice-feature-item id="phoneHubCombinedSetupItem"
-        page-content-data="[[pageContentData]]" is-sub-feature>
-      <div id="featureName" slot="feature-name">
-        [[setupName_]]
-      </div>
-      <localized-link
-          class="secondary"
-          id="featureSecondary"
-          slot="feature-summary"
-          localized-string="[[setupSummary_]]">
-      </localized-link>
-      <cr-button
-          slot="feature-controller"
-          on-click="handlePhoneHubSetupClick_"
-          aria-labelledby="featureName"
-          aria-describedby="featureSecondary"
-          disabled="[[getButtonDisabledState_(pageContentData)]]">
-        $i18n{multideviceSetupButton}
-      </cr-button>
-    </settings-multidevice-feature-item>
-  </template>
-  <script src="multidevice_combined_setup_item.js"></script>
-</dom-module>
+<style include="settings-shared">
+  cr-button {
+    white-space: nowrap;
+  }
+</style>
+<settings-multidevice-feature-item id="phoneHubCombinedSetupItem"
+    page-content-data="[[pageContentData]]" is-sub-feature>
+  <div id="featureName" slot="feature-name">
+    [[setupName_]]
+  </div>
+  <localized-link
+      class="secondary"
+      id="featureSecondary"
+      slot="feature-summary"
+      localized-string="[[setupSummary_]]">
+  </localized-link>
+  <cr-button
+      slot="feature-controller"
+      on-click="handlePhoneHubSetupClick_"
+      aria-labelledby="featureName"
+      aria-describedby="featureSecondary"
+      disabled="[[getButtonDisabledState_(pageContentData)]]">
+    $i18n{multideviceSetupButton}
+  </cr-button>
+</settings-multidevice-feature-item>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.js
index 0e235f4..b218631 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.js
@@ -2,11 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_components/localized_link/localized_link.js';
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import './multidevice_feature_item.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview 'settings-multidevice-combined-setup-item' encapsulates
  * special logic for setting up multiple features from one click.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-combined-setup-item',
 
   behaviors: [MultiDeviceFeatureBehavior],
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.js
index 80829f3..625e6ac 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.js
@@ -2,161 +2,146 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('settings', function() {
+/**
+ * The state of the preference controlling Smart Lock's ability to sign-in the
+ * user.
+ * @enum {string}
+ */
+export const SmartLockSignInEnabledState = {
+  ENABLED: 'enabled',
+  DISABLED: 'disabled',
+};
 
-  /**
-   * The state of the preference controlling Smart Lock's ability to sign-in the
-   * user.
-   * @enum {string}
-   */
-  /* #export */ const SmartLockSignInEnabledState = {
-    ENABLED: 'enabled',
-    DISABLED: 'disabled',
-  };
+/**
+ * The possible statuses of hosts on the logged in account that determine the
+ * page content. Note that this is based on (and must include an analog of
+ * all values in) the HostStatus enum in
+ * services/multidevice_setup/public/mojom/multidevice_setup.mojom.
+ * @enum {number}
+ */
+export const MultiDeviceSettingsMode = {
+  NO_ELIGIBLE_HOSTS: 0,
+  NO_HOST_SET: 1,
+  HOST_SET_WAITING_FOR_SERVER: 2,
+  HOST_SET_WAITING_FOR_VERIFICATION: 3,
+  HOST_SET_VERIFIED: 4,
+};
 
-  /**
-   * The possible statuses of hosts on the logged in account that determine the
-   * page content. Note that this is based on (and must include an analog of
-   * all values in) the HostStatus enum in
-   * services/multidevice_setup/public/mojom/multidevice_setup.mojom.
-   * @enum {number}
-   */
-  /* #export */ const MultiDeviceSettingsMode = {
-    NO_ELIGIBLE_HOSTS: 0,
-    NO_HOST_SET: 1,
-    HOST_SET_WAITING_FOR_SERVER: 2,
-    HOST_SET_WAITING_FOR_VERIFICATION: 3,
-    HOST_SET_VERIFIED: 4,
-  };
+/**
+ * Enum of MultiDevice features. Note that this is copied from (and must
+ * include an analog of all values in) the Feature enum in
+ * //ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.
+ * @enum {number}
+ */
+export const MultiDeviceFeature = {
+  BETTER_TOGETHER_SUITE: 0,
+  INSTANT_TETHERING: 1,
+  MESSAGES: 2,
+  SMART_LOCK: 3,
+  PHONE_HUB: 4,
+  PHONE_HUB_NOTIFICATIONS: 5,
+  PHONE_HUB_TASK_CONTINUATION: 6,
+  WIFI_SYNC: 7,
+  ECHE: 8,
+  PHONE_HUB_CAMERA_ROLL: 9,
+};
 
-  /**
-   * Enum of MultiDevice features. Note that this is copied from (and must
-   * include an analog of all values in) the Feature enum in
-   * //ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.
-   * @enum {number}
-   */
-  /* #export */ const MultiDeviceFeature = {
-    BETTER_TOGETHER_SUITE: 0,
-    INSTANT_TETHERING: 1,
-    MESSAGES: 2,
-    SMART_LOCK: 3,
-    PHONE_HUB: 4,
-    PHONE_HUB_NOTIFICATIONS: 5,
-    PHONE_HUB_TASK_CONTINUATION: 6,
-    WIFI_SYNC: 7,
-    ECHE: 8,
-    PHONE_HUB_CAMERA_ROLL: 9,
-  };
+/**
+ * Possible states of MultiDevice features. Note that this is copied from (and
+ * must include an analog of all values in) the FeatureState enum in
+ * //ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.
+ * @enum {number}
+ */
+export const MultiDeviceFeatureState = {
+  PROHIBITED_BY_POLICY: 0,
+  DISABLED_BY_USER: 1,
+  ENABLED_BY_USER: 2,
+  NOT_SUPPORTED_BY_CHROMEBOOK: 3,
+  NOT_SUPPORTED_BY_PHONE: 4,
+  UNAVAILABLE_NO_VERIFIED_HOST: 5,
+  UNAVAILABLE_INSUFFICIENT_SECURITY: 6,
+  UNAVAILABLE_SUITE_DISABLED: 7,
+  FURTHER_SETUP_REQUIRED: 8,
+  UNAVAILABLE_TOP_LEVEL_FEATURE_DISABLED: 9,
+  UNAVAILABLE_NO_VERIFIED_HOST_CLIENT_NOT_READY: 10,
+};
 
-  /**
-   * Possible states of MultiDevice features. Note that this is copied from (and
-   * must include an analog of all values in) the FeatureState enum in
-   * //ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.
-   * @enum {number}
-   */
-  /* #export */ const MultiDeviceFeatureState = {
-    PROHIBITED_BY_POLICY: 0,
-    DISABLED_BY_USER: 1,
-    ENABLED_BY_USER: 2,
-    NOT_SUPPORTED_BY_CHROMEBOOK: 3,
-    NOT_SUPPORTED_BY_PHONE: 4,
-    UNAVAILABLE_NO_VERIFIED_HOST: 5,
-    UNAVAILABLE_INSUFFICIENT_SECURITY: 6,
-    UNAVAILABLE_SUITE_DISABLED: 7,
-    FURTHER_SETUP_REQUIRED: 8,
-    UNAVAILABLE_TOP_LEVEL_FEATURE_DISABLED: 9,
-    UNAVAILABLE_NO_VERIFIED_HOST_CLIENT_NOT_READY: 10,
-  };
+/**
+ * Possible states of Phone Hub's feature access. Access can be
+ * prohibited if the user is using a work profile on their phone on Android
+ * version <N, or if the policy managing the phone disables access.
+ * @enum {number}
+ */
+export const PhoneHubFeatureAccessStatus = {
+  PROHIBITED: 0,
+  AVAILABLE_BUT_NOT_GRANTED: 1,
+  ACCESS_GRANTED: 2,
+};
 
-  /**
-   * Possible states of Phone Hub's feature access. Access can be
-   * prohibited if the user is using a work profile on their phone on Android
-   * version <N, or if the policy managing the phone disables access.
-   * @enum {number}
-   */
-  /* #export */ const PhoneHubFeatureAccessStatus = {
-    PROHIBITED: 0,
-    AVAILABLE_BUT_NOT_GRANTED: 1,
-    ACCESS_GRANTED: 2,
-  };
+/**
+ * Possible reasons for Phone Hub's feature access being prohibited.
+ * Users should ensure feature access is actually prohibited before
+ * comparing against these reasons.
+ * @enum {number}
+ */
+export const PhoneHubFeatureAccessProhibitedReason = {
+  UNKNOWN: 0,
+  WORK_PROFILE: 1,
+  DISABLED_BY_PHONE_POLICY: 2,
+};
 
-  /**
-   * Possible reasons for Phone Hub's feature access being prohibited.
-   * Users should ensure feature access is actually prohibited before
-   * comparing against these reasons.
-   * @enum {number}
-   */
-  /* #export */ const PhoneHubFeatureAccessProhibitedReason = {
-    UNKNOWN: 0,
-    WORK_PROFILE: 1,
-    DISABLED_BY_PHONE_POLICY: 2,
-  };
+/**
+ * Possible of Phone Hub's permissions setup mode.The value will be assigned
+ * when the user clicks on the settings UI. Basically, INIT_MODE will be
+ * default value, which means it has not been set yet.
+ * ALL_PERMISSIONS_SETUP_MODE means that we will process notifications and
+ * apps streaming onboarding flow in order.
+ *
+ * @enum {number}
+ */
+export const PhoneHubPermissionsSetupMode = {
+  INIT_MODE: 0,
+  NOTIFICATION_SETUP_MODE: 1,
+  APPS_SETUP_MODE: 2,
+  CAMERA_ROLL_SETUP_MODE: 3,
+  ALL_PERMISSIONS_SETUP_MODE: 4,
+};
 
-  /**
-   * Possible of Phone Hub's permissions setup mode.The value will be assigned
-   * when the user clicks on the settings UI. Basically, INIT_MODE will be
-   * default value, which means it has not been set yet.
-   * ALL_PERMISSIONS_SETUP_MODE means that we will process notifications and
-   * apps streaming onboarding flow in order.
-   *
-   * @enum {number}
-   */
-  /* #export */ const PhoneHubPermissionsSetupMode = {
-    INIT_MODE: 0,
-    NOTIFICATION_SETUP_MODE: 1,
-    APPS_SETUP_MODE: 2,
-    CAMERA_ROLL_SETUP_MODE: 3,
-    ALL_PERMISSIONS_SETUP_MODE: 4,
-  };
-
-  /**
-   * Container for the initial data that the page requires in order to display
-   * the correct content. It is also used for receiving status updates during
-   * use. Note that the host device may be verified (enabled or disabled),
-   * awaiting verification, or it may have failed setup because it was not able
-   * to connect to the server.
-   *
-   * For each MultiDevice feature (including the "suite" feature, which acts as
-   * a gatekeeper for the others), the corresponding *State property is an enum
-   * containing the data necessary to display it. Note that hostDeviceName
-   * should be undefined if and only if no host has been set up, regardless of
-   * whether there are potential hosts on the account.
-   *
-   * @typedef {{
-   *   mode: !settings.MultiDeviceSettingsMode,
-   *   hostDeviceName: (string|undefined),
-   *   betterTogetherState: !settings.MultiDeviceFeatureState,
-   *   instantTetheringState: !settings.MultiDeviceFeatureState,
-   *   messagesState: !settings.MultiDeviceFeatureState,
-   *   smartLockState: !settings.MultiDeviceFeatureState,
-   *   phoneHubState: !settings.MultiDeviceFeatureState,
-   *   phoneHubCameraRollState: !settings.MultiDeviceFeatureState,
-   *   phoneHubNotificationsState: !settings.MultiDeviceFeatureState,
-   *   phoneHubTaskContinuationState: !settings.MultiDeviceFeatureState,
-   *   phoneHubAppsState: !settings.MultiDeviceFeatureState,
-   *   wifiSyncState: !settings.MultiDeviceFeatureState,
-   *   isAndroidSmsPairingComplete: boolean,
-   *   cameraRollAccessStatus: !settings.PhoneHubFeatureAccessStatus,
-   *   notificationAccessStatus: !settings.PhoneHubFeatureAccessStatus,
-   *   notificationAccessProhibitedReason:
-   *       !settings.PhoneHubFeatureAccessProhibitedReason,
-   *   isNearbyShareDisallowedByPolicy: boolean,
-   *   isPhoneHubAppsAccessGranted: boolean,
-   *   isPhoneHubPermissionsDialogSupported: boolean,
-   *   isCameraRollFilePermissionGranted: boolean
-   * }}
-   */
-  /* #export */ let MultiDevicePageContentData;
-
-  // #cr_define_end
-  return {
-    MultiDeviceSettingsMode,
-    MultiDeviceFeature,
-    MultiDeviceFeatureState,
-    MultiDevicePageContentData,
-    PhoneHubFeatureAccessStatus,
-    PhoneHubFeatureAccessProhibitedReason,
-    PhoneHubPermissionsSetupMode,
-    SmartLockSignInEnabledState
-  };
-});
+/**
+ * Container for the initial data that the page requires in order to display
+ * the correct content. It is also used for receiving status updates during
+ * use. Note that the host device may be verified (enabled or disabled),
+ * awaiting verification, or it may have failed setup because it was not able
+ * to connect to the server.
+ *
+ * For each MultiDevice feature (including the "suite" feature, which acts as
+ * a gatekeeper for the others), the corresponding *State property is an enum
+ * containing the data necessary to display it. Note that hostDeviceName
+ * should be undefined if and only if no host has been set up, regardless of
+ * whether there are potential hosts on the account.
+ *
+ * @typedef {{
+ *   mode: !MultiDeviceSettingsMode,
+ *   hostDeviceName: (string|undefined),
+ *   betterTogetherState: !MultiDeviceFeatureState,
+ *   instantTetheringState: !MultiDeviceFeatureState,
+ *   messagesState: !MultiDeviceFeatureState,
+ *   smartLockState: !MultiDeviceFeatureState,
+ *   phoneHubState: !MultiDeviceFeatureState,
+ *   phoneHubCameraRollState: !MultiDeviceFeatureState,
+ *   phoneHubNotificationsState: !MultiDeviceFeatureState,
+ *   phoneHubTaskContinuationState: !MultiDeviceFeatureState,
+ *   phoneHubAppsState: !MultiDeviceFeatureState,
+ *   wifiSyncState: !MultiDeviceFeatureState,
+ *   isAndroidSmsPairingComplete: boolean,
+ *   cameraRollAccessStatus: !PhoneHubFeatureAccessStatus,
+ *   notificationAccessStatus: !PhoneHubFeatureAccessStatus,
+ *   notificationAccessProhibitedReason:
+ *       !PhoneHubFeatureAccessProhibitedReason,
+ *   isNearbyShareDisallowedByPolicy: boolean,
+ *   isPhoneHubAppsAccessGranted: boolean,
+ *   isPhoneHubPermissionsDialogSupported: boolean,
+ *   isCameraRollFilePermissionGranted: boolean
+ * }}
+ */
+export let MultiDevicePageContentData;
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_behavior.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_behavior.js
index abeadba..1ab63ab 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_behavior.js
@@ -2,10 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// clang-format off
-// #import {MultiDeviceSettingsMode, MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, PhoneHubFeatureAccessStatus, PhoneHubFeatureAccessProhibitedReason} from './multidevice_constants.m.js';
-// #import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
-// clang-format on
+import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+
+import {MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, PhoneHubFeatureAccessStatus} from './multidevice_constants.js';
 
 /**
  * @fileoverview Polymer behavior for dealing with MultiDevice features. It is
@@ -16,7 +15,7 @@
 /** @polymerBehavior */
 const MultiDeviceFeatureBehaviorImpl = {
   properties: {
-    /** @type {!settings.MultiDevicePageContentData} */
+    /** @type {!MultiDevicePageContentData} */
     pageContentData: Object,
 
     /**
@@ -25,7 +24,7 @@
      */
     MultiDeviceFeature: {
       type: Object,
-      value: settings.MultiDeviceFeature,
+      value: MultiDeviceFeature,
     },
   },
 
@@ -37,7 +36,7 @@
   isSuiteOn() {
     return !!this.pageContentData &&
         this.pageContentData.betterTogetherState ===
-        settings.MultiDeviceFeatureState.ENABLED_BY_USER;
+        MultiDeviceFeatureState.ENABLED_BY_USER;
   },
 
   /**
@@ -48,26 +47,26 @@
   isSuiteAllowedByPolicy() {
     return !!this.pageContentData &&
         this.pageContentData.betterTogetherState !==
-        settings.MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
+        MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
   },
 
   /**
    * Whether an individual feature is allowed by policy.
-   * @param {!settings.MultiDeviceFeature} feature
+   * @param {!MultiDeviceFeature} feature
    * @return {boolean}
    */
   isFeatureAllowedByPolicy(feature) {
     return this.getFeatureState(feature) !==
-        settings.MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
+        MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
   },
 
   /**
-   * @param {!settings.MultiDeviceFeature} feature
+   * @param {!MultiDeviceFeature} feature
    * @return {boolean}
    */
   isFeatureSupported(feature) {
-    return ![settings.MultiDeviceFeatureState.NOT_SUPPORTED_BY_CHROMEBOOK,
-             settings.MultiDeviceFeatureState.NOT_SUPPORTED_BY_PHONE,
+    return ![MultiDeviceFeatureState.NOT_SUPPORTED_BY_CHROMEBOOK,
+             MultiDeviceFeatureState.NOT_SUPPORTED_BY_PHONE,
     ].includes(this.getFeatureState(feature));
   },
 
@@ -76,20 +75,19 @@
    * @return {boolean}
    */
   isPhoneHubOn() {
-    return this.getFeatureState(settings.MultiDeviceFeature.PHONE_HUB) ===
-        settings.MultiDeviceFeatureState.ENABLED_BY_USER;
+    return this.getFeatureState(MultiDeviceFeature.PHONE_HUB) ===
+        MultiDeviceFeatureState.ENABLED_BY_USER;
   },
 
   /**
-   * @param {!settings.MultiDeviceFeature} feature
+   * @param {!MultiDeviceFeature} feature
    * @return {boolean}
    */
   isPhoneHubSubFeature(feature) {
     return [
-      settings.MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL,
-      settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
-      settings.MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION,
-      settings.MultiDeviceFeature.ECHE
+      MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL,
+      MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS,
+      MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION, MultiDeviceFeature.ECHE
     ].includes(feature);
   },
 
@@ -100,7 +98,7 @@
   isPhoneHubNotificationAccessProhibited() {
     return this.pageContentData &&
         this.pageContentData.notificationAccessStatus ===
-        settings.PhoneHubFeatureAccessStatus.PROHIBITED;
+        PhoneHubFeatureAccessStatus.PROHIBITED;
   },
 
   /**
@@ -108,10 +106,9 @@
    * @return {boolean}
    */
   isPhoneHubCameraRollSetupRequired() {
-    return this.isFeatureSupported(
-               settings.MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL) &&
+    return this.isFeatureSupported(MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL) &&
         this.pageContentData.cameraRollAccessStatus ===
-        settings.PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED;
+        PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED;
   },
 
   /**
@@ -119,7 +116,7 @@
    * @return {boolean}
    */
   isPhoneHubAppsSetupRequired() {
-    return this.isFeatureSupported(settings.MultiDeviceFeature.ECHE) &&
+    return this.isFeatureSupported(MultiDeviceFeature.ECHE) &&
         this.pageContentData.isPhoneHubPermissionsDialogSupported &&
         !this.pageContentData.isPhoneHubAppsAccessGranted;
   },
@@ -130,19 +127,19 @@
    */
   isPhoneHubNotificationsSetupRequired() {
     return this.pageContentData.notificationAccessStatus ===
-        settings.PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED;
+        PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED;
   },
 
   /**
    * Whether the user is prevented from attempted to change a given feature. In
    * the UI this corresponds to a disabled toggle.
-   * @param {!settings.MultiDeviceFeature} feature
+   * @param {!MultiDeviceFeature} feature
    * @return {boolean}
    */
   isFeatureStateEditable(feature) {
     // The suite is off and the toggle corresponds to an individual feature
     // (as opposed to the full suite).
-    if (feature !== settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE &&
+    if (feature !== MultiDeviceFeature.BETTER_TOGETHER_SUITE &&
         !this.isSuiteOn()) {
       return false;
     }
@@ -155,43 +152,43 @@
 
     // Cannot edit the Phone Hub notification toggle if notification access is
     // prohibited.
-    if (feature === settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS &&
+    if (feature === MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS &&
         this.isPhoneHubNotificationAccessProhibited()) {
       return false;
     }
 
     return [
-      settings.MultiDeviceFeatureState.DISABLED_BY_USER,
-      settings.MultiDeviceFeatureState.ENABLED_BY_USER
+      MultiDeviceFeatureState.DISABLED_BY_USER,
+      MultiDeviceFeatureState.ENABLED_BY_USER
     ].includes(this.getFeatureState(feature));
   },
 
   /**
    * The localized string representing the name of the feature.
-   * @param {!settings.MultiDeviceFeature} feature
+   * @param {!MultiDeviceFeature} feature
    * @return {string}
    */
   getFeatureName(feature) {
     switch (feature) {
-      case settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE:
+      case MultiDeviceFeature.BETTER_TOGETHER_SUITE:
         return this.i18n('multideviceSetupItemHeading');
-      case settings.MultiDeviceFeature.INSTANT_TETHERING:
+      case MultiDeviceFeature.INSTANT_TETHERING:
         return this.i18n('multideviceInstantTetheringItemTitle');
-      case settings.MultiDeviceFeature.MESSAGES:
+      case MultiDeviceFeature.MESSAGES:
         return this.i18n('multideviceAndroidMessagesItemTitle');
-      case settings.MultiDeviceFeature.SMART_LOCK:
+      case MultiDeviceFeature.SMART_LOCK:
         return this.i18n('multideviceSmartLockItemTitle');
-      case settings.MultiDeviceFeature.PHONE_HUB:
+      case MultiDeviceFeature.PHONE_HUB:
         return this.i18n('multidevicePhoneHubItemTitle');
-      case settings.MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
+      case MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
         return this.i18n('multidevicePhoneHubCameraRollItemTitle');
-      case settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
+      case MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
         return this.i18n('multidevicePhoneHubNotificationsItemTitle');
-      case settings.MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
+      case MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
         return this.i18n('multidevicePhoneHubTaskContinuationItemTitle');
-      case settings.MultiDeviceFeature.WIFI_SYNC:
+      case MultiDeviceFeature.WIFI_SYNC:
         return this.i18n('multideviceWifiSyncItemTitle');
-      case settings.MultiDeviceFeature.ECHE:
+      case MultiDeviceFeature.ECHE:
         return this.i18n('multidevicePhoneHubAppsItemTitle');
       default:
         return '';
@@ -201,24 +198,24 @@
   /**
    * The full icon name used provided by the containing iron-iconset-svg
    * (i.e. [iron-iconset-svg name]:[SVG <g> tag id]) for a given feature.
-   * @param {!settings.MultiDeviceFeature} feature
+   * @param {!MultiDeviceFeature} feature
    * @return {string}
    */
   getIconName(feature) {
     switch (feature) {
-      case settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE:
+      case MultiDeviceFeature.BETTER_TOGETHER_SUITE:
         return 'os-settings:multidevice-better-together-suite';
-      case settings.MultiDeviceFeature.MESSAGES:
+      case MultiDeviceFeature.MESSAGES:
         return 'os-settings:multidevice-messages';
-      case settings.MultiDeviceFeature.SMART_LOCK:
+      case MultiDeviceFeature.SMART_LOCK:
         return 'os-settings:multidevice-smart-lock';
-      case settings.MultiDeviceFeature.PHONE_HUB:
-      case settings.MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
-      case settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
-      case settings.MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
-      case settings.MultiDeviceFeature.ECHE:
+      case MultiDeviceFeature.PHONE_HUB:
+      case MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
+      case MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
+      case MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
+      case MultiDeviceFeature.ECHE:
         return 'os-settings:multidevice-better-together-suite';
-      case settings.MultiDeviceFeature.WIFI_SYNC:
+      case MultiDeviceFeature.WIFI_SYNC:
         return 'os-settings:multidevice-wifi-sync';
       default:
         return '';
@@ -228,29 +225,29 @@
   /**
    * The localized string providing a description or useful status information
    * concerning a given feature.
-   * @param {!settings.MultiDeviceFeature} feature
+   * @param {!MultiDeviceFeature} feature
    * @return {string}
    */
   getFeatureSummaryHtml(feature) {
     switch (feature) {
-      case settings.MultiDeviceFeature.SMART_LOCK:
+      case MultiDeviceFeature.SMART_LOCK:
         return this.i18nAdvanced('multideviceSmartLockItemSummary');
-      case settings.MultiDeviceFeature.INSTANT_TETHERING:
+      case MultiDeviceFeature.INSTANT_TETHERING:
         return this.i18nAdvanced('multideviceInstantTetheringItemSummary');
-      case settings.MultiDeviceFeature.MESSAGES:
+      case MultiDeviceFeature.MESSAGES:
         return this.i18nAdvanced('multideviceAndroidMessagesItemSummary');
-      case settings.MultiDeviceFeature.PHONE_HUB:
+      case MultiDeviceFeature.PHONE_HUB:
         return this.i18nAdvanced('multidevicePhoneHubItemSummary');
-      case settings.MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
+      case MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
         return this.i18nAdvanced('multidevicePhoneHubCameraRollItemSummary');
-      case settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
+      case MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
         return this.i18nAdvanced('multidevicePhoneHubNotificationsItemSummary');
-      case settings.MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
+      case MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
         return this.i18nAdvanced(
             'multidevicePhoneHubTaskContinuationItemSummary');
-      case settings.MultiDeviceFeature.WIFI_SYNC:
+      case MultiDeviceFeature.WIFI_SYNC:
         return this.i18nAdvanced('multideviceWifiSyncItemSummary');
-      case settings.MultiDeviceFeature.ECHE:
+      case MultiDeviceFeature.ECHE:
         return this.i18nAdvanced('multidevicePhoneHubAppsItemSummary');
       default:
         return '';
@@ -261,8 +258,8 @@
    * Extracts the MultiDeviceFeatureState enum value describing the given
    * feature from this.pageContentData. Returns null if the feature is not
    * an accepted value (e.g. testing fake).
-   * @param {!settings.MultiDeviceFeature} feature
-   * @return {?settings.MultiDeviceFeatureState}
+   * @param {!MultiDeviceFeature} feature
+   * @return {?MultiDeviceFeatureState}
    */
   getFeatureState(feature) {
     if (!this.pageContentData) {
@@ -270,25 +267,25 @@
     }
 
     switch (feature) {
-      case settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE:
+      case MultiDeviceFeature.BETTER_TOGETHER_SUITE:
         return this.pageContentData.betterTogetherState;
-      case settings.MultiDeviceFeature.INSTANT_TETHERING:
+      case MultiDeviceFeature.INSTANT_TETHERING:
         return this.pageContentData.instantTetheringState;
-      case settings.MultiDeviceFeature.MESSAGES:
+      case MultiDeviceFeature.MESSAGES:
         return this.pageContentData.messagesState;
-      case settings.MultiDeviceFeature.SMART_LOCK:
+      case MultiDeviceFeature.SMART_LOCK:
         return this.pageContentData.smartLockState;
-      case settings.MultiDeviceFeature.PHONE_HUB:
+      case MultiDeviceFeature.PHONE_HUB:
         return this.pageContentData.phoneHubState;
-      case settings.MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
+      case MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL:
         return this.pageContentData.phoneHubCameraRollState;
-      case settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
+      case MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS:
         return this.pageContentData.phoneHubNotificationsState;
-      case settings.MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
+      case MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION:
         return this.pageContentData.phoneHubTaskContinuationState;
-      case settings.MultiDeviceFeature.WIFI_SYNC:
+      case MultiDeviceFeature.WIFI_SYNC:
         return this.pageContentData.wifiSyncState;
-      case settings.MultiDeviceFeature.ECHE:
+      case MultiDeviceFeature.ECHE:
         return this.pageContentData.phoneHubAppsState;
       default:
         return null;
@@ -301,15 +298,15 @@
    */
   isHostSet() {
     return [
-      settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
-      settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
-      settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED,
+      MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
+      MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
+      MultiDeviceSettingsMode.HOST_SET_VERIFIED,
     ].includes(this.pageContentData.mode);
   },
 };
 
 /** @polymerBehavior */
-/* #export */ const MultiDeviceFeatureBehavior = [
+export const MultiDeviceFeatureBehavior = [
   I18nBehavior,
   MultiDeviceFeatureBehaviorImpl,
 ];
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html
index f34c8f5..9dcc043 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html
@@ -1,125 +1,104 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared">
+  :host([is-sub-feature]) #feature-icon {
+    display: none;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="chrome://resources/cr_components/localized_link/localized_link.html">
-<link rel="import" href="../route_origin_behavior.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_toggle.html">
+  :host([is-sub-feature]) .settings-box .middle {
+    padding-inline-start: 64px;
+  }
 
-<dom-module id="settings-multidevice-feature-item">
-  <template>
-    <style include="settings-shared">
-      :host([is-sub-feature]) #feature-icon {
-        display: none;
-      }
+  #card {
+    border-top: var(--cr-separator-line);
+    border-top-style: var(--feature-item-border-top-style, solid);
+    padding: var(--feature-item-row-padding);
+  }
 
-      :host([is-sub-feature]) .settings-box .middle {
-        padding-inline-start: 64px;
-      }
+  #feature-icon {
+    padding: 2px;
+  }
 
-      #card {
-        border-top: var(--cr-separator-line);
-        border-top-style: var(--feature-item-border-top-style, solid);
-        padding: var(--feature-item-row-padding);
-      }
+  cr-policy-indicator {
+    padding: 0 var(--cr-controlled-by-spacing);
+  }
 
-      #feature-icon {
-        padding: 2px;
-      }
-
-      cr-policy-indicator {
-        padding: 0 var(--cr-controlled-by-spacing);
-      }
-
-      #help-icon:active {
-        /* Still reveal tooltip on click. */
-        pointer-events: none;
-      }
-    </style>
-    <div id="card" class="settings-box two-line no-padding">
-      <div id="linkWrapper" class="link-wrapper"
-           actionable$="[[isRowClickable_(
-               feature, pageContentData, subpageRoute)]]"
-           on-click="handleItemClick_">
-        <template is="dom-if" if="[[!isFeatureIconHidden]]">
-          <slot name="icon">
-            <iron-icon id="feature-icon" icon="[[getIconName(feature)]]"
-                aria-hidden="true">
-            </iron-icon>
-          </slot>
-        </template>
-        <div id="item-text-container"
-          class$="[[getItemTextContainerClassName_(isFeatureIconHidden)]]"
-          aria-hidden="true">
-          <slot name="feature-name">
-            <div id="featureName">[[getFeatureName(feature)]]</div>
-          </slot>
-          <slot name="feature-summary">
-            <localized-link
-                class="secondary"
-                id="featureSecondary"
-                localized-string="[[getFeatureSummaryHtml(feature)]]">
-            </localized-link>
-          </slot>
-        </div>
-        <template is="dom-if"
-            if="[[hasSubpageClickHandler_(feature, pageContentData,
-                      subpageRoute)]]"
-                  restamp>
-          <cr-icon-button id="subpageButton" class="subpage-arrow"
-              aria-labelledby="featureName" aria-describedby="featureSecondary"
-              aria-roledescription="$i18n{subpageArrowRoleDescription}">
-          </cr-icon-button>
-        </template>
-        <template is="dom-if" if="[[iconTooltip]]" restamp>
-          <iron-icon id="help-icon" tabindex="0" icon="[[icon]]"
-              aria-labelledby="tooltip">
-          </iron-icon>
-          <paper-tooltip id="tooltip" for="help-icon" position="top"
-              aria-hidden="true" fit-to-visible-bounds>
-            [[iconTooltip]]
-          </paper-tooltip>
-        </template>
-      </div>
-      <template is="dom-if"
-          if="[[shouldShowSeparator_(
-                    feature, pageContentData, subpageRoute)]]"
-                restamp>
-        <div class="separator"></div>
-      </template>
-
-      <template is="dom-if"
-          if="[[!isFeatureAllowedByPolicy(feature, pageContentData)]]"
-          restamp>
-        <cr-policy-indicator indicator-type="userPolicy"></cr-policy-indicator>
-      </template>
-      <div class="margin-matches-padding" aria-labelledby="featureName"
-          aria-describedby="featureSecondary">
-        <!-- The aria-labelledby and aria-describedby are used by custom slotted
-        content, without which ChromeVox will not announce the #featureName
-        or #featureSummary. Note that the default slotted content still needs
-        their own aria-labelledby and aria-describedby attributes. -->
-        <slot name="feature-controller">
-          <!-- This settings-multidevice-feature-toggle is the default
-          controller. If an element with slot="feature-controller" is attached,
-          it will replace this one. -->
-          <settings-multidevice-feature-toggle
-              aria-labelledby="featureName"
-              aria-describedby="featureSecondary"
-              feature="[[feature]]"
-              page-content-data="[[pageContentData]]">
-          </settings-multidevice-feature-toggle>
-        </slot>
-      </div>
+  #help-icon:active {
+    /* Still reveal tooltip on click. */
+    pointer-events: none;
+  }
+</style>
+<div id="card" class="settings-box two-line no-padding">
+  <div id="linkWrapper" class="link-wrapper"
+        actionable$="[[isRowClickable_(
+            feature, pageContentData, subpageRoute)]]"
+        on-click="handleItemClick_">
+    <template is="dom-if" if="[[!isFeatureIconHidden]]">
+      <slot name="icon">
+        <iron-icon id="feature-icon" icon="[[getIconName(feature)]]"
+            aria-hidden="true">
+        </iron-icon>
+      </slot>
+    </template>
+    <div id="item-text-container"
+      class$="[[getItemTextContainerClassName_(isFeatureIconHidden)]]"
+      aria-hidden="true">
+      <slot name="feature-name">
+        <div id="featureName">[[getFeatureName(feature)]]</div>
+      </slot>
+      <slot name="feature-summary">
+        <localized-link
+            class="secondary"
+            id="featureSecondary"
+            localized-string="[[getFeatureSummaryHtml(feature)]]">
+        </localized-link>
+      </slot>
     </div>
+    <template is="dom-if"
+        if="[[hasSubpageClickHandler_(feature, pageContentData,
+                  subpageRoute)]]"
+              restamp>
+      <cr-icon-button id="subpageButton" class="subpage-arrow"
+          aria-labelledby="featureName" aria-describedby="featureSecondary"
+          aria-roledescription="$i18n{subpageArrowRoleDescription}">
+      </cr-icon-button>
+    </template>
+    <template is="dom-if" if="[[iconTooltip]]" restamp>
+      <iron-icon id="help-icon" tabindex="0" icon="[[icon]]"
+          aria-labelledby="tooltip">
+      </iron-icon>
+      <paper-tooltip id="tooltip" for="help-icon" position="top"
+          aria-hidden="true" fit-to-visible-bounds>
+        [[iconTooltip]]
+      </paper-tooltip>
+    </template>
+  </div>
+  <template is="dom-if"
+      if="[[shouldShowSeparator_(
+                feature, pageContentData, subpageRoute)]]"
+            restamp>
+    <div class="separator"></div>
   </template>
-  <script src="multidevice_feature_item.js"></script>
-</dom-module>
+
+  <template is="dom-if"
+      if="[[!isFeatureAllowedByPolicy(feature, pageContentData)]]"
+      restamp>
+    <cr-policy-indicator indicator-type="userPolicy"></cr-policy-indicator>
+  </template>
+  <div class="margin-matches-padding" aria-labelledby="featureName"
+      aria-describedby="featureSecondary">
+    <!-- The aria-labelledby and aria-describedby are used by custom slotted
+    content, without which ChromeVox will not announce the #featureName
+    or #featureSummary. Note that the default slotted content still needs
+    their own aria-labelledby and aria-describedby attributes. -->
+    <slot name="feature-controller">
+      <!-- This settings-multidevice-feature-toggle is the default
+      controller. If an element with slot="feature-controller" is attached,
+      it will replace this one. -->
+      <settings-multidevice-feature-toggle
+          aria-labelledby="featureName"
+          aria-describedby="featureSecondary"
+          feature="[[feature]]"
+          page-content-data="[[pageContentData]]">
+      </settings-multidevice-feature-toggle>
+    </slot>
+  </div>
+</div>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.js
index 1bf9d9b..cf2e9faf 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.js
@@ -2,6 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '../../settings_shared_css.js';
+import '//resources/cr_components/localized_link/localized_link.js';
+import './multidevice_feature_toggle.js';
+
+import {assert} from '//resources/js/assert.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Route, Router} from '../../router.js';
+import {routes} from '../os_route.m.js';
+import {RouteOriginBehavior} from '../route_origin_behavior.m.js';
+
+import {MultiDeviceFeature} from './multidevice_constants.js';
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview
  * Item for an individual multidevice feature. These features appear in the
@@ -11,19 +28,20 @@
  * feature's autonomous page if there is one.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-feature-item',
 
-  behaviors: [MultiDeviceFeatureBehavior, settings.RouteOriginBehavior],
+  behaviors: [MultiDeviceFeatureBehavior, RouteOriginBehavior],
 
   properties: {
-    /** @type {!settings.MultiDeviceFeature} */
+    /** @type {!MultiDeviceFeature} */
     feature: Number,
 
     /**
      * If it is truthy, the item should be actionable and clicking on it should
      * navigate to the provided route. Otherwise, the item is simply not
      * actionable.
-     * @type {!settings.Route|undefined}
+     * @type {!Route|undefined}
      */
     subpageRoute: Object,
 
@@ -62,8 +80,8 @@
     }
   },
 
-  /** settings.RouteOriginBehavior override */
-  route_: settings.routes.MULTIDEVICE_FEATURES,
+  /** RouteOriginBehavior override */
+  route_: routes.MULTIDEVICE_FEATURES,
 
   ready() {
     this.addFocusConfig(this.subpageRoute, '#subpageButton');
@@ -128,8 +146,8 @@
     // Remove the search term when navigating to avoid potentially having any
     // visible search term reappear at a later time. See
     // https://crbug.com/989119.
-    settings.Router.getInstance().navigateTo(
-        /** @type {!settings.Route} */ (this.subpageRoute),
+    Router.getInstance().navigateTo(
+        /** @type {!Route} */ (this.subpageRoute),
         this.subpageRouteUrlSearchParams, true /* opt_removeSearch */);
   },
 
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.html
index ff3bacf1..37eace30 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.html
@@ -1,18 +1,6 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-
-<dom-module id="settings-multidevice-feature-toggle">
-  <template>
-    <cr-toggle id="toggle"
-        aria-label$="[[getToggleA11yLabel_(feature)]]"
-        checked="{{checked_}}"
-        disabled="[[!isFeatureStateEditable(feature, pageContentData)]]"
-        on-change="toggleFeature">
-    </cr-toggle>
-  </template>
-  <script src="multidevice_feature_toggle.js"></script>
-</dom-module>
+<cr-toggle id="toggle"
+    aria-label$="[[getToggleA11yLabel_(feature)]]"
+    checked="{{checked_}}"
+    disabled="[[!isFeatureStateEditable(feature, pageContentData)]]"
+    on-change="toggleFeature">
+</cr-toggle>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.js
index 3fbcfb7..5fd229f 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.js
@@ -2,6 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_toggle/cr_toggle.m.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {MultiDeviceFeature, MultiDeviceFeatureState} from './multidevice_constants.js';
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview
  * A toggle button specially suited for the MultiDevice Settings UI use-case.
@@ -11,12 +18,13 @@
  * reflects them in the toggle status.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-feature-toggle',
 
   behaviors: [MultiDeviceFeatureBehavior],
 
   properties: {
-    /** @type {!settings.MultiDeviceFeature} */
+    /** @type {!MultiDeviceFeature} */
     feature: Number,
 
     toggleAriaLabel: String,
@@ -63,14 +71,14 @@
    */
   resetChecked_() {
     // If Phone Hub notification access is prohibited, the toggle is always off.
-    if (this.feature === settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS &&
+    if (this.feature === MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS &&
         this.isPhoneHubNotificationAccessProhibited()) {
       this.checked_ = false;
       return;
     }
 
     this.checked_ = this.getFeatureState(this.feature) ===
-        settings.MultiDeviceFeatureState.ENABLED_BY_USER;
+        MultiDeviceFeatureState.ENABLED_BY_USER;
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
index 4d3240f..17ed8019 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
@@ -1,172 +1,152 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-shared-style settings-shared">
+  :host {
+    --cr-dialog-font-family: 'Google Sans';
+    --cr-dialog-title-font-size: 16px;
+  }
 
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="chrome://resources/cr_components/localized_link/localized_link.html">
-<link rel="import" href="../os_icons.html">
-<link rel="import" href="../../settings_shared_css.html">
+  cr-dialog::part(dialog) {
+    width: 512px;
+  }
 
-<dom-module id="settings-multidevice-notification-access-setup-dialog">
-  <template>
-    <style include="cr-shared-style settings-shared">
-      :host {
-        --cr-dialog-font-family: 'Google Sans';
-        --cr-dialog-title-font-size: 16px;
-      }
+  div[slot='title'] {
+    flex-direction: column;
+    height: auto;
+  }
 
-      cr-dialog::part(dialog) {
-        width: 512px;
-      }
+  div[slot='body'] {
+    align-items: center;
+    display: flex;
+    flex-direction: column;
+    height: auto;
+    justify-content: center;
+    width: 464px;
+  }
 
-      div[slot='title'] {
-        flex-direction: column;
-        height: auto;
-      }
+  iron-icon {
+    --iron-icon-fill-color: var(--cros-icon-color-alert);
+    padding-bottom: 13px;
+  }
 
-      div[slot='body'] {
-        align-items: center;
-        display: flex;
-        flex-direction: column;
-        height: auto;
-        justify-content: center;
-        width: 464px;
-      }
+  #description {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
 
-      iron-icon {
-        --iron-icon-fill-color: var(--cros-icon-color-alert);
-        padding-bottom: 13px;
-      }
+  :host(:not([did-setup-attempt-fail_])) #description {
+    /* Larger height to account for the lack of #failureIcon */
+    height: 93px;
+  }
 
-      #description {
-        display: flex;
-        flex-direction: column;
-        gap: 12px;
-      }
+  :host([did-setup-attempt-fail_]) #description {
+    /* Smaller height to account for the presence of #failureIcon */
+    height: 60px;
+  }
 
-      :host(:not([did-setup-attempt-fail_])) #description {
-        /* Larger height to account for the lack of #failureIcon */
-        height: 93px;
-      }
+  #illustration {
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: contain;
+    height: 200px;
+    margin-bottom: 24px;
+    margin-top: 24px;
+    width: 100%;
+  }
 
-      :host([did-setup-attempt-fail_]) #description {
-        /* Smaller height to account for the presence of #failureIcon */
-        height: 60px;
-      }
+  :host([has-not-started-setup-attempt_]) #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_setup.svg);
+  }
 
-      #illustration {
-        background-position: center center;
-        background-repeat: no-repeat;
-        background-size: contain;
-        height: 200px;
-        margin-bottom: 24px;
-        margin-top: 24px;
-        width: 100%;
-      }
+  :host([is-setup-attempt-in-progress_]) #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_connecting.svg);
+  }
 
-      :host([has-not-started-setup-attempt_]) #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_setup.svg);
-      }
+  :host([did-setup-attempt-fail_]) #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_error.svg);
+  }
 
-      :host([is-setup-attempt-in-progress_]) #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_connecting.svg);
-      }
+  :host([has-completed-setup-successfully_]) #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_finished.svg);
+  }
 
-      :host([did-setup-attempt-fail_]) #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_error.svg);
-      }
+  @media(prefers-color-scheme: dark) {
+    :host([has-not-started-setup-attempt_]) #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_setup_dark.svg);
+    }
 
-      :host([has-completed-setup-successfully_]) #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_finished.svg);
-      }
+    :host([is-setup-attempt-in-progress_]) #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_connecting_dark.svg);
+    }
 
-      @media(prefers-color-scheme: dark) {
-        :host([has-not-started-setup-attempt_]) #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_setup_dark.svg);
-        }
+    :host([did-setup-attempt-fail_]) #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_error_dark.svg);
+    }
 
-        :host([is-setup-attempt-in-progress_]) #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_connecting_dark.svg);
-        }
-
-        :host([did-setup-attempt-fail_]) #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_error_dark.svg);
-        }
-
-        :host([has-completed-setup-successfully_]) #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_finished_dark.svg);
-        }
-      }
-    </style>
-    <cr-dialog id="dialog" close-text="$i18n{close}">
-      <div id="dialogTitle" slot="title">
-        <template is="dom-if" if="[[didSetupAttemptFail_]]" restamp>
-          <iron-icon id="failureIcon" icon="os-settings:failure-alert">
-          </iron-icon>
-        </template>
-        <div id="title">[[title_]]</div>
+    :host([has-completed-setup-successfully_]) #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_finished_dark.svg);
+    }
+  }
+</style>
+<cr-dialog id="dialog" close-text="$i18n{close}">
+  <div id="dialogTitle" slot="title">
+    <template is="dom-if" if="[[didSetupAttemptFail_]]" restamp>
+      <iron-icon id="failureIcon" icon="os-settings:failure-alert">
+      </iron-icon>
+    </template>
+    <div id="title">[[title_]]</div>
+  </div>
+  <div id="dialogBody" slot="body">
+    <div id="illustration"></div>
+    <div id="description">
+      <template is="dom-if" if="[[description_]]" restamp>
+        <localized-link localized-string="[[description_]]">
+        </localized-link>
+      </template>
+      <div hidden="[[!shouldShowSetupInstructionsSeparately_]]">
+        $i18n{multideviceNotificationAccessSetupInstructions}
       </div>
-      <div id="dialogBody" slot="body">
-        <div id="illustration"></div>
-        <div id="description">
-          <template is="dom-if" if="[[description_]]" restamp>
-            <localized-link localized-string="[[description_]]">
-            </localized-link>
-          </template>
-          <div hidden="[[!shouldShowSetupInstructionsSeparately_]]">
-            $i18n{multideviceNotificationAccessSetupInstructions}
-          </div>
-        </div>
-      </div>
-      <div id="buttonContainer" slot="button-container">
-        <template is="dom-if" if="[[shouldShowCancelButton_(setupState_)]]"
-            restamp>
-          <cr-button id="cancelButton" class="cancel-button"
-              on-click="onCancelClicked_">
-            $i18n{cancel}
-          </cr-button>
-        </template>
-        <template is="dom-if" if="[[hasCompletedSetupSuccessfully_]]" restamp>
-          <cr-button id="doneButton" class="action-button"
-              on-click="onDoneOrCloseButtonClicked_">
-            $i18n{done}
-          </cr-button>
-        </template>
-        <template is="dom-if" if="[[isNotificationAccessProhibited_]]" restamp>
-          <cr-button id="closeButton" class="action-button"
-              on-click="onDoneOrCloseButtonClicked_">
-            $i18n{close}
-          </cr-button>
-        </template>
-        <template is="dom-if" if="[[hasNotStartedSetupAttempt_]]" restamp>
-          <cr-button id="getStartedButton" class="action-button"
-              on-click="attemptNotificationSetup_">
-            $i18n{multideviceNotificationAccessSetupGetStarted}
-          </cr-button>
-        </template>
-        <template is="dom-if" if="[[shouldShowTryAgainButton_(setupState_)]]"
-            restamp>
-          <cr-button id="tryAgainButton" class="action-button"
-              on-click="attemptNotificationSetup_">
-            $i18n{multideviceNotificationAccessSetupTryAgain}
-          </cr-button>
-        </template>
-      </div>
-    </cr-dialog>
-  </template>
-  <script src="multidevice_notification_access_setup_dialog.js"></script>
-</dom-module>
+    </div>
+  </div>
+  <div id="buttonContainer" slot="button-container">
+    <template is="dom-if" if="[[shouldShowCancelButton_(setupState_)]]"
+        restamp>
+      <cr-button id="cancelButton" class="cancel-button"
+          on-click="onCancelClicked_">
+        $i18n{cancel}
+      </cr-button>
+    </template>
+    <template is="dom-if" if="[[hasCompletedSetupSuccessfully_]]" restamp>
+      <cr-button id="doneButton" class="action-button"
+          on-click="onDoneOrCloseButtonClicked_">
+        $i18n{done}
+      </cr-button>
+    </template>
+    <template is="dom-if" if="[[isNotificationAccessProhibited_]]" restamp>
+      <cr-button id="closeButton" class="action-button"
+          on-click="onDoneOrCloseButtonClicked_">
+        $i18n{close}
+      </cr-button>
+    </template>
+    <template is="dom-if" if="[[hasNotStartedSetupAttempt_]]" restamp>
+      <cr-button id="getStartedButton" class="action-button"
+          on-click="attemptNotificationSetup_">
+        $i18n{multideviceNotificationAccessSetupGetStarted}
+      </cr-button>
+    </template>
+    <template is="dom-if" if="[[shouldShowTryAgainButton_(setupState_)]]"
+        restamp>
+      <cr-button id="tryAgainButton" class="action-button"
+          on-click="attemptNotificationSetup_">
+        $i18n{multideviceNotificationAccessSetupTryAgain}
+      </cr-button>
+    </template>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js
index df54590..f7fb6992 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js
@@ -2,6 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '//resources/cr_components/localized_link/localized_link.js';
+import '../os_icons.js';
+import '../../settings_shared_css.js';
+
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_browser_proxy.js';
+import {MultiDeviceFeature} from './multidevice_constants.js';
+
 /**
  * @fileoverview
  * This element provides the Phone Hub notification access setup flow that, when
@@ -15,7 +30,7 @@
  * CONNECTION_REQUESTED.
  * @enum{number}
  */
-/* #export */ const NotificationAccessSetupOperationStatus = {
+export const NotificationAccessSetupOperationStatus = {
   CONNECTION_REQUESTED: 0,
   CONNECTING: 1,
   TIMED_OUT_CONNECTING: 2,
@@ -26,6 +41,7 @@
 };
 
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-notification-access-setup-dialog',
 
   behaviors: [
@@ -98,12 +114,12 @@
     },
   },
 
-  /** @private {?settings.MultiDeviceBrowserProxy} */
+  /** @private {?MultiDeviceBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   ready() {
-    this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
+    this.browserProxy_ = MultiDeviceBrowserProxyImpl.getInstance();
   },
 
   /** @override */
@@ -123,7 +139,7 @@
     if (this.setupState_ ===
         NotificationAccessSetupOperationStatus.COMPLETED_SUCCESSFULLY) {
       this.browserProxy_.setFeatureEnabledState(
-          settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS, true);
+          MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS, true);
     }
   },
 
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
index 077d679..96c37f5 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
@@ -1,253 +1,215 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../../i18n_setup.html">
-<link rel="import" href="../deep_linking_behavior.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../route_observer_behavior.html">
-<link rel="import" href="../../controls/password_prompt_dialog.html">
-<link rel="import" href="../../settings_page/settings_animated_pages.html">
-<link rel="import" href="../../settings_page/settings_subpage.html">
-<link rel="import" href="../prefs_behavior.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../nearby_share_page/nearby_share_subpage.html">
-<link rel="import" href="../../shared/nearby_share_settings_behavior.html">
-<link rel="import" href="chrome://resources/cr_components/localized_link/localized_link.html">
-<link rel="import" href="../metrics_recorder.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_toggle.html">
-<link rel="import" href="multidevice_notification_access_setup_dialog.html">
-<link rel="import" href="multidevice_permissions_setup_dialog.html">
-<link rel="import" href="multidevice_smartlock_subpage.html">
-<link rel="import" href="multidevice_subpage.html">
-
-<dom-module id="settings-multidevice-page">
-  <template>
-    <style include="settings-shared">
-      cr-policy-indicator {
-        padding: 0 var(--cr-controlled-by-spacing);
-      }
-    </style>
-    <settings-animated-pages id="pages" section="multidevice"
-        focus-config="[[focusConfig_]]">
-      <div route-path="default">
-        <div id="multidevice-item"
-            class="settings-box first two-line no-padding">
-          <div class="link-wrapper" id="suiteLinkWrapper"
-              actionable$="[[doesClickOpenSubpage_(pageContentData)]]"
-              on-click="handleItemClick_">
-            <iron-icon icon=
-                "[[getIconName(MultiDeviceFeature.BETTER_TOGETHER_SUITE)]]"
-                id="betterTogetherSuiteIcon">
-            </iron-icon>
-            <div class="middle settings-box-text"
-                aria-hidden$="[[getTextAriaHidden_(pageContentData)]]">
-              <div id="multidevice-label">
-                [[getLabelText_(pageContentData)]]
-              </div>
-              <localized-link id="multideviceSubLabel"
-                  class="secondary"
-                  localized-string="[[getSubLabelInnerHtml_(pageContentData)]]">
-              </localized-link>
-            </div>
-            <template is="dom-if"
-                      if="[[doesClickOpenSubpage_(pageContentData)]]"
-                      restamp>
-              <cr-icon-button class="subpage-arrow"
-                  aria-labelledby="multidevice-label"
-                  aria-describedby="multideviceSubLabel"
-                  aria-roledescription="$i18n{subpageArrowRoleDescription}">
-              </cr-icon-button>
-            </template>
+<style include="settings-shared">
+  cr-policy-indicator {
+    padding: 0 var(--cr-controlled-by-spacing);
+  }
+</style>
+<settings-animated-pages id="pages" section="multidevice"
+    focus-config="[[focusConfig_]]">
+  <div route-path="default">
+    <div id="multidevice-item"
+        class="settings-box first two-line no-padding">
+      <div class="link-wrapper" id="suiteLinkWrapper"
+          actionable$="[[doesClickOpenSubpage_(pageContentData)]]"
+          on-click="handleItemClick_">
+        <iron-icon icon=
+            "[[getIconName(MultiDeviceFeature.BETTER_TOGETHER_SUITE)]]"
+            id="betterTogetherSuiteIcon">
+        </iron-icon>
+        <div class="middle settings-box-text"
+            aria-hidden$="[[getTextAriaHidden_(pageContentData)]]">
+          <div id="multidevice-label">
+            [[getLabelText_(pageContentData)]]
           </div>
-          <template is="dom-if"
-                    if="[[!isSuiteAllowedByPolicy(pageContentData)]]"
-                    restamp>
-            <cr-policy-indicator id="suitePolicyIndicator"
-                                 indicator-type="userPolicy">
-            </cr-policy-indicator>
-            <settings-multidevice-feature-toggle
-                class="margin-matches-padding"
-                toggle-aria-label="$i18n{multideviceSuiteToggleA11yLabel}"
-                feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
-                page-content-data="[[pageContentData]]"
-                deep-link-focus-id$="[[Setting.kMultiDeviceOnOff]]">
-            </settings-multidevice-feature-toggle>
-          </template>
-          <template is="dom-if"
-              if="[[shouldShowSeparatorAndSubpageArrow_(pageContentData)]]"
-              restamp>
-            <div class="separator"></div>
-          </template>
-          <template is="dom-if"
-              if="[[shouldShowButton_(pageContentData)]]"
-              restamp>
-            <cr-button class="margin-matches-padding"
-                on-click="handleButtonClick_"
-                aria-describedby="multideviceSubLabel"
-                deep-link-focus-id$="[[Setting.kSetUpMultiDevice]]
-                    [[Setting.kVerifyMultiDeviceSetup]]">
-              [[getButtonText_(pageContentData)]]
-            </cr-button>
-          </template>
-          <template is="dom-if"
-              if="[[shouldShowToggle_(pageContentData)]]"
-              restamp>
-            <settings-multidevice-feature-toggle
-                class="margin-matches-padding"
-                toggle-aria-label="$i18n{multideviceSuiteToggleA11yLabel}"
-                feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
-                page-content-data="[[pageContentData]]"
-                deep-link-focus-id$="[[Setting.kMultiDeviceOnOff]]">
-            </settings-multidevice-feature-toggle>
-          </template>
+          <localized-link id="multideviceSubLabel"
+              class="secondary"
+              localized-string="[[getSubLabelInnerHtml_(pageContentData)]]">
+          </localized-link>
         </div>
-        <template is="dom-if" if="[[isNearbyShareSupported_]]" restamp>
-          <div id="nearbyshare-item" class="settings-box two-line no-padding">
-            <div class="link-wrapper" id="nearbyLinkWrapper"
-                actionable$=
-                    "[[!isNearbyShareDisallowedByPolicy_(pageContentData)]]"
-                on-click="nearbyShareClick_">
-              <iron-icon icon="os-settings:nearby-share">
-              </iron-icon>
-              <div id="nearbyShareLabel" class="middle settings-box-text">
-                <div aria-hidden="true">
-                  $i18n{nearbyShareTitle}
-                </div>
-                <template is="dom-if" if="[[showNearbyShareOnOffString_(
-                          prefs.nearby_sharing.onboarding_complete.value,
-                          pageContentData)]]" restamp>
-                  <div class="secondary" id="nearbyShareSecondary">
-                    [[getOnOffString_(prefs.nearby_sharing.enabled.value,
-                      '$i18nPolymer{deviceOn}', '$i18nPolymer{deviceOff}')]]
-                  </div>
-                </template>
-                <template is="dom-if" if="[[showNearbyShareDescription_(
-                          prefs.nearby_sharing.onboarding_complete.value,
-                          pageContentData)]]" restamp>
-                  <div class="secondary" id="nearbyShareSecondary">
-                    <localized-link
-                        localized-string="$i18n{nearbyShareDescription}"
-                        link-url="$i18n{nearbyShareLearnMoreLink}">
-                    </localized-link>
-                  </div>
-                </template>
-              </div>
-              <template is="dom-if" if="[[shouldShowNearbyShareSubpageArrow_(
-                  prefs.nearby_sharing.enabled.value,
-                  shouldEnableNearbyShareBackgroundScanningRevamp_,
-                  pageContentData)]]" restamp>
-                <cr-icon-button id="nearbyShareSubpageArrow"
-                    class="subpage-arrow"
-                    aria-labelledby="nearbyShareLabel"
-                    aria-describedby="nearbyShareSecondary"
-                    aria-roledescription="$i18n{subpageArrowRoleDescription}">
-                </cr-icon-button>
-              </template>
-            </div>
-            <template is="dom-if"
-                if="[[isNearbyShareDisallowedByPolicy_(pageContentData)]]"
-                restamp>
-              <cr-policy-indicator id="nearbyPolicyIndicator"
-                                   indicator-type="userPolicy">
-              </cr-policy-indicator>
-            </template>
-            <template is="dom-if"
-                if="[[!isNearbyShareDisallowedByPolicy_(pageContentData)]]"
-                restamp>
-              <div class="separator"></div>
-            </template>
-            <template is="dom-if" if="[[showNearbyShareToggle_(
-                      prefs.nearby_sharing.onboarding_complete.value,
-                      pageContentData)]]" restamp>
-              <cr-toggle id="nearbySharingToggleButton"
-                  class="margin-matches-padding"
-                  aria-label="$i18n{nearbyShareTitle}"
-                  checked="{{prefs.nearby_sharing.enabled.value}}"
-                  disabled=
-                      "[[isNearbyShareDisallowedByPolicy_(pageContentData)]]"
-                  deep-link-focus-id$="[[Setting.kNearbyShareOnOff]]">
-              </cr-toggle>
-            </template>
-            <template is="dom-if" if="[[showNearbyShareSetupButton_(
-                      prefs.nearby_sharing.onboarding_complete.value,
-                      pageContentData)]]" restamp>
-              <cr-button class="margin-matches-padding"
-                  id="nearbySetUp"
-                  on-click="handleNearbySetUpClick_"
-                  disabled=
-                      "[[isNearbyShareDisallowedByPolicy_(pageContentData)]]">
-                $i18n{nearbyShareSetUpButtonTitle}
-              </cr-button>
-            </template>
-          </div>
+        <template is="dom-if"
+                  if="[[doesClickOpenSubpage_(pageContentData)]]"
+                  restamp>
+          <cr-icon-button class="subpage-arrow"
+              aria-labelledby="multidevice-label"
+              aria-describedby="multideviceSubLabel"
+              aria-roledescription="$i18n{subpageArrowRoleDescription}">
+          </cr-icon-button>
         </template>
       </div>
-      <template is="dom-if" route-path="/multidevice/features" restamp>
-        <settings-subpage page-title="[[getLabelText_(pageContentData)]]">
-          <settings-multidevice-subpage
-              page-content-data="[[pageContentData]]">
-          </settings-multidevice-subpage>
-        </settings-subpage>
+      <template is="dom-if"
+                if="[[!isSuiteAllowedByPolicy(pageContentData)]]"
+                restamp>
+        <cr-policy-indicator id="suitePolicyIndicator"
+                              indicator-type="userPolicy">
+        </cr-policy-indicator>
+        <settings-multidevice-feature-toggle
+            class="margin-matches-padding"
+            toggle-aria-label="$i18n{multideviceSuiteToggleA11yLabel}"
+            feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
+            page-content-data="[[pageContentData]]"
+            deep-link-focus-id$="[[Setting.kMultiDeviceOnOff]]">
+        </settings-multidevice-feature-toggle>
       </template>
-      <template is="dom-if" route-path="/multidevice/features/smartLock"
+      <template is="dom-if"
+          if="[[shouldShowSeparatorAndSubpageArrow_(pageContentData)]]"
           restamp>
-        <settings-subpage page-title="$i18n{easyUnlockSectionTitle}">
-          <settings-multidevice-smartlock-subpage
-              prefs="{{prefs}}"
-              page-content-data="[[pageContentData]]">
-          </settings-multidevice-smartlock-subpage>
-        </settings-subpage>
+        <div class="separator"></div>
       </template>
-      <template is="dom-if" if="[[isNearbyShareSupported_]]" restamp>
-        <template is="dom-if" route-path="/multidevice/nearbyshare" restamp>
-          <settings-subpage page-title="$i18n{nearbyShareTitle}">
-            <settings-nearby-share-subpage settings="{{settings}}"
-                prefs="{{prefs}}"
-                is-settings-retreived="[[isSettingsRetreived]]">
-            </settings-nearby-share-subpage>
-          </settings-subpage>
+      <template is="dom-if"
+          if="[[shouldShowButton_(pageContentData)]]"
+          restamp>
+        <cr-button class="margin-matches-padding"
+            on-click="handleButtonClick_"
+            aria-describedby="multideviceSubLabel"
+            deep-link-focus-id$="[[Setting.kSetUpMultiDevice]]
+                [[Setting.kVerifyMultiDeviceSetup]]">
+          [[getButtonText_(pageContentData)]]
+        </cr-button>
+      </template>
+      <template is="dom-if"
+          if="[[shouldShowToggle_(pageContentData)]]"
+          restamp>
+        <settings-multidevice-feature-toggle
+            class="margin-matches-padding"
+            toggle-aria-label="$i18n{multideviceSuiteToggleA11yLabel}"
+            feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
+            page-content-data="[[pageContentData]]"
+            deep-link-focus-id$="[[Setting.kMultiDeviceOnOff]]">
+        </settings-multidevice-feature-toggle>
+      </template>
+    </div>
+    <template is="dom-if" if="[[isNearbyShareSupported_]]" restamp>
+      <div id="nearbyshare-item" class="settings-box two-line no-padding">
+        <div class="link-wrapper" id="nearbyLinkWrapper"
+            actionable$=
+                "[[!isNearbyShareDisallowedByPolicy_(pageContentData)]]"
+            on-click="nearbyShareClick_">
+          <iron-icon icon="os-settings:nearby-share">
+          </iron-icon>
+          <div id="nearbyShareLabel" class="middle settings-box-text">
+            <div aria-hidden="true">
+              $i18n{nearbyShareTitle}
+            </div>
+            <template is="dom-if" if="[[showNearbyShareOnOffString_(
+                      prefs.nearby_sharing.onboarding_complete.value,
+                      pageContentData)]]" restamp>
+              <div class="secondary" id="nearbyShareSecondary">
+                [[getOnOffString_(prefs.nearby_sharing.enabled.value,
+                  '$i18nPolymer{deviceOn}', '$i18nPolymer{deviceOff}')]]
+              </div>
+            </template>
+            <template is="dom-if" if="[[showNearbyShareDescription_(
+                      prefs.nearby_sharing.onboarding_complete.value,
+                      pageContentData)]]" restamp>
+              <div class="secondary" id="nearbyShareSecondary">
+                <localized-link
+                    localized-string="$i18n{nearbyShareDescription}"
+                    link-url="$i18n{nearbyShareLearnMoreLink}">
+                </localized-link>
+              </div>
+            </template>
+          </div>
+          <template is="dom-if" if="[[shouldShowNearbyShareSubpageArrow_(
+              prefs.nearby_sharing.enabled.value,
+              shouldEnableNearbyShareBackgroundScanningRevamp_,
+              pageContentData)]]" restamp>
+            <cr-icon-button id="nearbyShareSubpageArrow"
+                class="subpage-arrow"
+                aria-labelledby="nearbyShareLabel"
+                aria-describedby="nearbyShareSecondary"
+                aria-roledescription="$i18n{subpageArrowRoleDescription}">
+            </cr-icon-button>
+          </template>
+        </div>
+        <template is="dom-if"
+            if="[[isNearbyShareDisallowedByPolicy_(pageContentData)]]"
+            restamp>
+          <cr-policy-indicator id="nearbyPolicyIndicator"
+                                indicator-type="userPolicy">
+          </cr-policy-indicator>
         </template>
-      </template>
-    </settings-animated-pages>
-    <template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
-      <settings-password-prompt-dialog id="multidevicePasswordPrompt"
-          on-token-obtained="onTokenObtained_">
-      </settings-password-prompt-dialog>
+        <template is="dom-if"
+            if="[[!isNearbyShareDisallowedByPolicy_(pageContentData)]]"
+            restamp>
+          <div class="separator"></div>
+        </template>
+        <template is="dom-if" if="[[showNearbyShareToggle_(
+                  prefs.nearby_sharing.onboarding_complete.value,
+                  pageContentData)]]" restamp>
+          <cr-toggle id="nearbySharingToggleButton"
+              class="margin-matches-padding"
+              aria-label="$i18n{nearbyShareTitle}"
+              checked="{{prefs.nearby_sharing.enabled.value}}"
+              disabled=
+                  "[[isNearbyShareDisallowedByPolicy_(pageContentData)]]"
+              deep-link-focus-id$="[[Setting.kNearbyShareOnOff]]">
+          </cr-toggle>
+        </template>
+        <template is="dom-if" if="[[showNearbyShareSetupButton_(
+                  prefs.nearby_sharing.onboarding_complete.value,
+                  pageContentData)]]" restamp>
+          <cr-button class="margin-matches-padding"
+              id="nearbySetUp"
+              on-click="handleNearbySetUpClick_"
+              disabled=
+                  "[[isNearbyShareDisallowedByPolicy_(pageContentData)]]">
+            $i18n{nearbyShareSetUpButtonTitle}
+          </cr-button>
+        </template>
+      </div>
     </template>
-    <template is="dom-if" if="[[showPermissionsSetupDialog_(
-        showPhonePermissionSetupDialog_)]]"
-        restamp>
-      <settings-multidevice-notification-access-setup-dialog
-          is-password-dialog-showing="{{isPasswordDialogShowing_}}"
-          on-close="onHidePhonePermissionsSetupDialog_">
-      </settings-multidevice-notification-access-setup-dialog>
-    </template>
-    <template is="dom-if"
-        if="[[showNewPermissionsSetupDialog_(
-        showPhonePermissionSetupDialog_)]]"
-        restamp>
-      <settings-multidevice-permissions-setup-dialog
-          is-password-dialog-showing="{{isPasswordDialogShowing_}}"
-          show-camera-roll="[[isPhoneHubCameraRollSetupRequired(
-                                            pageContentData)]]"
-          show-notifications="[[isPhoneHubNotificationsSetupRequired(
-                                              pageContentData)]]"
-          show-app-streaming="[[isPhoneHubAppsSetupRequired(
-                                              pageContentData)]]"
-          on-close="onHidePhonePermissionsSetupDialog_">
-      </settings-multidevice-permissions-setup-dialog>
+  </div>
+  <template is="dom-if" route-path="/multidevice/features" restamp>
+    <settings-subpage page-title="[[getLabelText_(pageContentData)]]">
+      <settings-multidevice-subpage
+          page-content-data="[[pageContentData]]">
+      </settings-multidevice-subpage>
+    </settings-subpage>
+  </template>
+  <template is="dom-if" route-path="/multidevice/features/smartLock"
+      restamp>
+    <settings-subpage page-title="$i18n{easyUnlockSectionTitle}">
+      <settings-multidevice-smartlock-subpage
+          prefs="{{prefs}}"
+          page-content-data="[[pageContentData]]">
+      </settings-multidevice-smartlock-subpage>
+    </settings-subpage>
+  </template>
+  <template is="dom-if" if="[[isNearbyShareSupported_]]" restamp>
+    <template is="dom-if" route-path="/multidevice/nearbyshare" restamp>
+      <settings-subpage page-title="$i18n{nearbyShareTitle}">
+        <settings-nearby-share-subpage settings="{{settings}}"
+            prefs="{{prefs}}"
+            is-settings-retreived="[[isSettingsRetreived]]">
+        </settings-nearby-share-subpage>
+      </settings-subpage>
     </template>
   </template>
-  <script src="multidevice_page.js"></script>
-</dom-module>
+</settings-animated-pages>
+<template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
+  <settings-password-prompt-dialog id="multidevicePasswordPrompt"
+      on-token-obtained="onTokenObtained_">
+  </settings-password-prompt-dialog>
+</template>
+<template is="dom-if" if="[[showPermissionsSetupDialog_(
+    showPhonePermissionSetupDialog_)]]"
+    restamp>
+  <settings-multidevice-notification-access-setup-dialog
+      is-password-dialog-showing="{{isPasswordDialogShowing_}}"
+      on-close="onHidePhonePermissionsSetupDialog_">
+  </settings-multidevice-notification-access-setup-dialog>
+</template>
+<template is="dom-if"
+    if="[[showNewPermissionsSetupDialog_(
+    showPhonePermissionSetupDialog_)]]"
+    restamp>
+  <settings-multidevice-permissions-setup-dialog
+      is-password-dialog-showing="{{isPasswordDialogShowing_}}"
+      show-camera-roll="[[isPhoneHubCameraRollSetupRequired(
+                                        pageContentData)]]"
+      show-notifications="[[isPhoneHubNotificationsSetupRequired(
+                                          pageContentData)]]"
+      show-app-streaming="[[isPhoneHubAppsSetupRequired(
+                                          pageContentData)]]"
+      on-close="onHidePhonePermissionsSetupDialog_">
+  </settings-multidevice-permissions-setup-dialog>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
index 003d8e2..6660c982 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
@@ -2,13 +2,46 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
+import '//resources/cr_elements/cr_toggle/cr_toggle.m.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '../../controls/password_prompt_dialog.js';
+import '../../settings_page/settings_animated_pages.js';
+import '../../settings_page/settings_subpage.js';
+import '../../settings_shared_css.js';
+import '../nearby_share_page/nearby_share_subpage.js';
+import '//resources/cr_components/localized_link/localized_link.js';
+import './multidevice_feature_toggle.js';
+import './multidevice_notification_access_setup_dialog.js';
+import './multidevice_permissions_setup_dialog.js';
+import './multidevice_smartlock_subpage.js';
+import './multidevice_subpage.js';
+
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {beforeNextRender, html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {loadTimeData} from '../../i18n_setup.js';
+import {Route, Router} from '../../router.js';
+import {NearbyShareSettingsBehavior} from '../../shared/nearby_share_settings_behavior.m.js';
+import {DeepLinkingBehavior} from '../deep_linking_behavior.js';
+import {recordSettingChange} from '../metrics_recorder.m.js';
+import {routes} from '../os_route.m.js';
+import {PrefsBehavior} from '../prefs_behavior.js';
+import {RouteObserverBehavior} from '../route_observer_behavior.js';
+
+import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_browser_proxy.js';
+import {MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, PhoneHubFeatureAccessStatus} from './multidevice_constants.js';
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-page',
 
   behaviors: [
-    DeepLinkingBehavior, settings.RouteObserverBehavior,
-    MultiDeviceFeatureBehavior, WebUIListenerBehavior, PrefsBehavior,
-    nearby_share.NearbyShareSettingsBehavior
+    DeepLinkingBehavior, RouteObserverBehavior, MultiDeviceFeatureBehavior,
+    WebUIListenerBehavior, PrefsBehavior, NearbyShareSettingsBehavior
   ],
 
   properties: {
@@ -17,7 +50,7 @@
 
     /**
      * A Map specifying which element should be focused when exiting a subpage.
-     * The key of the map holds a settings.Route path, and the value holds a
+     * The key of the map holds a Route path, and the value holds a
      * query selector that identifies the desired element.
      * @private {!Map<string, string>}
      */
@@ -25,9 +58,9 @@
       type: Object,
       value() {
         const map = new Map();
-        if (settings.routes.MULTIDEVICE_FEATURES) {
+        if (routes.MULTIDEVICE_FEATURES) {
           map.set(
-              settings.routes.MULTIDEVICE_FEATURES.path,
+              routes.MULTIDEVICE_FEATURES.path,
               '#multidevice-item .subpage-arrow');
         }
         return map;
@@ -48,7 +81,7 @@
      * required. This value is initialized to null, is set when the password
      * dialog is opened, and is reset to null again once the password dialog is
      * closed.
-     * @private {?settings.MultiDeviceFeature}
+     * @private {?MultiDeviceFeature}
      */
     featureToBeEnabledOnceAuthenticated_: {
       type: Number,
@@ -123,12 +156,12 @@
     'permission-setup-requested': 'onPermissionSetupRequested_',
   },
 
-  /** @private {?settings.MultiDeviceBrowserProxy} */
+  /** @private {?MultiDeviceBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   ready() {
-    this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
+    this.browserProxy_ = MultiDeviceBrowserProxyImpl.getInstance();
 
     this.addWebUIListener(
         'settings.updateMultidevicePageContentData',
@@ -139,23 +172,23 @@
   },
 
   /**
-   * Overridden from nearby_share.NearbyShareSettingsBehavior.
+   * Overridden from NearbyShareSettingsBehavior.
    */
   onSettingsRetrieved() {
     this.isSettingsRetreived = true;
   },
 
   /**
-   * Overridden from settings.RouteObserverBehavior.
-   * @param {!settings.Route} route
-   * @param {!settings.Route} oldRoute
+   * Overridden from RouteObserverBehavior.
+   * @param {!Route} route
+   * @param {!Route} oldRoute
    * @protected
    */
   currentRouteChanged(route, oldRoute) {
     this.leaveNestedPageIfNoHostIsSet_();
 
     // Does not apply to this page.
-    if (route !== settings.routes.MULTIDEVICE) {
+    if (route !== routes.MULTIDEVICE) {
       return;
     }
 
@@ -180,13 +213,13 @@
       return this.i18nAdvanced('multideviceSetupSummary');
     }
     switch (this.pageContentData.mode) {
-      case settings.MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS:
+      case MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS:
         return this.i18nAdvanced('multideviceNoHostText');
-      case settings.MultiDeviceSettingsMode.NO_HOST_SET:
+      case MultiDeviceSettingsMode.NO_HOST_SET:
         return this.i18nAdvanced('multideviceSetupSummary');
-      case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
+      case MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
       // Intentional fall-through.
-      case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
+      case MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
         return this.i18nAdvanced('multideviceVerificationText');
       default:
         return this.isSuiteOn() ? this.i18n('multideviceEnabled') :
@@ -200,11 +233,11 @@
    */
   getButtonText_() {
     switch (this.pageContentData.mode) {
-      case settings.MultiDeviceSettingsMode.NO_HOST_SET:
+      case MultiDeviceSettingsMode.NO_HOST_SET:
         return this.i18n('multideviceSetupButton');
-      case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
+      case MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
       // Intentional fall-through.
-      case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
+      case MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
         return this.i18n('multideviceVerifyButton');
       default:
         return '';
@@ -223,7 +256,7 @@
     // so the text is still available to assistive tools.
     return String(
         this.pageContentData.mode ===
-        settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED);
+        MultiDeviceSettingsMode.HOST_SET_VERIFIED);
   },
 
   /**
@@ -232,9 +265,9 @@
    */
   shouldShowButton_() {
     return [
-      settings.MultiDeviceSettingsMode.NO_HOST_SET,
-      settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
-      settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
+      MultiDeviceSettingsMode.NO_HOST_SET,
+      MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
+      MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
     ].includes(this.pageContentData.mode);
   },
 
@@ -244,7 +277,7 @@
    */
   shouldShowToggle_() {
     return this.pageContentData.mode ===
-        settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED;
+        MultiDeviceSettingsMode.HOST_SET_VERIFIED;
   },
 
   /**
@@ -255,7 +288,7 @@
    */
   shouldShowSeparatorAndSubpageArrow_() {
     return this.pageContentData.mode !==
-        settings.MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS;
+        MultiDeviceSettingsMode.NO_ELIGIBLE_HOSTS;
   },
 
   /**
@@ -278,20 +311,19 @@
       return;
     }
 
-    settings.Router.getInstance().navigateTo(
-        settings.routes.MULTIDEVICE_FEATURES);
+    Router.getInstance().navigateTo(routes.MULTIDEVICE_FEATURES);
   },
 
   /** @private */
   handleButtonClick_(event) {
     event.stopPropagation();
     switch (this.pageContentData.mode) {
-      case settings.MultiDeviceSettingsMode.NO_HOST_SET:
+      case MultiDeviceSettingsMode.NO_HOST_SET:
         this.browserProxy_.showMultiDeviceSetupDialog();
         return;
-      case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
+      case MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
       // Intentional fall-through.
-      case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
+      case MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
         // If this device is waiting for action on the server or the host
         // device, clicking the button should trigger this action.
         this.browserProxy_.retryPendingHostSetup();
@@ -324,7 +356,7 @@
       this.browserProxy_.setFeatureEnabledState(
           this.featureToBeEnabledOnceAuthenticated_, true /* enabled */,
           this.authToken_.token);
-      settings.recordSettingChange();
+      recordSettingChange();
 
       // Reset |this.authToken_| now that it has been used. This ensures that
       // users cannot keep an old auth token and reuse it on an subsequent
@@ -347,7 +379,7 @@
    * authentication process.
    *
    * @param {!CustomEvent<!{
-   *     feature: !settings.MultiDeviceFeature,
+   *     feature: !MultiDeviceFeature,
    *     enabled: boolean
    * }>} event
    * @private
@@ -367,13 +399,12 @@
 
     // If the feature to enable is Phone Hub Notifications, notification access
     // must have been granted before the feature can be enabled.
-    if (feature === settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS &&
-        enabled) {
+    if (feature === MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS && enabled) {
       switch (this.pageContentData.notificationAccessStatus) {
-        case settings.PhoneHubFeatureAccessStatus.PROHIBITED:
+        case PhoneHubFeatureAccessStatus.PROHIBITED:
           assertNotReached('Cannot enable notification access; prohibited');
           return;
-        case settings.PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED:
+        case PhoneHubFeatureAccessStatus.AVAILABLE_BUT_NOT_GRANTED:
           this.showPhonePermissionSetupDialog_ = true;
           return;
         default:
@@ -385,28 +416,27 @@
     // Disabling any feature does not require authentication, and enable some
     // features does not require authentication.
     this.browserProxy_.setFeatureEnabledState(feature, enabled);
-    settings.recordSettingChange();
+    recordSettingChange();
   },
 
   /**
-   * @param {!settings.MultiDeviceFeature} feature The feature to enable.
+   * @param {!MultiDeviceFeature} feature The feature to enable.
    * @return {boolean} Whether authentication is required to enable the feature.
    * @private
    */
   isAuthenticationRequiredToEnable_(feature) {
     // Enabling SmartLock always requires authentication.
-    if (feature === settings.MultiDeviceFeature.SMART_LOCK) {
+    if (feature === MultiDeviceFeature.SMART_LOCK) {
       return true;
     }
 
     // Enabling any feature besides SmartLock and the Better Together suite does
     // not require authentication.
-    if (feature !== settings.MultiDeviceFeature.BETTER_TOGETHER_SUITE) {
+    if (feature !== MultiDeviceFeature.BETTER_TOGETHER_SUITE) {
       return false;
     }
 
-    const smartLockState =
-        this.getFeatureState(settings.MultiDeviceFeature.SMART_LOCK);
+    const smartLockState = this.getFeatureState(MultiDeviceFeature.SMART_LOCK);
 
     // If the user is enabling the Better Together suite and this change would
     // result in SmartLock being implicitly enabled, authentication is required.
@@ -414,16 +444,16 @@
     // to the suite being disabled or due to the SmartLock host device not
     // having a lock screen set.
     return smartLockState ===
-        settings.MultiDeviceFeatureState.UNAVAILABLE_SUITE_DISABLED ||
+        MultiDeviceFeatureState.UNAVAILABLE_SUITE_DISABLED ||
         smartLockState ===
-        settings.MultiDeviceFeatureState.UNAVAILABLE_INSUFFICIENT_SECURITY;
+        MultiDeviceFeatureState.UNAVAILABLE_INSUFFICIENT_SECURITY;
   },
 
   /** @private */
   onForgetDeviceRequested_() {
     this.browserProxy_.removeHostDevice();
-    settings.recordSettingChange();
-    settings.Router.getInstance().navigateTo(settings.routes.MULTIDEVICE);
+    recordSettingChange();
+    Router.getInstance().navigateTo(routes.MULTIDEVICE);
   },
 
   /** @private */
@@ -444,28 +474,25 @@
 
     // Host status doesn't matter if we are navigating to Nearby Share
     // settings.
-    if (settings.routes.NEARBY_SHARE ===
-        settings.Router.getInstance().getCurrentRoute()) {
+    if (routes.NEARBY_SHARE === Router.getInstance().getCurrentRoute()) {
       return;
     }
 
     // If the user gets to the a nested page without a host (e.g. by clicking a
     // stale 'existing user' notifications after forgetting their host) we
     // direct them back to the main settings page.
-    if (settings.routes.MULTIDEVICE !==
-            settings.Router.getInstance().getCurrentRoute() &&
-        settings.routes.MULTIDEVICE.contains(
-            settings.Router.getInstance().getCurrentRoute()) &&
+    if (routes.MULTIDEVICE !== Router.getInstance().getCurrentRoute() &&
+        routes.MULTIDEVICE.contains(Router.getInstance().getCurrentRoute()) &&
         !this.isHostSet()) {
       // Render MULTIDEVICE page before the MULTIDEVICE_FEATURES has a chance.
-      Polymer.RenderStatus.beforeNextRender(this, () => {
-        settings.Router.getInstance().navigateTo(settings.routes.MULTIDEVICE);
+      beforeNextRender(this, () => {
+        Router.getInstance().navigateTo(routes.MULTIDEVICE);
       });
     }
   },
 
   /**
-   * @param {!settings.MultiDevicePageContentData} newData
+   * @param {!MultiDevicePageContentData} newData
    * @private
    */
   onInitialPageContentDataFetched_(newData) {
@@ -474,14 +501,14 @@
     // Show the notification access dialog if the url contains the correct
     // param.
     // Show combined access dialog with URL having param and features.
-    const urlParams = settings.Router.getInstance().getQueryParameters();
+    const urlParams = Router.getInstance().getQueryParameters();
     if (urlParams.get('showPhonePermissionSetupDialog') !== null) {
       this.showPhonePermissionSetupDialog_ = true;
     }
   },
 
   /**
-   * @param {!settings.MultiDevicePageContentData} newData
+   * @param {!MultiDevicePageContentData} newData
    * @private
    */
   onPageContentDataChanged_(newData) {
@@ -573,7 +600,7 @@
     // whether Nearby Share is on or off so that users can enable/disable the
     // "Nearby device is trying to share" notification.
     if (this.shouldEnableNearbyShareBackgroundScanningRevamp_) {
-      settings.Router.getInstance().navigateTo(settings.routes.NEARBY_SHARE);
+      Router.getInstance().navigateTo(routes.NEARBY_SHARE);
       return;
     }
 
@@ -594,8 +621,7 @@
       params.set('entrypoint', 'settings');
       params.set('onboarding', '');
     }
-    settings.Router.getInstance().navigateTo(
-        settings.routes.NEARBY_SHARE, params);
+    Router.getInstance().navigateTo(routes.NEARBY_SHARE, params);
   },
 
 
@@ -636,8 +662,7 @@
     params.set('onboarding', '');
     // Set by metrics to determine entrypoint for onboarding
     params.set('entrypoint', 'settings');
-    settings.Router.getInstance().navigateTo(
-        settings.routes.NEARBY_SHARE, params);
+    Router.getInstance().navigateTo(routes.NEARBY_SHARE, params);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html
index adf5fdfb..9c4d569 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html
@@ -1,367 +1,344 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-shared-style settings-shared">
+  cr-dialog::part(dialog) {
+    width: 512px;
+  }
 
-<link rel="import" href="chrome://resources/cr_components/localized_link/localized_link.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_screen_lock_subpage.html">
-<link rel="import" href="../os_icons.html">
-<link rel="import" href="../../settings_shared_css.html">
+  #dialogTitle {
+    --cr-dialog-title-slot-padding-bottom: 24px;
+    --cr-dialog-title-slot-padding-end: 24px;
+    --cr-dialog-title-slot-padding-start: 24px;
+    --cr-dialog-title-slot-padding-top: 24px;
+  }
 
-<dom-module id="settings-multidevice-permissions-setup-dialog">
-  <template>
-    <style include="cr-shared-style settings-shared">
-      cr-dialog::part(dialog) {
-        width: 512px;
-      }
+  :host(:not([has-started-setup-attempt_])) #dialogTitle {
+    --cr-dialog-title-slot-padding-bottom: 20px;
+  }
 
-      #dialogTitle {
-        --cr-dialog-title-slot-padding-bottom: 24px;
-        --cr-dialog-title-slot-padding-end: 24px;
-        --cr-dialog-title-slot-padding-start: 24px;
-        --cr-dialog-title-slot-padding-top: 24px;
-      }
+  #title {
+    align-items: center;
+    color: var(--cros-text-color-primary);
+    display: flex;
+    flex-direction: row;
+    font-family: 'Google Sans Medium';
+    font-size: 16px;
+    gap: 8px;
+    justify-content: flex-start;
+    line-height: 24px;
+  }
 
-      :host(:not([has-started-setup-attempt_])) #dialogTitle {
-        --cr-dialog-title-slot-padding-bottom: 20px;
-      }
+  #subtitle {
+    color: var(--cros-text-color-secondary);
+    font-family: 'Roboto';
+    font-size: 14px;
+    line-height: 20px;
+  }
 
-      #title {
-        align-items: center;
-        color: var(--cros-text-color-primary);
-        display: flex;
-        flex-direction: row;
-        font-family: 'Google Sans Medium';
-        font-size: 16px;
-        gap: 8px;
-        justify-content: flex-start;
-        line-height: 24px;
-      }
+  #dialogBody {
+    display: flex;
+    flex-direction: column;
+    padding-inline-end: 24px;
+    padding-inline-start: 24px;
+  }
 
-      #subtitle {
-        color: var(--cros-text-color-secondary);
-        font-family: 'Roboto';
-        font-size: 14px;
-        line-height: 20px;
-      }
+  :host([has-started-setup-attempt_]) #dialogBody {
+    /* Fixed height after intro state */
+    height: 296px;
+  }
 
-      #dialogBody {
-        display: flex;
-        flex-direction: column;
-        padding-inline-end: 24px;
-        padding-inline-start: 24px;
-      }
+  :host([did-setup-attempt-fail_]) #dialogBody {
+    /* Smaller fixed height to account for the presence of #failureIcon */
+    height: 288px;
+  }
 
-      :host([has-started-setup-attempt_]) #dialogBody {
-        /* Fixed height after intro state */
-        height: 296px;
-      }
+  :host([should-show-setup-instructions-separately_]) #dialogBody {
+    /* Add space to force setup instructions to bottom of body */
+    justify-content: space-between;
+  }
 
-      :host([did-setup-attempt-fail_]) #dialogBody {
-        /* Smaller fixed height to account for the presence of #failureIcon */
-        height: 288px;
-      }
+  #buttonContainer {
+    align-items: center;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    padding-bottom: 20px;
+    padding-inline-end: 24px;
+    padding-inline-start: 24px;
+    padding-top: 0;
+  }
 
-      :host([should-show-setup-instructions-separately_]) #dialogBody {
-        /* Add space to force setup instructions to bottom of body */
-        justify-content: space-between;
-      }
+  #failure-icon {
+    --iron-icon-fill-color: var(--cros-icon-color-warning);
+    height: 32px;
+    width: 32px;
+  }
 
-      #buttonContainer {
-        align-items: center;
-        display: flex;
-        flex-direction: row;
-        justify-content: space-between;
-        padding-bottom: 20px;
-        padding-inline-end: 24px;
-        padding-inline-start: 24px;
-        padding-top: 0;
-      }
+  #feature-icon {
+    --iron-icon-fill-color: var(--cros-icon-color-prominent);
+    height: 20px;
+    width: 20px;
+  }
 
-      #failure-icon {
-        --iron-icon-fill-color: var(--cros-icon-color-warning);
-        height: 32px;
-        width: 32px;
-      }
+  #instruction-icon {
+    --iron-icon-fill-color: var(--cros-icon-color-secondary);
+    height: 16px;
+    width: 16px;
+  }
 
-      #feature-icon {
-        --iron-icon-fill-color: var(--cros-icon-color-prominent);
-        height: 20px;
-        width: 20px;
-      }
+  #button-detail {
+    align-items: flex-end;
+    display: flex;
+    flex-direction: row;
+    gap: 8px;
+    justify-content: right;
+  }
 
-      #instruction-icon {
-        --iron-icon-fill-color: var(--cros-icon-color-secondary);
-        height: 16px;
-        width: 16px;
-      }
+  #description {
+    color: var(--cros-text-color-secondary);
+    font-family: 'Roboto';
+    font-size: 13px;
+    line-height: 20px;
+    padding-top: 24px;
+  }
 
-      #button-detail {
-        align-items: flex-end;
-        display: flex;
-        flex-direction: row;
-        gap: 8px;
-        justify-content: right;
-      }
+  #feature-description {
+    align-items: flex-start;
+    display: flex;
+    flex-direction: column;
+    width: 252px;
+  }
 
-      #description {
-        color: var(--cros-text-color-secondary);
-        font-family: 'Roboto';
-        font-size: 13px;
-        line-height: 20px;
-        padding-top: 24px;
-      }
+  #start-setup-description {
+    align-items: flex-start;
+    display: flex;
+    flex-direction: row;
+    height: auto;
+    justify-content: center;
+  }
 
-      #feature-description {
-        align-items: flex-start;
-        display: flex;
-        flex-direction: column;
-        width: 252px;
-      }
+  #feature-details-container {
+    align-items: flex-start;
+    color: var(--cros-text-color-secondary);
+    display: flex;
+    flex-direction: row;
+    font-family: 'Roboto';
+    font-size: 13px;
+    gap: 20px;
+    justify-content: start;
+    line-height: 20px;
+    padding-bottom: 16px;
+  }
 
-      #start-setup-description {
-        align-items: flex-start;
-        display: flex;
-        flex-direction: row;
-        height: auto;
-        justify-content: center;
-      }
+  #half-container {
+    flex: 1;
+  }
 
-      #feature-details-container {
-        align-items: flex-start;
-        color: var(--cros-text-color-secondary);
-        display: flex;
-        flex-direction: row;
-        font-family: 'Roboto';
-        font-size: 13px;
-        gap: 20px;
-        justify-content: start;
-        line-height: 20px;
-        padding-bottom: 16px;
-      }
+  /*
+    * Text size specified as 11-16.
+    * Aligned to flex-end and height reduced to 14px to shift text down 2px.
+  */
+  #instruction {
+    align-items: flex-end;
+    color: var(--cros-text-color-secondary);
+    display: flex;
+    flex-direction: row;
+    font-family: 'Roboto';
+    font-size: 11px;
+    gap: 6px;
+    justify-content: start;
+    line-height: 14px;
+    padding-bottom: 24px;
+  }
 
-      #half-container {
-        flex: 1;
-      }
+  #illustration {
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: contain;
+    height: 200px;
+    width: 100%;
+  }
 
-      /*
-       * Text size specified as 11-16.
-       * Aligned to flex-end and height reduced to 14px to shift text down 2px.
-      */
-      #instruction {
-        align-items: flex-end;
-        color: var(--cros-text-color-secondary);
-        display: flex;
-        flex-direction: row;
-        font-family: 'Roboto';
-        font-size: 11px;
-        gap: 6px;
-        justify-content: start;
-        line-height: 14px;
-        padding-bottom: 24px;
-      }
+  :host(:not([has-started-setup-attempt_])) #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_setup.svg);
+    padding-bottom: 8px;
+    padding-top: 8px;
+    width: 200px;
+  }
 
-      #illustration {
-        background-position: center center;
-        background-repeat: no-repeat;
-        background-size: contain;
-        height: 200px;
-        width: 100%;
-      }
+  :host([is-setup-attempt-in-progress_]) #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_connecting.svg);
+  }
 
-      :host(:not([has-started-setup-attempt_])) #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_setup.svg);
-        padding-bottom: 8px;
-        padding-top: 8px;
-        width: 200px;
-      }
+  :host([did-setup-attempt-fail_]) #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_error.svg);
+  }
 
-      :host([is-setup-attempt-in-progress_]) #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_connecting.svg);
-      }
+  :host([has-completed-setup-successfully_]) #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_finished.svg);
+  }
 
-      :host([did-setup-attempt-fail_]) #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_error.svg);
-      }
+  @media(prefers-color-scheme: dark) {
+    :host(:not([has-started-setup-attempt_])) #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_setup_dark.svg);
+    }
 
-      :host([has-completed-setup-successfully_]) #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_finished.svg);
-      }
+    :host([is-setup-attempt-in-progress_]) #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_connecting_dark.svg);
+    }
 
-      @media(prefers-color-scheme: dark) {
-        :host(:not([has-started-setup-attempt_])) #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_setup_dark.svg);
-        }
+    :host([did-setup-attempt-fail_]) #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_error_dark.svg);
+    }
 
-        :host([is-setup-attempt-in-progress_]) #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_connecting_dark.svg);
-        }
-
-        :host([did-setup-attempt-fail_]) #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_error_dark.svg);
-        }
-
-        :host([has-completed-setup-successfully_]) #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_finished_dark.svg);
-        }
-      }
-    </style>
-    <cr-dialog id="dialog" close-text="$i18n{close}">
-      <div id="dialogTitle" slot="title">
-        <div id="title">
-          <template is="dom-if" if="[[didSetupAttemptFail_]]" restamp>
-            <iron-icon id="failure-icon" icon="os-settings:multidevice-error">
-            </iron-icon>
-          </template>
-          [[title_]]
+    :host([has-completed-setup-successfully_]) #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_finished_dark.svg);
+    }
+  }
+</style>
+<cr-dialog id="dialog" close-text="$i18n{close}">
+  <div id="dialogTitle" slot="title">
+    <div id="title">
+      <template is="dom-if" if="[[didSetupAttemptFail_]]" restamp>
+        <iron-icon id="failure-icon" icon="os-settings:multidevice-error">
+        </iron-icon>
+      </template>
+      [[title_]]
+    </div>
+    <template is="dom-if" if="[[hasStartedSetupAttempt_]]" restamp>
+      <template is="dom-if" if="[[shouldShowScreenLockInstructions_(flowState_)]]" restamp>
+        <div id="subtitle">
+          $i18n{multideviceNotificationAccessSetupScreenLockSubtitle}
         </div>
-        <template is="dom-if" if="[[hasStartedSetupAttempt_]]" restamp>
-          <template is="dom-if" if="[[shouldShowScreenLockInstructions_(flowState_)]]" restamp>
-            <div id="subtitle">
-              $i18n{multideviceNotificationAccessSetupScreenLockSubtitle}
-            </div>
-          </template>
-        </template>
-        <template is="dom-if" if="[[!hasStartedSetupAttempt_]]" restamp>
-          <div id="subtitle">
-            $i18n{multidevicePermissionsSetupAckSubtitle}
-          </div>
-        </template>
+      </template>
+    </template>
+    <template is="dom-if" if="[[!hasStartedSetupAttempt_]]" restamp>
+      <div id="subtitle">
+        $i18n{multidevicePermissionsSetupAckSubtitle}
       </div>
-      <div id="dialogBody" slot="body">
-        <template is="dom-if" if="[[!hasStartedSetupAttempt_]]" restamp>
-          <div id="start-setup-description">
-            <div id="half-container">
-              <div id="illustration"></div>
-            </div>
-            <div id="half-container">
-              <div id="feature-description">
-                <template is="dom-if" if="[[showCameraRoll]]"
-                    restamp>
-                  <div id="feature-details-container">
-                    <iron-icon id="feature-icon"
-                        icon="os-settings:multidevice-recent-photos">
-                    </iron-icon>
-                    $i18n{multidevicePermissionsSetupCameraRollSummary}
-                  </div>
-                </template>
-                <template is="dom-if" if="[[showNotifications]]"
-                    restamp>
-                  <div id="feature-details-container">
-                    <iron-icon id="feature-icon"
-                        icon="os-settings:multidevice-notifications">
-                    </iron-icon>
-                    $i18n{multidevicePermissionsSetupNotificationsSummary}
-                  </div>
-                </template>
-                <template is="dom-if" if="[[showAppStreaming]]" restamp>
-                  <div id="feature-details-container">
-                    <iron-icon id="feature-icon"
-                        icon="os-settings:multidevice-app-streaming">
-                    </iron-icon>
-                    $i18n{multidevicePermissionsSetupAppsSummary}
-                  </div>
-                </template>
-              </div>
-            </div>
-          </div>
-          <div id="instruction">
-            <iron-icon id="instruction-icon" icon="os-settings:failure-alert">
-            </iron-icon>
-            $i18n{multideviceNotificationAccessSetupInstructions}
-          </div>
-        </template>
-        <template is="dom-if" if="[[hasStartedSetupAttempt_]]" restamp>
-          <template is="dom-if" if="[[shouldShowScreenLockInstructions_(flowState_)]]" restamp>
-            <settings-multidevice-screen-lock-subpage
-                is-screen-lock-enabled="{{isScreenLockEnabled_}}"
-                is-password-dialog-showing="{{isPasswordDialogShowing}}">
-            </settings-multidevice-screen-lock-subpage>
-          </template>
-          <template is="dom-if" if="[[!shouldShowScreenLockInstructions_(flowState_)]]" restamp>
-            <div id="illustration"></div>
-            <template is="dom-if" if="[[description_]]" restamp>
-              <div id="description">
-                <localized-link localized-string="[[description_]]">
-                </localized-link>
-              </div>
-            </template>
-          </template>
-        </template>
-      </div>
-      <div id="buttonContainer" slot="button-container">
+    </template>
+  </div>
+  <div id="dialogBody" slot="body">
+    <template is="dom-if" if="[[!hasStartedSetupAttempt_]]" restamp>
+      <div id="start-setup-description">
         <div id="half-container">
-          <template is="dom-if" if="[[shouldShowLearnMoreButton_]]" restamp>
-            <cr-button id="learnMore" on-click="onLearnMoreClicked_">
-              $i18n{multideviceLearnMoreWithoutURL}
-            </cr-button>
-          </template>
+          <div id="illustration"></div>
         </div>
-        <div id="button-detail">
-          <template is="dom-if" if="[[shouldShowCancelButton_(setupState_)]]"
-              restamp>
-            <cr-button id="cancelButton" on-click="onCancelClicked_">
-              $i18n{cancel}
-            </cr-button>
-          </template>
-          <template is="dom-if" if="[[shouldShowDisabledDoneButton_]]" restamp>
-            <cr-button id="doneButton" class="action-button" disabled>
-              $i18n{done}
-            </cr-button>
-          </template>
-          <template is="dom-if" if="[[hasCompletedSetupSuccessfully_]]" restamp>
-            <cr-button id="doneButton" class="action-button"
-                on-click="onDoneOrCloseButtonClicked_">
-              $i18n{done}
-            </cr-button>
-          </template>
-          <template is="dom-if" if="[[isNotificationAccessProhibited_]]"
-              restamp>
-            <cr-button id="closeButton" class="action-button"
-                on-click="onDoneOrCloseButtonClicked_">
-              $i18n{close}
-            </cr-button>
-          </template>
-          <template is="dom-if" if="[[!hasStartedSetupAttempt_]]" restamp>
-            <cr-button id="getStartedButton" class="action-button"
-                on-click="nextPage_">
-              $i18n{next}
-            </cr-button>
-          </template>
-          <template is="dom-if" if="[[hasStartedSetupAttempt_]]" restamp>
-            <template is="dom-if" if="[[shouldShowScreenLockInstructions_(flowState_)]]" restamp>
-              <cr-button id="getStartedButton" class="action-button"
-                  on-click="nextPage_">
-                $i18n{next}
-              </cr-button>
+        <div id="half-container">
+          <div id="feature-description">
+            <template is="dom-if" if="[[showCameraRoll]]"
+                restamp>
+              <div id="feature-details-container">
+                <iron-icon id="feature-icon"
+                    icon="os-settings:multidevice-recent-photos">
+                </iron-icon>
+                $i18n{multidevicePermissionsSetupCameraRollSummary}
+              </div>
             </template>
-          </template>
-          <template is="dom-if" if="[[shouldShowTryAgainButton_(setupState_)]]"
-              restamp>
-            <cr-button id="tryAgainButton" class="action-button"
-                on-click="nextPage_">
-              $i18n{multideviceNotificationAccessSetupTryAgain}
-            </cr-button>
-          </template>
+            <template is="dom-if" if="[[showNotifications]]"
+                restamp>
+              <div id="feature-details-container">
+                <iron-icon id="feature-icon"
+                    icon="os-settings:multidevice-notifications">
+                </iron-icon>
+                $i18n{multidevicePermissionsSetupNotificationsSummary}
+              </div>
+            </template>
+            <template is="dom-if" if="[[showAppStreaming]]" restamp>
+              <div id="feature-details-container">
+                <iron-icon id="feature-icon"
+                    icon="os-settings:multidevice-app-streaming">
+                </iron-icon>
+                $i18n{multidevicePermissionsSetupAppsSummary}
+              </div>
+            </template>
+          </div>
         </div>
       </div>
-    </cr-dialog>
-  </template>
-  <script src="multidevice_permissions_setup_dialog.js"></script>
-</dom-module>
+      <div id="instruction">
+        <iron-icon id="instruction-icon" icon="os-settings:failure-alert">
+        </iron-icon>
+        $i18n{multideviceNotificationAccessSetupInstructions}
+      </div>
+    </template>
+    <template is="dom-if" if="[[hasStartedSetupAttempt_]]" restamp>
+      <template is="dom-if" if="[[shouldShowScreenLockInstructions_(flowState_)]]" restamp>
+        <settings-multidevice-screen-lock-subpage
+            is-screen-lock-enabled="{{isScreenLockEnabled_}}"
+            is-password-dialog-showing="{{isPasswordDialogShowing}}">
+        </settings-multidevice-screen-lock-subpage>
+      </template>
+      <template is="dom-if" if="[[!shouldShowScreenLockInstructions_(flowState_)]]" restamp>
+        <div id="illustration"></div>
+        <template is="dom-if" if="[[description_]]" restamp>
+          <div id="description">
+            <localized-link localized-string="[[description_]]">
+            </localized-link>
+          </div>
+        </template>
+      </template>
+    </template>
+  </div>
+  <div id="buttonContainer" slot="button-container">
+    <div id="half-container">
+      <template is="dom-if" if="[[shouldShowLearnMoreButton_]]" restamp>
+        <cr-button id="learnMore" on-click="onLearnMoreClicked_">
+          $i18n{multideviceLearnMoreWithoutURL}
+        </cr-button>
+      </template>
+    </div>
+    <div id="button-detail">
+      <template is="dom-if" if="[[shouldShowCancelButton_(setupState_)]]"
+          restamp>
+        <cr-button id="cancelButton" on-click="onCancelClicked_">
+          $i18n{cancel}
+        </cr-button>
+      </template>
+      <template is="dom-if" if="[[shouldShowDisabledDoneButton_]]" restamp>
+        <cr-button id="doneButton" class="action-button" disabled>
+          $i18n{done}
+        </cr-button>
+      </template>
+      <template is="dom-if" if="[[hasCompletedSetupSuccessfully_]]" restamp>
+        <cr-button id="doneButton" class="action-button"
+            on-click="onDoneOrCloseButtonClicked_">
+          $i18n{done}
+        </cr-button>
+      </template>
+      <template is="dom-if" if="[[isNotificationAccessProhibited_]]"
+          restamp>
+        <cr-button id="closeButton" class="action-button"
+            on-click="onDoneOrCloseButtonClicked_">
+          $i18n{close}
+        </cr-button>
+      </template>
+      <template is="dom-if" if="[[!hasStartedSetupAttempt_]]" restamp>
+        <cr-button id="getStartedButton" class="action-button"
+            on-click="nextPage_">
+          $i18n{next}
+        </cr-button>
+      </template>
+      <template is="dom-if" if="[[hasStartedSetupAttempt_]]" restamp>
+        <template is="dom-if" if="[[shouldShowScreenLockInstructions_(flowState_)]]" restamp>
+          <cr-button id="getStartedButton" class="action-button"
+              on-click="nextPage_">
+            $i18n{next}
+          </cr-button>
+        </template>
+      </template>
+      <template is="dom-if" if="[[shouldShowTryAgainButton_(setupState_)]]"
+          restamp>
+        <cr-button id="tryAgainButton" class="action-button"
+            on-click="nextPage_">
+          $i18n{multideviceNotificationAccessSetupTryAgain}
+        </cr-button>
+      </template>
+    </div>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.js
index 6446c04..01ab2f93 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.js
@@ -2,6 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_components/localized_link/localized_link.js';
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import '//resources/cr_elements/shared_style_css.m.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import './multidevice_screen_lock_subpage.js';
+import '../os_icons.js';
+import '../../settings_shared_css.js';
+
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_browser_proxy.js';
+import {MultiDeviceFeature} from './multidevice_constants.js';
+
 /**
  * @fileoverview
  * This element provides the Phone Hub notification and apps access setup flow
@@ -15,7 +33,7 @@
  * with the exception of CONNECTION_REQUESTED.
  * @enum {number}
  */
-/* #export */ const PermissionsSetupStatus = {
+export const PermissionsSetupStatus = {
   CONNECTION_REQUESTED: 0,
   CONNECTING: 1,
   TIMED_OUT_CONNECTING: 2,
@@ -29,7 +47,7 @@
  * Numerical values the flow of dialog set up progress.
  * @enum {number}
  */
-/* #export */ const SetupFlowStatus = {
+export const SetupFlowStatus = {
   INTRO: 0,
   SET_LOCKSCREEN: 1,
   WAIT_FOR_PHONE_NOTIFICATION: 2,
@@ -37,6 +55,7 @@
 };
 
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-permissions-setup-dialog',
 
   behaviors: [
@@ -154,12 +173,12 @@
     },
   },
 
-  /** @private {?settings.MultiDeviceBrowserProxy} */
+  /** @private {?MultiDeviceBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   ready() {
-    this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
+    this.browserProxy_ = MultiDeviceBrowserProxyImpl.getInstance();
   },
 
   /** @override */
@@ -188,7 +207,7 @@
     }
 
     this.browserProxy_.setFeatureEnabledState(
-        settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS, true);
+        MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS, true);
 
     if (this.showAppStreaming) {
       this.browserProxy_.attemptAppsSetup();
@@ -209,8 +228,7 @@
     this.setupState_ = setupState;
 
     if (this.setupState_ === PermissionsSetupStatus.COMPLETED_SUCCESSFULLY) {
-      this.browserProxy_.setFeatureEnabledState(
-          settings.MultiDeviceFeature.ECHE, true);
+      this.browserProxy_.setFeatureEnabledState(MultiDeviceFeature.ECHE, true);
     }
   },
 
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.html
index 7caddc3..848001d 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.html
@@ -1,57 +1,41 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared cr-radio-button-style">
+  :host([disabled]) {
+    opacity: 1;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_style_css.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
-<link rel="import" href="../../settings_shared_css.html">
+  /* Disc and label should be transluscent, but not the policy indicator. */
+  :host([disabled]) .disc-wrapper,
+  :host([disabled]) #labelWrapper {
+    opacity: var(--cr-disabled-opacity);
+  }
 
-<!-- TODO(jhawkins): This is copy/pasted from controlled_radio_button. Figure
-  out how to refactor such that a common UI/behavior may be shared. The only
-  difference is how the controlling preference is read. -->
-<dom-module id="multidevice-radio-button">
-  <template>
-    <style include="settings-shared cr-radio-button-style">
-      :host([disabled]) {
-        opacity: 1;
-      }
+  cr-policy-pref-indicator {
+    margin-inline-start: var(--settings-controlled-by-spacing);
+    /* Enable pointer events for the indicator so :hover works. Disable
+      * clicks/taps via onIndicatorTap_ so outer on-tap doesn't trigger. */
+    pointer-events: all;
+  }
+</style>
 
-      /* Disc and label should be transluscent, but not the policy indicator. */
-      :host([disabled]) .disc-wrapper,
-      :host([disabled]) #labelWrapper {
-        opacity: var(--cr-disabled-opacity);
-      }
+<div
+    role="presentation"
+    class="disc-wrapper"
+    id="button"
+    tabindex$="[[buttonTabIndex_]]"
+    on-keydown="onInputKeydown_">
+  <div class="disc-border"></div>
+  <div class="disc"></div>
+</div>
 
-      cr-policy-pref-indicator {
-        margin-inline-start: var(--settings-controlled-by-spacing);
-        /* Enable pointer events for the indicator so :hover works. Disable
-         * clicks/taps via onIndicatorTap_ so outer on-tap doesn't trigger. */
-        pointer-events: all;
-      }
-    </style>
+<div id="labelWrapper" role="presentation">
+  <span>[[label]]</span>
+</div>
 
-    <div
-        role="presentation"
-        class="disc-wrapper"
-        id="button"
-        tabindex$="[[buttonTabIndex_]]"
-        on-keydown="onInputKeydown_">
-      <div class="disc-border"></div>
-      <div class="disc"></div>
-    </div>
+<template is="dom-if" if="[[disabled]]" restamp>
+  <cr-policy-indicator
+      indicator-type="userPolicy"
+      icon-aria-label="[[label]]"
+      on-click="onIndicatorTap_">
+  </cr-policy-indicator>
+</template>
 
-    <div id="labelWrapper" role="presentation">
-      <span>[[label]]</span>
-    </div>
-
-    <template is="dom-if" if="[[disabled]]" restamp>
-      <cr-policy-indicator
-          indicator-type="userPolicy"
-          icon-aria-label="[[label]]"
-          on-click="onIndicatorTap_">
-      </cr-policy-indicator>
-    </template>
-
-  </template>
-  <script src="multidevice_radio_button.js"></script>
-</dom-module>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.js
index c6bc408..10a1484 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.js
@@ -2,7 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_radio_button/cr_radio_button_style_css.m.js';
+import '//resources/cr_elements/policy/cr_policy_indicator.m.js';
+import '//resources/polymer/v3_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js';
+import '../../settings_shared_css.js';
+
+import {CrRadioButtonBehavior} from '//resources/cr_elements/cr_radio_button/cr_radio_button_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'multidevice-radio-button',
 
   behaviors: [
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html
index caeb345c..844d9ba 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html
@@ -1,141 +1,123 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-shared-style settings-shared">
 
-<link rel="import" href="chrome://resources/cr_components/chromeos/quick_unlock/lock_screen_constants.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="../os_people_page/lock_screen_password_prompt_dialog.html">
-<link rel="import" href="../os_people_page/lock_state_behavior.html">
-<link rel="import" href="../os_people_page/setup_pin_dialog.html">
-<link rel="import" href="../os_people_page/pin_autosubmit_dialog.html">
+  #screen-lock-description {
+    align-items: center;
+    display: flex;
+    flex-direction: row;
+    height: auto;
+    justify-content: center;
+  }
 
-<dom-module id="settings-multidevice-screen-lock-subpage">
-  <template>
-    <style include="cr-shared-style settings-shared">
+  #half-container {
+    flex: 1;
+    height: 216px;
+  }
 
-      #screen-lock-description {
-        align-items: center;
-        display: flex;
-        flex-direction: row;
-        height: auto;
-        justify-content: center;
-      }
+  #illustration {
+    background-image:
+        url(chrome://os-settings/images/notification_access_connecting.svg);
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: contain;
+    height: 200px;
+    margin-bottom: 8px;
+    margin-top: 8px;
+    width: 100%;
+  }
 
-      #half-container {
-        flex: 1;
-        height: 216px;
-      }
+  @media(prefers-color-scheme: dark) {
+    #illustration {
+      background-image: url(
+        chrome://os-settings/images/notification_access_connecting_dark.svg);
+    }
+  }
 
-      #illustration {
-        background-image:
-            url(chrome://os-settings/images/notification_access_connecting.svg);
-        background-position: center center;
-        background-repeat: no-repeat;
-        background-size: contain;
-        height: 200px;
-        margin-bottom: 8px;
-        margin-top: 8px;
-        width: 100%;
-      }
+  #radio-button-container {
+    padding-top: 20px;
+  }
 
-      @media(prefers-color-scheme: dark) {
-        #illustration {
-          background-image: url(
-            chrome://os-settings/images/notification_access_connecting_dark.svg);
-        }
-      }
+  #passwordRadioButton {
+    --cr-radio-button-label-spacing: 20px;
+    --cr-radio-button-size: 20px;
+    color: var(--cr-primary-text-color);
+    font-family: 'Roboto';
+    font-size: 13px;
+    font-weight: medium;
+    line-height: 20px;
+    min-height: 20px;
+    padding-inline-start: 8px;
+    padding-top: 24px;
+  }
 
-      #radio-button-container {
-        padding-top: 20px;
-      }
+  #pinRadioButton {
+    --cr-radio-button-label-spacing: 20px;
+    --cr-radio-button-size: 20px;
+    color: var(--cr-primary-text-color);
+    font-family: 'Roboto';
+    font-size: 13px;
+    font-weight: medium;
+    line-height: 20px;
+    min-height: 20px;
+    padding-inline-start: 8px;
+    padding-top: 44px;
+  }
 
-      #passwordRadioButton {
-        --cr-radio-button-label-spacing: 20px;
-        --cr-radio-button-size: 20px;
-        color: var(--cr-primary-text-color);
-        font-family: 'Roboto';
-        font-size: 13px;
-        font-weight: medium;
-        line-height: 20px;
-        min-height: 20px;
-        padding-inline-start: 8px;
-        padding-top: 24px;
-      }
-
-      #pinRadioButton {
-        --cr-radio-button-label-spacing: 20px;
-        --cr-radio-button-size: 20px;
-        color: var(--cr-primary-text-color);
-        font-family: 'Roboto';
-        font-size: 13px;
-        font-weight: medium;
-        line-height: 20px;
-        min-height: 20px;
-        padding-inline-start: 8px;
-        padding-top: 44px;
-      }
-
-      #subtext {
-        color: var(--cr-secondary-text-color);
-        font-family: 'Roboto';
-        font-size: 13px;
-        font-weight: medium;
-        line-height: 20px;
-        padding-inline-start: 48px;
-      }
-    </style>
-    <div id="screen-lock-description">
-      <div id="half-container">
-        <div id="illustration"></div>
-      </div>
-      <div id="half-container">
-        <template is="dom-if" if="[[authToken_]]">
-          <cr-radio-group id=radio-button-container
-              disabled$="[[quickUnlockDisabledByPolicy_]]"
-              selected="{{selectedUnlockType}}"
-              deep-link-focus-id$="[[Setting.kChangeAuthPinV2]]">
-            <cr-radio-button id="passwordRadioButton" name="password"
-                label=$i18n{lockScreenPasswordOnly}>
-            </cr-radio-button>
-            <cr-radio-button id="pinRadioButton" name="pin+password"
-                label=$i18n{lockScreenPinOrPassword}>
-            </cr-radio-button>
-            <div id="subtext">
-              $i18n{multideviceNotificationAccessSetupScreenLockInstruction}
+  #subtext {
+    color: var(--cr-secondary-text-color);
+    font-family: 'Roboto';
+    font-size: 13px;
+    font-weight: medium;
+    line-height: 20px;
+    padding-inline-start: 48px;
+  }
+</style>
+<div id="screen-lock-description">
+  <div id="half-container">
+    <div id="illustration"></div>
+  </div>
+  <div id="half-container">
+    <template is="dom-if" if="[[authToken_]]">
+      <cr-radio-group id=radio-button-container
+          disabled$="[[quickUnlockDisabledByPolicy_]]"
+          selected="{{selectedUnlockType}}"
+          deep-link-focus-id$="[[Setting.kChangeAuthPinV2]]">
+        <cr-radio-button id="passwordRadioButton" name="password"
+            label=$i18n{lockScreenPasswordOnly}>
+        </cr-radio-button>
+        <cr-radio-button id="pinRadioButton" name="pin+password"
+            label=$i18n{lockScreenPinOrPassword}>
+        </cr-radio-button>
+        <div id="subtext">
+          $i18n{multideviceNotificationAccessSetupScreenLockInstruction}
+        </div>
+        <template is="dom-if"
+            if="[[showConfigurePinButton_(selectedUnlockType)]]">
+          <div class="list-item-end">
+            <div id="pinPasswordSecondaryActionDiv"
+                class="secondary-action">
+              <!-- Use stop-keyboard-event-propagation to prevent
+                    triggering this when focused after closing the
+                    dialog. -->
+              <cr-button id="setupPinButton" on-click="onConfigurePin_"
+                  stop-keyboard-event-propagation>
+                [[getSetupPinText_(hasPin)]]
+              </cr-button>
             </div>
-            <template is="dom-if"
-                if="[[showConfigurePinButton_(selectedUnlockType)]]">
-              <div class="list-item-end">
-                <div id="pinPasswordSecondaryActionDiv"
-                    class="secondary-action">
-                  <!-- Use stop-keyboard-event-propagation to prevent
-                       triggering this when focused after closing the
-                       dialog. -->
-                  <cr-button id="setupPinButton" on-click="onConfigurePin_"
-                      stop-keyboard-event-propagation>
-                    [[getSetupPinText_(hasPin)]]
-                  </cr-button>
-                </div>
-              </div>
-            </template>
-          </cr-radio-group>
+          </div>
         </template>
-      </div>
-    </div>
-    <template is="dom-if" if="[[shouldPromptPasswordDialog_]]" restamp>
-      <settings-lock-screen-password-prompt-dialog id="passwordDialog"
-          on-close="onPasswordPromptDialogClose_"
-          on-auth-token-obtained="onAuthTokenObtained_">
-      </settings-lock-screen-password-prompt-dialog>
+      </cr-radio-group>
     </template>
-    <template is="dom-if" if="[[showSetupPinDialog_]]" restamp>
-      <settings-setup-pin-dialog id="setupPin"
-          set-modes="[[setModes_]]"
-          on-close="onSetupPinDialogClose_">
-      </settings-setup-pin-dialog>
-    </template>
-  </template>
-  <script src="multidevice_screen-lock_subpage.js"></script>
-</dom-module>
+  </div>
+</div>
+<template is="dom-if" if="[[shouldPromptPasswordDialog_]]" restamp>
+  <settings-lock-screen-password-prompt-dialog id="passwordDialog"
+      on-close="onPasswordPromptDialogClose_"
+      on-auth-token-obtained="onAuthTokenObtained_">
+  </settings-lock-screen-password-prompt-dialog>
+</template>
+<template is="dom-if" if="[[showSetupPinDialog_]]" restamp>
+  <settings-setup-pin-dialog id="setupPin"
+      set-modes="[[setModes_]]"
+      on-close="onSetupPinDialogClose_">
+  </settings-setup-pin-dialog>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.js
index 7330db0..0d3d88b 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.js
@@ -2,12 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '../os_people_page/lock_screen_password_prompt_dialog.js';
+import '../os_people_page/setup_pin_dialog.js';
+import '../os_people_page/pin_autosubmit_dialog.js';
+
+import {LockScreenProgress, recordLockScreenProgress} from '//resources/cr_components/chromeos/quick_unlock/lock_screen_constants.m.js';
+import {assert} from '//resources/js/assert.m.js';
+import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {LockScreenUnlockType, LockStateBehavior} from '../os_people_page/lock_state_behavior.m.js';
+
 /**
  * @fileoverview
  * Subpage of settings-multidevice-notification-access-setup-dialog for setting
  * up screen lock.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-screen-lock-subpage',
 
   behaviors: [
@@ -47,7 +62,7 @@
     writeUma_: {
       type: Object,
       value() {
-        return settings.recordLockScreenProgress;
+        return recordLockScreenProgress;
       },
     },
 
@@ -190,14 +205,14 @@
    */
   onConfigurePin_(e) {
     e.preventDefault();
-    this.writeUma_(settings.LockScreenProgress.CHOOSE_PIN_OR_PASSWORD);
+    this.writeUma_(LockScreenProgress.CHOOSE_PIN_OR_PASSWORD);
     this.showSetupPinDialog_ = true;
   },
 
   /** @private */
   onSetupPinDialogClose_() {
     this.showSetupPinDialog_ = false;
-    cr.ui.focusWithoutInk(assert(this.$$('#setupPinButton')));
+    focusWithoutInk(assert(this.$$('#setupPinButton')));
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html
index fb3d952..e69801a 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html
@@ -1,28 +1,10 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../metrics_recorder.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../os_settings_routes.html">
-<link rel="import" href="../../i18n_setup.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_item.html">
-
-<dom-module id="settings-multidevice-smartlock-item">
-  <template>
-    <template is="dom-if"
-        if="[[shouldShowFeature_(pageContentData)]]"
-        restamp>
-      <settings-multidevice-feature-item id="smartLockItem"
-          feature="[[MultiDeviceFeature.SMART_LOCK]]"
-          page-content-data="[[pageContentData]]"
-          subpage-route="[[routes.SMART_LOCK]]"
-          is-feature-icon-hidden>
-      </settings-multidevice-feature-item>
-    </template>
-  </template>
-  <script src="multidevice_smartlock_item.js"></script>
-</dom-module>
+<template is="dom-if"
+    if="[[shouldShowFeature_(pageContentData)]]"
+    restamp>
+  <settings-multidevice-feature-item id="smartLockItem"
+      feature="[[MultiDeviceFeature.SMART_LOCK]]"
+      page-content-data="[[pageContentData]]"
+      subpage-route="[[routes.SMART_LOCK]]"
+      is-feature-icon-hidden>
+  </settings-multidevice-feature-item>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.js
index f094fe7b..cdd0296 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.js
@@ -2,6 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './multidevice_feature_item.js';
+
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {recordSettingChange} from '../metrics_recorder.m.js';
+import {routes} from '../os_route.m.js';
+import {OsSettingsRoutes} from '../os_settings_routes.m.js';
+
+import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_browser_proxy.js';
+import {MultiDeviceFeature, MultiDevicePageContentData, MultiDeviceSettingsMode} from './multidevice_constants.js';
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview
  * Wrapper for multidevice-feature-item that allows displaying the Smart Lock
@@ -10,6 +23,7 @@
  * in an auth token.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-smartlock-item',
 
   behaviors: [
@@ -19,12 +33,12 @@
 
   properties: {
     /**
-     * Alias for allowing Polymer bindings to settings.routes.
+     * Alias for allowing Polymer bindings to routes.
      * @type {?OsSettingsRoutes}
      */
     routes: {
       type: Object,
-      value: settings.routes,
+      value: routes,
     },
 
     /**
@@ -40,12 +54,12 @@
     'feature-toggle-clicked': 'onFeatureToggleClicked_',
   },
 
-  /** @private {?settings.MultiDeviceBrowserProxy} */
+  /** @private {?MultiDeviceBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   ready() {
-    this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
+    this.browserProxy_ = MultiDeviceBrowserProxyImpl.getInstance();
 
     this.addWebUIListener(
         'settings.updateMultidevicePageContentData',
@@ -61,7 +75,7 @@
   },
 
   /**
-   * @param {!settings.MultiDevicePageContentData} newData
+   * @param {!MultiDevicePageContentData} newData
    * @private
    */
   onPageContentDataChanged_(newData) {
@@ -75,10 +89,10 @@
   shouldShowFeature_() {
     // We only show the feature when it is editable, because a disabled toggle
     // is confusing for the user without greater context.
-    return this.isFeatureSupported(settings.MultiDeviceFeature.SMART_LOCK) &&
+    return this.isFeatureSupported(MultiDeviceFeature.SMART_LOCK) &&
         this.pageContentData.mode ===
-        settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED &&
-        this.isFeatureStateEditable(settings.MultiDeviceFeature.SMART_LOCK);
+        MultiDeviceSettingsMode.HOST_SET_VERIFIED &&
+        this.isFeatureStateEditable(MultiDeviceFeature.SMART_LOCK);
   },
 
   /**
@@ -88,7 +102,7 @@
    * multidevice page
    *
    * @param {!CustomEvent<!{
-   *     feature: !settings.MultiDeviceFeature,
+   *     feature: !MultiDeviceFeature,
    *     enabled: boolean
    * }>} event
    * @private
@@ -99,6 +113,6 @@
 
     this.browserProxy_.setFeatureEnabledState(
         feature, enabled, this.authToken.token);
-    settings.recordSettingChange();
+    recordSettingChange();
   },
 });
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.html
index 76e0725..56cf92e 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.html
@@ -1,74 +1,50 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_toggle.html">
-<link rel="import" href="multidevice_radio_button.html">
-<link rel="import" href="../deep_linking_behavior.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../os_settings_routes.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../route_observer_behavior.html">
-<link rel="import" href="../../i18n_setup.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../metrics_recorder.html">
-
-<dom-module id="settings-multidevice-smartlock-subpage">
-  <template>
-    <style include="settings-shared"></style>
-    <div class="settings-box first">
-      <!-- TODO(jhawkins): Remove this status text and move the toggle into
-           the subpage header section. -->
-      <div class="start">
-        <template is="dom-if" if="[[smartLockEnabled_]]" restamp>
-          $i18n{multideviceEnabled}
-        </template>
-        <template is="dom-if" if="[[!smartLockEnabled_]]" restamp>
-          $i18n{multideviceDisabled}
-        </template>
-      </div>
-      <settings-multidevice-feature-toggle
-          feature="[[MultiDeviceFeature.SMART_LOCK]]"
-          page-content-data="[[pageContentData]]"
-          deep-link-focus-id$="[[Setting.kSmartLockOnOff]]">
-      </settings-multidevice-feature-toggle>
-    </div>
-    <iron-collapse opened="[[smartLockEnabled_]]">
-      <div class="settings-box first line-only">
-          <h2 class="start first">
-            $i18n{multideviceSmartLockOptions}
-          </h2>
-      </div>
-      <div class="list-frame">
-        <cr-radio-group
-            selected="[[smartLockSignInEnabled_]]"
-            selectable-elements="multidevice-radio-button"
-            disabled="[[!smartLockSignInAllowed_]]"
-            on-selected-changed="onSmartLockSignInEnabledChanged_"
-            deep-link-focus-id$="[[Setting.kSmartLockUnlockOrSignIn]]">
-          <multidevice-radio-button
-              name="disabled"
-              class="list-item underbar"
-              label="$i18n{easyUnlockUnlockDeviceOnly}">
-          </multidevice-radio-button>
-          <multidevice-radio-button
-              name="enabled"
-              class="list-item"
-              label="$i18n{easyUnlockUnlockDeviceAndAllowSignin}">
-          </multidevice-radio-button>
-        </cr-radio-group>
-      </div>
-    </iron-collapse>
-    <template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
-      <settings-password-prompt-dialog id="smartLockSignInPasswordPrompt"
-          on-close="onEnableSignInDialogClose_"
-          on-token-obtained="onTokenObtained_">
-      </settings-password-prompt-dialog>
+<style include="settings-shared"></style>
+<div class="settings-box first">
+  <!-- TODO(jhawkins): Remove this status text and move the toggle into
+        the subpage header section. -->
+  <div class="start">
+    <template is="dom-if" if="[[smartLockEnabled_]]" restamp>
+      $i18n{multideviceEnabled}
     </template>
-  </template>
-  <script src="multidevice_smartlock_subpage.js"></script>
-</dom-module>
+    <template is="dom-if" if="[[!smartLockEnabled_]]" restamp>
+      $i18n{multideviceDisabled}
+    </template>
+  </div>
+  <settings-multidevice-feature-toggle
+      feature="[[MultiDeviceFeature.SMART_LOCK]]"
+      page-content-data="[[pageContentData]]"
+      deep-link-focus-id$="[[Setting.kSmartLockOnOff]]">
+  </settings-multidevice-feature-toggle>
+</div>
+<iron-collapse opened="[[smartLockEnabled_]]">
+  <div class="settings-box first line-only">
+      <h2 class="start first">
+        $i18n{multideviceSmartLockOptions}
+      </h2>
+  </div>
+  <div class="list-frame">
+    <cr-radio-group
+        selected="[[smartLockSignInEnabled_]]"
+        selectable-elements="multidevice-radio-button"
+        disabled="[[!smartLockSignInAllowed_]]"
+        on-selected-changed="onSmartLockSignInEnabledChanged_"
+        deep-link-focus-id$="[[Setting.kSmartLockUnlockOrSignIn]]">
+      <multidevice-radio-button
+          name="disabled"
+          class="list-item underbar"
+          label="$i18n{easyUnlockUnlockDeviceOnly}">
+      </multidevice-radio-button>
+      <multidevice-radio-button
+          name="enabled"
+          class="list-item"
+          label="$i18n{easyUnlockUnlockDeviceAndAllowSignin}">
+      </multidevice-radio-button>
+    </cr-radio-group>
+  </div>
+</iron-collapse>
+<template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
+  <settings-password-prompt-dialog id="smartLockSignInPasswordPrompt"
+      on-close="onEnableSignInDialogClose_"
+      on-token-obtained="onTokenObtained_">
+  </settings-password-prompt-dialog>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
index f0345c7..595bf22 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
@@ -2,13 +2,34 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import './multidevice_feature_toggle.js';
+import './multidevice_radio_button.js';
+import '../../settings_shared_css.js';
+
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Route} from '../../router.js';
+import {DeepLinkingBehavior} from '../deep_linking_behavior.js';
+import {recordSettingChange} from '../metrics_recorder.m.js';
+import {routes} from '../os_route.m.js';
+import {OsSettingsRoutes} from '../os_settings_routes.m.js';
+import {RouteObserverBehavior} from '../route_observer_behavior.js';
+
+import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_browser_proxy.js';
+import {MultiDeviceFeature, MultiDeviceFeatureState, SmartLockSignInEnabledState} from './multidevice_constants.js';
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-smartlock-subpage',
 
   behaviors: [
     DeepLinkingBehavior,
     MultiDeviceFeatureBehavior,
-    settings.RouteObserverBehavior,
+    RouteObserverBehavior,
     WebUIListenerBehavior,
   ],
 
@@ -16,7 +37,7 @@
     /** @type {?OsSettingsRoutes} */
     routes: {
       type: Object,
-      value: settings.routes,
+      value: routes,
     },
 
     /**
@@ -31,11 +52,11 @@
     /**
      * Whether Smart Lock may be used to sign-in the user (as opposed to only
      * being able to unlock the user's screen).
-     * @private {!settings.SmartLockSignInEnabledState}
+     * @private {!SmartLockSignInEnabledState}
      */
     smartLockSignInEnabled_: {
       type: Object,
-      value: settings.SmartLockSignInEnabledState.DISABLED,
+      value: SmartLockSignInEnabledState.DISABLED,
     },
 
     /**
@@ -74,12 +95,12 @@
     },
   },
 
-  /** @private {?settings.MultiDeviceBrowserProxy} */
+  /** @private {?MultiDeviceBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   ready() {
-    this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
+    this.browserProxy_ = MultiDeviceBrowserProxyImpl.getInstance();
 
     this.addWebUIListener(
         'smart-lock-signin-enabled-changed',
@@ -99,12 +120,12 @@
   },
 
   /**
-   * @param {!settings.Route} route
-   * @param {!settings.Route} oldRoute
+   * @param {!Route} route
+   * @param {!Route} oldRoute
    */
   currentRouteChanged(route, oldRoute) {
     // Does not apply to this page.
-    if (route !== settings.routes.SMART_LOCK) {
+    if (route !== routes.SMART_LOCK) {
       return;
     }
 
@@ -118,8 +139,8 @@
    */
   computeIsSmartLockEnabled_() {
     return !!this.pageContentData &&
-        this.getFeatureState(settings.MultiDeviceFeature.SMART_LOCK) ===
-        settings.MultiDeviceFeatureState.ENABLED_BY_USER;
+        this.getFeatureState(MultiDeviceFeature.SMART_LOCK) ===
+        MultiDeviceFeatureState.ENABLED_BY_USER;
   },
 
   /**
@@ -127,9 +148,9 @@
    * @private
    */
   updateSmartLockSignInEnabled_(enabled) {
-    this.smartLockSignInEnabled_ =
-        enabled ? settings.SmartLockSignInEnabledState.ENABLED :
-        settings.SmartLockSignInEnabledState.DISABLED;
+    this.smartLockSignInEnabled_ = enabled ?
+        SmartLockSignInEnabledState.ENABLED :
+        SmartLockSignInEnabledState.DISABLED;
   },
 
   /**
@@ -153,19 +174,18 @@
    */
   onSmartLockSignInEnabledChanged_() {
     const radioGroup = this.$$('cr-radio-group');
-    const enabled =
-        radioGroup.selected === settings.SmartLockSignInEnabledState.ENABLED;
+    const enabled = radioGroup.selected === SmartLockSignInEnabledState.ENABLED;
 
     if (!enabled) {
       // No authentication check is required to disable.
       this.browserProxy_.setSmartLockSignInEnabled(false /* enabled */);
-      settings.recordSettingChange();
+      recordSettingChange();
       return;
     }
 
     // Toggle the enabled state back to disabled, as authentication may not
     // succeed. The toggle state updates automatically by the pref listener.
-    radioGroup.selected = settings.SmartLockSignInEnabledState.DISABLED;
+    radioGroup.selected = SmartLockSignInEnabledState.DISABLED;
     this.openPasswordPromptDialog_();
   },
 
@@ -183,7 +203,7 @@
     if (this.authToken_) {
       this.browserProxy_.setSmartLockSignInEnabled(
           true /* enabled */, this.authToken_.token);
-      settings.recordSettingChange();
+      recordSettingChange();
     }
 
     // Always require password entry if re-enabling SignIn with Smart Lock.
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
index ca335893..05d11c9 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
@@ -1,251 +1,220 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared iron-flex">
+  settings-multidevice-feature-item,
+  settings-multidevice-tether-item {
+    --feature-item-row-padding: 0;
+  }
 
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../../i18n_setup.html">
-<link rel="import" href="../deep_linking_behavior.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../os_settings_routes.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../route_observer_behavior.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../../settings_vars_css.html">
-<link rel="import" href="multidevice_combined_setup_item.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="multidevice_browser_proxy.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_item.html">
-<link rel="import" href="multidevice_feature_toggle.html">
-<link rel="import" href="multidevice_task_continuation_item.html">
-<link rel="import" href="multidevice_tether_item.html">
-<link rel="import" href="multidevice_wifi_sync_item.html">
+  settings-multidevice-feature-item:first-of-type {
+    --feature-item-border-top-style: none;
+  }
 
-<dom-module id="settings-multidevice-subpage">
-  <template>
-    <style include="settings-shared iron-flex">
-      settings-multidevice-feature-item,
-      settings-multidevice-tether-item {
-        --feature-item-row-padding: 0;
-      }
+  cr-button {
+    white-space: nowrap;
+  }
 
-      settings-multidevice-feature-item:first-of-type {
-        --feature-item-border-top-style: none;
-      }
-
-      cr-button {
-        white-space: nowrap;
-      }
-
-      #feature-items-container {
-        padding-inline-start: var(--cr-section-indent-padding);
-      }
-    </style>
-    <div class="settings-box first">
-      <div id="status-text-container"
-          class="start"
-          enabled$="[[isSuiteOn(pageContentData)]]"
-          inner-h-t-m-l="[[getStatusInnerHtml_(pageContentData)]]">
-      </div>
-        <template is="dom-if" if="[[shouldShowVerifyButton_(pageContentData)]]"
+  #feature-items-container {
+    padding-inline-start: var(--cr-section-indent-padding);
+  }
+</style>
+<div class="settings-box first">
+  <div id="status-text-container"
+      class="start"
+      enabled$="[[isSuiteOn(pageContentData)]]"
+      inner-h-t-m-l="[[getStatusInnerHtml_(pageContentData)]]">
+  </div>
+    <template is="dom-if" if="[[shouldShowVerifyButton_(pageContentData)]]"
+        restamp>
+      <cr-button on-click="handleVerifyButtonClick_">
+        $i18n{multideviceVerifyButton}
+      </cr-button>
+    </template>
+    <template is="dom-if" if="[[shouldShowSuiteToggle_(pageContentData)]]"
+        restamp>
+      <settings-multidevice-feature-toggle
+          toggle-aria-label="$i18n{multideviceSuiteToggleA11yLabel}"
+          feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
+          page-content-data="[[pageContentData]]"
+          deep-link-focus-id$="[[Setting.kMultiDeviceOnOff]]">
+      </settings-multidevice-feature-toggle>
+    </template>
+</div>
+<template is="dom-if"
+    if="[[shouldShowIndividualFeatures_(pageContentData)]]"
+    restamp>
+  <div id="feature-items-container">
+    <template is="dom-if"
+        if="[[isFeatureSupported(
+            MultiDeviceFeature.SMART_LOCK, pageContentData)]]"
+        restamp>
+      <settings-multidevice-feature-item id="smartLockItem"
+          feature="[[MultiDeviceFeature.SMART_LOCK]]"
+          page-content-data="[[pageContentData]]"
+          subpage-route="[[routes.SMART_LOCK]]"
+          deep-link-focus-id$="[[Setting.kSmartLockOnOff]]">
+      </settings-multidevice-feature-item>
+    </template>
+    <template is="dom-if"
+        if="[[isFeatureSupported(
+            MultiDeviceFeature.INSTANT_TETHERING, pageContentData)]]"
+        restamp>
+      <settings-multidevice-tether-item id="instantTetheringItem"
+          page-content-data="[[pageContentData]]"
+          deep-link-focus-id$="[[Setting.kInstantTetheringOnOff]]">
+      </settings-multidevice-tether-item>
+    </template>
+    <template is="dom-if"
+        if="[[isFeatureSupported(
+            MultiDeviceFeature.MESSAGES, pageContentData)]]"
+        restamp>
+      <settings-multidevice-feature-item id="messagesItem"
+          feature="[[MultiDeviceFeature.MESSAGES]]"
+          page-content-data="[[pageContentData]]"
+          deep-link-focus-id$="[[Setting.kMessagesOnOff]]
+              [[Setting.kMessagesSetUp]]">
+        <template is="dom-if"
+            if="[[doesAndroidMessagesRequireSetUp_(pageContentData)]]"
             restamp>
-          <cr-button on-click="handleVerifyButtonClick_">
-            $i18n{multideviceVerifyButton}
+          <cr-button disabled$="[[isAndroidMessagesSetupButtonDisabled_(
+                                    pageContentData)]]"
+              on-click="handleAndroidMessagesButtonClick_"
+              slot="feature-controller">
+            $i18n{multideviceSetupButton}
           </cr-button>
         </template>
-        <template is="dom-if" if="[[shouldShowSuiteToggle_(pageContentData)]]"
-            restamp>
-          <settings-multidevice-feature-toggle
-              toggle-aria-label="$i18n{multideviceSuiteToggleA11yLabel}"
-              feature="[[MultiDeviceFeature.BETTER_TOGETHER_SUITE]]"
-              page-content-data="[[pageContentData]]"
-              deep-link-focus-id$="[[Setting.kMultiDeviceOnOff]]">
-          </settings-multidevice-feature-toggle>
-        </template>
-    </div>
-    <template is="dom-if"
-        if="[[shouldShowIndividualFeatures_(pageContentData)]]"
-        restamp>
-      <div id="feature-items-container">
-        <template is="dom-if"
-            if="[[isFeatureSupported(
-                MultiDeviceFeature.SMART_LOCK, pageContentData)]]"
-            restamp>
-          <settings-multidevice-feature-item id="smartLockItem"
-              feature="[[MultiDeviceFeature.SMART_LOCK]]"
-              page-content-data="[[pageContentData]]"
-              subpage-route="[[routes.SMART_LOCK]]"
-              deep-link-focus-id$="[[Setting.kSmartLockOnOff]]">
-          </settings-multidevice-feature-item>
-        </template>
-        <template is="dom-if"
-            if="[[isFeatureSupported(
-                MultiDeviceFeature.INSTANT_TETHERING, pageContentData)]]"
-            restamp>
-          <settings-multidevice-tether-item id="instantTetheringItem"
-              page-content-data="[[pageContentData]]"
-              deep-link-focus-id$="[[Setting.kInstantTetheringOnOff]]">
-          </settings-multidevice-tether-item>
-        </template>
-        <template is="dom-if"
-            if="[[isFeatureSupported(
-                MultiDeviceFeature.MESSAGES, pageContentData)]]"
-            restamp>
-          <settings-multidevice-feature-item id="messagesItem"
-              feature="[[MultiDeviceFeature.MESSAGES]]"
-              page-content-data="[[pageContentData]]"
-              deep-link-focus-id$="[[Setting.kMessagesOnOff]]
-                  [[Setting.kMessagesSetUp]]">
-            <template is="dom-if"
-                if="[[doesAndroidMessagesRequireSetUp_(pageContentData)]]"
-                restamp>
-              <cr-button disabled$="[[isAndroidMessagesSetupButtonDisabled_(
-                                       pageContentData)]]"
-                  on-click="handleAndroidMessagesButtonClick_"
-                  slot="feature-controller">
-                $i18n{multideviceSetupButton}
-              </cr-button>
-            </template>
-          </settings-multidevice-feature-item>
-        </template>
-        <template is="dom-if"
-            if="[[isFeatureSupported(
-                MultiDeviceFeature.PHONE_HUB, pageContentData)]]"
-            restamp>
-          <settings-multidevice-feature-item id="phoneHubItem"
-              feature="[[MultiDeviceFeature.PHONE_HUB]]"
-              page-content-data="[[pageContentData]]"
-              deep-link-focus-id$="[[Setting.kPhoneHubOnOff]]">
-          </settings-multidevice-feature-item>
-        </template>
-        <template is="dom-if"
-            if="[[isFeatureSupported(
-                MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION,
-                pageContentData)]]"
-            restamp>
-          <settings-multidevice-task-continuation-item
-              id="phoneHubTaskContinuationItem"
-              page-content-data="[[pageContentData]]"
-              deep-link-focus-id$="[[Setting.kPhoneHubTaskContinuationOnOff]]">
-          </settings-multidevice-task-continuation-item>
-        </template>
-        <template is="dom-if"
-            if="[[shouldShowPhoneHubCameraRollItem_(pageContentData)]]"
-            restamp>
-          <settings-multidevice-feature-item id="phoneHubCameraRollItem"
-              feature="[[MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL]]"
-              page-content-data="[[pageContentData]]" is-sub-feature
-              deep-link-focus-id$="[[Setting.kPhoneHubCameraRollOnOff]]">
-            <template is="dom-if"
-                if="[[isPhoneHubCameraRollSetupRequired(pageContentData)]]"
-                restamp>
-              <cr-button on-click="handlePhoneHubSetupClick_"
-                  slot="feature-controller"
-                  disabled="[[isPhoneHubDisabled_(pageContentData)]]">
-                $i18n{multideviceSetupButton}
-              </cr-button>
-            </template>
-          </settings-multidevice-feature-item>
-        </template>
-        <template is="dom-if"
-            if="[[shouldShowPhoneHubNotificationsItem_(pageContentData)]]"
-            restamp>
-          <settings-multidevice-feature-item id="phoneHubNotificationsItem"
-              feature="[[MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS]]"
-              icon-tooltip="[[getPhoneHubNotificationsTooltip_(
-                                  pageContentData)]]"
-              icon="cr:domain"
-              page-content-data="[[pageContentData]]" is-sub-feature
-              deep-link-focus-id$="[[Setting.kPhoneHubNotificationsOnOff]]">
-            <template is="dom-if"
-                if="[[isPhoneHubNotificationsSetupRequired(pageContentData)]]"
-                restamp>
-              <cr-button on-click="handlePhoneHubSetupClick_"
-                  slot="feature-controller"
-                  disabled="[[isPhoneHubDisabled_(pageContentData)]]">
-                $i18n{multideviceSetupButton}
-              </cr-button>
-            </template>
-          </settings-multidevice-feature-item>
-        </template>
-        <template is="dom-if"
-            if="[[shouldShowPhoneHubAppsItem_(pageContentData)]]"
-            restamp>
-          <settings-multidevice-feature-item id="phoneHubAppsItem"
-              feature="[[MultiDeviceFeature.ECHE]]"
-              page-content-data="[[pageContentData]]" is-sub-feature
-              deep-link-focus-id$="[[Setting.kPhoneHubAppsOnOff]]">
-            <template is="dom-if"
-                if="[[isPhoneHubAppsSetupRequired(pageContentData)]]"
-                restamp>
-              <cr-button on-click="handlePhoneHubSetupClick_"
-                  slot="feature-controller"
-                  disabled="[[isPhoneHubDisabled_(pageContentData)]]">
-                $i18n{multideviceSetupButton}
-              </cr-button>
-            </template>
-          </settings-multidevice-feature-item>
-        </template>
-        <template is="dom-if"
-            if="[[shouldShowPhoneHubCombinedSetupItem_(pageContentData)]]"
-            restamp>
-          <settings-multidevice-combined-setup-item
-              id="phoneHubCombinedSetupItem"
-              camera-roll="[[isPhoneHubCameraRollSetupRequired(
-                                pageContentData)]]"
-              notifications="[[isPhoneHubNotificationsSetupRequired(
-                                pageContentData)]]"
-              app-streaming="[[isPhoneHubAppsSetupRequired(pageContentData)]]"
-              page-content-data="[[pageContentData]]">
-          </settings-multidevice-combined-setup-item>
-        </template>
-        <template is="dom-if"
-            if="[[isFeatureSupported(
-                MultiDeviceFeature.WIFI_SYNC, pageContentData)]]"
-            restamp>
-          <settings-multidevice-wifi-sync-item id="wifiSyncItem"
-              page-content-data="[[pageContentData]]"
-              deep-link-focus-id$="[[Setting.kWifiSyncOnOff]]">
-          </settings-multidevice-wifi-sync-item>
-        </template>
-      </div>
+      </settings-multidevice-feature-item>
     </template>
-    <div class="settings-box two-line">
-      <div id="forgetDeviceLabel" class="start" aria-hidden="true">
-        $i18n{multideviceForgetDevice}
-        <div id="forgetDeviceSummary" class="secondary" aria-hidden="true">
-            $i18n{multideviceForgetDeviceSummary}
-        </div>
-      </div>
-      <cr-button on-click="handleForgetDeviceClick_"
-          aria-labelledby="forgetDeviceLabel"
-          aria-describedby="forgetDeviceSummary"
-          deep-link-focus-id$="[[Setting.kForgetPhone]]">
-        $i18n{multideviceForgetDeviceDisconnect}
-      </cr-button>
+    <template is="dom-if"
+        if="[[isFeatureSupported(
+            MultiDeviceFeature.PHONE_HUB, pageContentData)]]"
+        restamp>
+      <settings-multidevice-feature-item id="phoneHubItem"
+          feature="[[MultiDeviceFeature.PHONE_HUB]]"
+          page-content-data="[[pageContentData]]"
+          deep-link-focus-id$="[[Setting.kPhoneHubOnOff]]">
+      </settings-multidevice-feature-item>
+    </template>
+    <template is="dom-if"
+        if="[[isFeatureSupported(
+            MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION,
+            pageContentData)]]"
+        restamp>
+      <settings-multidevice-task-continuation-item
+          id="phoneHubTaskContinuationItem"
+          page-content-data="[[pageContentData]]"
+          deep-link-focus-id$="[[Setting.kPhoneHubTaskContinuationOnOff]]">
+      </settings-multidevice-task-continuation-item>
+    </template>
+    <template is="dom-if"
+        if="[[shouldShowPhoneHubCameraRollItem_(pageContentData)]]"
+        restamp>
+      <settings-multidevice-feature-item id="phoneHubCameraRollItem"
+          feature="[[MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL]]"
+          page-content-data="[[pageContentData]]" is-sub-feature
+          deep-link-focus-id$="[[Setting.kPhoneHubCameraRollOnOff]]">
+        <template is="dom-if"
+            if="[[isPhoneHubCameraRollSetupRequired(pageContentData)]]"
+            restamp>
+          <cr-button on-click="handlePhoneHubSetupClick_"
+              slot="feature-controller"
+              disabled="[[isPhoneHubDisabled_(pageContentData)]]">
+            $i18n{multideviceSetupButton}
+          </cr-button>
+        </template>
+      </settings-multidevice-feature-item>
+    </template>
+    <template is="dom-if"
+        if="[[shouldShowPhoneHubNotificationsItem_(pageContentData)]]"
+        restamp>
+      <settings-multidevice-feature-item id="phoneHubNotificationsItem"
+          feature="[[MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS]]"
+          icon-tooltip="[[getPhoneHubNotificationsTooltip_(
+                              pageContentData)]]"
+          icon="cr:domain"
+          page-content-data="[[pageContentData]]" is-sub-feature
+          deep-link-focus-id$="[[Setting.kPhoneHubNotificationsOnOff]]">
+        <template is="dom-if"
+            if="[[isPhoneHubNotificationsSetupRequired(pageContentData)]]"
+            restamp>
+          <cr-button on-click="handlePhoneHubSetupClick_"
+              slot="feature-controller"
+              disabled="[[isPhoneHubDisabled_(pageContentData)]]">
+            $i18n{multideviceSetupButton}
+          </cr-button>
+        </template>
+      </settings-multidevice-feature-item>
+    </template>
+    <template is="dom-if"
+        if="[[shouldShowPhoneHubAppsItem_(pageContentData)]]"
+        restamp>
+      <settings-multidevice-feature-item id="phoneHubAppsItem"
+          feature="[[MultiDeviceFeature.ECHE]]"
+          page-content-data="[[pageContentData]]" is-sub-feature
+          deep-link-focus-id$="[[Setting.kPhoneHubAppsOnOff]]">
+        <template is="dom-if"
+            if="[[isPhoneHubAppsSetupRequired(pageContentData)]]"
+            restamp>
+          <cr-button on-click="handlePhoneHubSetupClick_"
+              slot="feature-controller"
+              disabled="[[isPhoneHubDisabled_(pageContentData)]]">
+            $i18n{multideviceSetupButton}
+          </cr-button>
+        </template>
+      </settings-multidevice-feature-item>
+    </template>
+    <template is="dom-if"
+        if="[[shouldShowPhoneHubCombinedSetupItem_(pageContentData)]]"
+        restamp>
+      <settings-multidevice-combined-setup-item
+          id="phoneHubCombinedSetupItem"
+          camera-roll="[[isPhoneHubCameraRollSetupRequired(
+                            pageContentData)]]"
+          notifications="[[isPhoneHubNotificationsSetupRequired(
+                            pageContentData)]]"
+          app-streaming="[[isPhoneHubAppsSetupRequired(pageContentData)]]"
+          page-content-data="[[pageContentData]]">
+      </settings-multidevice-combined-setup-item>
+    </template>
+    <template is="dom-if"
+        if="[[isFeatureSupported(
+            MultiDeviceFeature.WIFI_SYNC, pageContentData)]]"
+        restamp>
+      <settings-multidevice-wifi-sync-item id="wifiSyncItem"
+          page-content-data="[[pageContentData]]"
+          deep-link-focus-id$="[[Setting.kWifiSyncOnOff]]">
+      </settings-multidevice-wifi-sync-item>
+    </template>
+  </div>
+</template>
+<div class="settings-box two-line">
+  <div id="forgetDeviceLabel" class="start" aria-hidden="true">
+    $i18n{multideviceForgetDevice}
+    <div id="forgetDeviceSummary" class="secondary" aria-hidden="true">
+        $i18n{multideviceForgetDeviceSummary}
     </div>
-    <cr-dialog id="forgetDeviceDialog">
-      <div slot="title">$i18n{multideviceForgetDevice}</div>
-      <div slot="body">
-        <div class="first">
-          $i18n{multideviceForgetDeviceDialogMessage}
-        </div>
-      </div>
-      <div slot="button-container">
-        <cr-button class="cancel-button"
-            on-click="onForgetDeviceDialogCancelClick_">
-          $i18n{cancel}
-        </cr-button>
-        <cr-button id="confirmButton"
-            class="action-button"
-            on-click="onForgetDeviceDialogConfirmClick_">
-          $i18n{multideviceForgetDeviceDisconnect}
-        </cr-button>
-      </div>
-    </cr-dialog>
-  </template>
-  <script src="multidevice_subpage.js"></script>
-</dom-module>
+  </div>
+  <cr-button on-click="handleForgetDeviceClick_"
+      aria-labelledby="forgetDeviceLabel"
+      aria-describedby="forgetDeviceSummary"
+      deep-link-focus-id$="[[Setting.kForgetPhone]]">
+    $i18n{multideviceForgetDeviceDisconnect}
+  </cr-button>
+</div>
+<cr-dialog id="forgetDeviceDialog">
+  <div slot="title">$i18n{multideviceForgetDevice}</div>
+  <div slot="body">
+    <div class="first">
+      $i18n{multideviceForgetDeviceDialogMessage}
+    </div>
+  </div>
+  <div slot="button-container">
+    <cr-button class="cancel-button"
+        on-click="onForgetDeviceDialogCancelClick_">
+      $i18n{cancel}
+    </cr-button>
+    <cr-button id="confirmButton"
+        class="action-button"
+        on-click="onForgetDeviceDialogConfirmClick_">
+      $i18n{multideviceForgetDeviceDisconnect}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
index 47cfdef..e4a0a28 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
@@ -2,28 +2,53 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_link_row/cr_link_row.js';
+import '//resources/cr_elements/shared_vars_css.m.js';
+import '../../settings_shared_css.js';
+import '../../settings_vars_css.js';
+import './multidevice_combined_setup_item.js';
+import './multidevice_feature_item.js';
+import './multidevice_feature_toggle.js';
+import './multidevice_task_continuation_item.js';
+import './multidevice_tether_item.js';
+import './multidevice_wifi_sync_item.js';
+
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Route} from '../../router.js';
+import {DeepLinkingBehavior} from '../deep_linking_behavior.js';
+import {routes} from '../os_route.m.js';
+import {OsSettingsRoutes} from '../os_settings_routes.m.js';
+import {RouteObserverBehavior} from '../route_observer_behavior.js';
+
+import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_browser_proxy.js';
+import {MultiDeviceFeature, MultiDeviceFeatureState, MultiDeviceSettingsMode, PhoneHubFeatureAccessProhibitedReason} from './multidevice_constants.js';
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview
  * Subpage of settings-multidevice-page for managing multidevice features
  * individually and for forgetting a host.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-subpage',
 
   behaviors: [
     DeepLinkingBehavior,
     MultiDeviceFeatureBehavior,
-    settings.RouteObserverBehavior,
+    RouteObserverBehavior,
   ],
 
   properties: {
     /**
-     * Alias for allowing Polymer bindings to settings.routes.
+     * Alias for allowing Polymer bindings to routes.
      * @type {?OsSettingsRoutes}
      */
     routes: {
       type: Object,
-      value: settings.routes,
+      value: routes,
     },
 
     /**
@@ -49,21 +74,21 @@
     },
   },
 
-  /** @private {?settings.MultiDeviceBrowserProxy} */
+  /** @private {?MultiDeviceBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   created() {
-    this.browserProxy_ = settings.MultiDeviceBrowserProxyImpl.getInstance();
+    this.browserProxy_ = MultiDeviceBrowserProxyImpl.getInstance();
   },
 
   /**
-   * @param {!settings.Route} route
-   * @param {!settings.Route} oldRoute
+   * @param {!Route} route
+   * @param {!Route} oldRoute
    */
   currentRouteChanged(route, oldRoute) {
     // Does not apply to this page.
-    if (route !== settings.routes.MULTIDEVICE_FEATURES) {
+    if (route !== routes.MULTIDEVICE_FEATURES) {
       return;
     }
 
@@ -86,7 +111,7 @@
    */
   shouldShowIndividualFeatures_() {
     return this.pageContentData.mode ===
-        settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED;
+        MultiDeviceSettingsMode.HOST_SET_VERIFIED;
   },
 
   /**
@@ -95,8 +120,8 @@
    */
   shouldShowVerifyButton_() {
     return [
-      settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
-      settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
+      MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
+      MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
     ].includes(this.pageContentData.mode);
   },
 
@@ -106,7 +131,7 @@
    */
   shouldShowSuiteToggle_() {
     return this.pageContentData.mode ===
-        settings.MultiDeviceSettingsMode.HOST_SET_VERIFIED;
+        MultiDeviceSettingsMode.HOST_SET_VERIFIED;
   },
 
   /** @private */
@@ -131,8 +156,8 @@
    */
   getStatusInnerHtml_() {
     if ([
-          settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
-          settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
+          MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER,
+          MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION,
         ].includes(this.pageContentData.mode)) {
       return this.i18nAdvanced('multideviceVerificationText');
     }
@@ -145,8 +170,8 @@
    * @private
    */
   doesAndroidMessagesRequireSetUp_() {
-    return this.getFeatureState(settings.MultiDeviceFeature.MESSAGES) ===
-        settings.MultiDeviceFeatureState.FURTHER_SETUP_REQUIRED;
+    return this.getFeatureState(MultiDeviceFeature.MESSAGES) ===
+        MultiDeviceFeatureState.FURTHER_SETUP_REQUIRED;
   },
 
   /**
@@ -155,10 +180,9 @@
    */
   isAndroidMessagesSetupButtonDisabled_() {
     const messagesFeatureState =
-        this.getFeatureState(settings.MultiDeviceFeature.MESSAGES);
+        this.getFeatureState(MultiDeviceFeature.MESSAGES);
     return !this.isSuiteOn() ||
-        messagesFeatureState ===
-        settings.MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
+        messagesFeatureState === MultiDeviceFeatureState.PROHIBITED_BY_POLICY;
   },
 
   getPhoneHubNotificationsTooltip_() {
@@ -167,12 +191,11 @@
     }
 
     switch (this.pageContentData.notificationAccessProhibitedReason) {
-      case settings.PhoneHubFeatureAccessProhibitedReason.UNKNOWN:
+      case PhoneHubFeatureAccessProhibitedReason.UNKNOWN:
         return this.i18n('multideviceNotificationAccessProhibitedTooltip');
-      case settings.PhoneHubFeatureAccessProhibitedReason.WORK_PROFILE:
+      case PhoneHubFeatureAccessProhibitedReason.WORK_PROFILE:
         return this.i18n('multideviceNotificationAccessProhibitedTooltip');
-      case settings.PhoneHubFeatureAccessProhibitedReason
-          .DISABLED_BY_PHONE_POLICY:
+      case PhoneHubFeatureAccessProhibitedReason.DISABLED_BY_PHONE_POLICY:
         return this.i18n(
             'multideviceNotificationAccessProhibitedDisabledByAdminTooltip');
       default:
@@ -185,8 +208,7 @@
    * @private
    */
   shouldShowPhoneHubCameraRollItem_() {
-    return this.isFeatureSupported(
-               settings.MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL) &&
+    return this.isFeatureSupported(MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL) &&
         (!this.isPhoneHubCameraRollSetupRequired() ||
          !this.shouldShowPhoneHubCombinedSetupItem_());
   },
@@ -197,7 +219,7 @@
    */
   shouldShowPhoneHubNotificationsItem_() {
     return this.isFeatureSupported(
-               settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS) &&
+               MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS) &&
         (!this.isPhoneHubNotificationsSetupRequired() ||
          !this.shouldShowPhoneHubCombinedSetupItem_());
   },
@@ -207,7 +229,7 @@
    * @private
    */
   shouldShowPhoneHubAppsItem_() {
-    return this.isFeatureSupported(settings.MultiDeviceFeature.ECHE) &&
+    return this.isFeatureSupported(MultiDeviceFeature.ECHE) &&
         (!this.isPhoneHubAppsSetupRequired() ||
          !this.shouldShowPhoneHubCombinedSetupItem_());
   },
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.html
index 4cb47ce..8a81899 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.html
@@ -1,22 +1,8 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../os_settings_routes.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../../settings_shared_css.html">
-
-<dom-module id="settings-multidevice-task-continuation-disabled-link">
-  <template>
-    <style include="settings-shared">
-      :host {
-        --cr-subsequent-anchors-of-span-margin: 0;
-      }
-    </style>
-    <div id="container"
-        inner-h-t-m-l="[[getAriaLabelledContent_()]]">
-    </div>
-  </template>
-  <script src="multidevice_task_continuation_disabled_link.js"></script>
-</dom-module>
\ No newline at end of file
+<style include="settings-shared">
+  :host {
+    --cr-subsequent-anchors-of-span-margin: 0;
+  }
+</style>
+<div id="container"
+    inner-h-t-m-l="[[getAriaLabelledContent_()]]">
+</div>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js
index b64a8338..33a9e27 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js
@@ -2,6 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../../settings_shared_css.js';
+
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Router} from '../../router.js';
+import {routes} from '../os_route.m.js';
+
 /**
  * @fileoverview 'settings-multidevice-task-continuation-disabled-link'
  * creates a localized string with accessibility labels for the Phone Hub Task
@@ -13,6 +22,7 @@
  * and the other to a Learn More page for Phone Hub.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-task-continuation-disabled-link',
 
   behaviors: [I18nBehavior],
@@ -73,7 +83,7 @@
       window.open('chrome://settings/syncSetup/advanced');
       this.fire('opened-browser-advanced-sync-settings');
     } else {
-      settings.Router.getInstance().navigateTo(settings.routes.SYNC_ADVANCED);
+      Router.getInstance().navigateTo(routes.SYNC_ADVANCED);
     }
   },
 });
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.html
index b3b1d251..e65fb6c7 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.html
@@ -1,35 +1,17 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_item.html">
-<link rel="import" href="multidevice_task_continuation_disabled_link.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="multidevice_constants.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../../people_page/sync_browser_proxy.html">
-
-<dom-module id="settings-multidevice-task-continuation-item">
-  <template>
-    <style include="settings-shared"></style>
-    <settings-multidevice-feature-item id="phoneHubTaskContinuationItem"
-        feature="[[MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION]]"
-        page-content-data="[[pageContentData]]" is-sub-feature>
-      <template is="dom-if" if="[[!isChromeTabsSyncEnabled_]]" restamp>
-        <settings-multidevice-task-continuation-disabled-link
-            class="secondary"
-            id="featureSecondary"
-            slot="feature-summary">
-        </settings-multidevice-task-continuation-disabled-link>
-        <!-- Replace the standard feature-controller with an always disabled and
-          off cr-toggle when Chrome Sync Open Tabs is disabled. When Chrome Sync
-          is enabled the standard feature-controller is used. -->
-        <cr-toggle disabled slot="feature-controller">
-        </cr-toggle>
-      </template>
-    </settings-multidevice-feature-item>
+<style include="settings-shared"></style>
+<settings-multidevice-feature-item id="phoneHubTaskContinuationItem"
+    feature="[[MultiDeviceFeature.PHONE_HUB_TASK_CONTINUATION]]"
+    page-content-data="[[pageContentData]]" is-sub-feature>
+  <template is="dom-if" if="[[!isChromeTabsSyncEnabled_]]" restamp>
+    <settings-multidevice-task-continuation-disabled-link
+        class="secondary"
+        id="featureSecondary"
+        slot="feature-summary">
+    </settings-multidevice-task-continuation-disabled-link>
+    <!-- Replace the standard feature-controller with an always disabled and
+      off cr-toggle when Chrome Sync Open Tabs is disabled. When Chrome Sync
+      is enabled the standard feature-controller is used. -->
+    <cr-toggle disabled slot="feature-controller">
+    </cr-toggle>
   </template>
-  <script src="multidevice_task_continuation_item.js"></script>
-</dom-module>
+</settings-multidevice-feature-item>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.js
index 6785c80a..6f3cded0 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.js
@@ -2,6 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './multidevice_feature_item.js';
+import './multidevice_task_continuation_disabled_link.js';
+import '//resources/cr_elements/cr_toggle/cr_toggle.m.js';
+import '../../settings_shared_css.js';
+
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {SyncBrowserProxyImpl} from '../../people_page/sync_browser_proxy.js';
+
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview 'settings-multidevice-task-continuation-item' encapsulates
  * special logic for the phonehub task continuation item used in the multidevice
@@ -17,6 +29,7 @@
  * is a special case containing two links.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-task-continuation-item',
 
   behaviors: [
@@ -32,12 +45,12 @@
     },
   },
 
-  /** @private {?settings.SyncBrowserProxy} */
+  /** @private {?SyncBrowserProxy} */
   syncBrowserProxy_: null,
 
   /** @override */
   created() {
-    this.syncBrowserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
+    this.syncBrowserProxy_ = SyncBrowserProxyImpl.getInstance();
   },
 
   /** @override */
@@ -61,7 +74,7 @@
 
   /**
    * Handler for when the sync preferences are updated.
-   * @param {!settings.SyncPrefs} syncPrefs
+   * @param {!SyncPrefs} syncPrefs
    * @private
    */
   handleSyncPrefsChanged_(syncPrefs) {
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html
index fec27f66..c5f1e4a 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html
@@ -1,32 +1,13 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_listener_behavior.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/mojo_interface_provider.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_icon.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/onc_mojo.html">
-<link rel="import" href="../../i18n_setup.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../os_settings_routes.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../../settings_vars_css.html">
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_item.html">
-
-<dom-module id="settings-multidevice-tether-item">
-  <template>
-    <style include="settings-shared"></style>
-    <settings-multidevice-feature-item page-content-data="[[pageContentData]]"
-        feature="[[MultiDeviceFeature.INSTANT_TETHERING]]"
-        subpage-route="[[routes.INTERNET_NETWORKS]]"
-        subpage-route-url-search-params=
-            "[[getTetherNetworkUrlSearchParams_()]]">
-      <network-icon slot="icon"
-          aria-hidden="true"
-          show-technology-badge="[[showTechnologyBadge_]]"
-          network-state="[[activeNetworkState_]]"
-          device-state="[[deviceState_]]">
-      </network-icon>
-    </settings-multidevice-feature-item>
-  </template>
-  <script src="multidevice_tether_item.js"></script>
-</dom-module>
+<style include="settings-shared"></style>
+<settings-multidevice-feature-item page-content-data="[[pageContentData]]"
+    feature="[[MultiDeviceFeature.INSTANT_TETHERING]]"
+    subpage-route="[[routes.INTERNET_NETWORKS]]"
+    subpage-route-url-search-params=
+        "[[getTetherNetworkUrlSearchParams_()]]">
+    <network-icon slot="icon"
+        aria-hidden="true"
+        show-technology-badge="[[showTechnologyBadge_]]"
+        network-state="[[activeNetworkState_]]"
+        device-state="[[deviceState_]]">
+    </network-icon>
+</settings-multidevice-feature-item>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.js
index 6b0cabcb..b6eef7a8 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.js
@@ -2,6 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '//resources/cr_components/chromeos/network/network_icon.m.js';
+import '../../settings_shared_css.js';
+import '../../settings_vars_css.js';
+import './multidevice_feature_item.js';
+
+import {MojoInterfaceProviderImpl} from '//resources/cr_components/chromeos/network/mojo_interface_provider.m.js';
+import {NetworkListenerBehavior} from '//resources/cr_components/chromeos/network/network_listener_behavior.m.js';
+import {OncMojo} from '//resources/cr_components/chromeos/network/onc_mojo.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {loadTimeData} from '../../i18n_setup.js';
+import {routes} from '../os_route.m.js';
+import {OsSettingsRoutes} from '../os_settings_routes.m.js';
+
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview
  * This element provides a layer between the settings-multidevice-subpage
@@ -10,8 +26,8 @@
  * networkConfig mojo API as well as updating the data in real time. It
  * serves a role comparable to the internet_page's network-summary element.
  */
-
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-tether-item',
 
   behaviors: [
@@ -35,12 +51,12 @@
     activeNetworkState_: Object,
 
     /**
-     * Alias for allowing Polymer bindings to settings.routes.
+     * Alias for allowing Polymer bindings to routes.
      * @type {?OsSettingsRoutes}
      */
     routes: {
       type: Object,
-      value: settings.routes,
+      value: routes,
     },
 
     /**
@@ -61,8 +77,8 @@
 
   /** @override */
   created() {
-    this.networkConfig_ = network_config.MojoInterfaceProviderImpl.getInstance()
-                              .getMojoServiceRemote();
+    this.networkConfig_ =
+        MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
   },
 
   /** @override */
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.html
index ec8fa62..8a81899 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.html
@@ -1,23 +1,8 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../os_settings_routes.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../../settings_shared_css.html">
-
-<dom-module id="settings-multidevice-wifi-sync-disabled-link">
-  <template>
-    <style include="settings-shared">
-      :host {
-        --cr-subsequent-anchors-of-span-margin: 0;
-      }
-    </style>
-    <div id="container"
-        inner-h-t-m-l="[[getAriaLabelledContent_()]]">
-    </div>
-  </template>
-  <script src="multidevice_wifi_sync_disabled_link.js"></script>
-</dom-module>
\ No newline at end of file
+<style include="settings-shared">
+  :host {
+    --cr-subsequent-anchors-of-span-margin: 0;
+  }
+</style>
+<div id="container"
+    inner-h-t-m-l="[[getAriaLabelledContent_()]]">
+</div>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
index f5f7cb98..800e560 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
@@ -2,6 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../../settings_shared_css.js';
+
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {Router} from '../../router.js';
+import {routes} from '../os_route.m.js';
+
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview 'settings-multidevice-wifi-sync-disabled-link' creates a
  * localized string with accessibility labels for the Wifi Sync feature when
@@ -12,6 +22,7 @@
  * and the other to a Learn More page for Wifi Sync.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-wifi-sync-disabled-link',
 
   behaviors: [
@@ -65,9 +76,9 @@
     if (loadTimeData.getBoolean('syncSettingsCategorizationEnabled')) {
       // If syncSettingsCategorization is enabled, then WiFi sync is controlled
       // by the OS sync settings, not the browser sync settings.
-      settings.Router.getInstance().navigateTo(settings.routes.OS_SYNC);
+      Router.getInstance().navigateTo(routes.OS_SYNC);
     } else {
-      settings.Router.getInstance().navigateTo(settings.routes.SYNC_ADVANCED);
+      Router.getInstance().navigateTo(routes.SYNC_ADVANCED);
     }
   },
 });
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.html
index c67f1f5..461b050 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.html
@@ -1,39 +1,21 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="multidevice_feature_behavior.html">
-<link rel="import" href="multidevice_feature_item.html">
-<link rel="import" href="multidevice_wifi_sync_disabled_link.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../os_people_page/os_sync_browser_proxy.html">
-<link rel="import" href="../../people_page/sync_browser_proxy.html">
-
-<dom-module id="settings-multidevice-wifi-sync-item">
-  <template>
-    <style include="settings-shared"></style>
-    <settings-multidevice-feature-item id="wifiSyncItem"
-        feature="[[MultiDeviceFeature.WIFI_SYNC]]"
-        page-content-data="[[pageContentData]]">
-      <template is="dom-if" if="[[!isWifiSyncV1Enabled_]]" restamp>
-        <settings-multidevice-wifi-sync-disabled-link
-            class="secondary"
-            id="featureSecondary"
-            slot="feature-summary">
-        </settings-multidevice-wifi-sync-disabled-link>
-        <!-- Replace the standard feature-controller with an always disabled
-          cr-toggle when Chrome Sync is disabled. When Chrome Sync is enabled
-          the standard feature-controller is used. This is done to avoid adding
-          extra logic in the standard feature-controller to keep the toggle
-          unchecked when Chrome Sync is on but Wifi Sync host is enabled on the
-          backend -->
-        <cr-toggle disabled="true"
-            slot="feature-controller">
-        </cr-toggle>
-      </template>
-    </settings-multidevice-feature-item>
+<style include="settings-shared"></style>
+<settings-multidevice-feature-item id="wifiSyncItem"
+    feature="[[MultiDeviceFeature.WIFI_SYNC]]"
+    page-content-data="[[pageContentData]]">
+  <template is="dom-if" if="[[!isWifiSyncV1Enabled_]]" restamp>
+    <settings-multidevice-wifi-sync-disabled-link
+        class="secondary"
+        id="featureSecondary"
+        slot="feature-summary">
+    </settings-multidevice-wifi-sync-disabled-link>
+    <!-- Replace the standard feature-controller with an always disabled
+      cr-toggle when Chrome Sync is disabled. When Chrome Sync is enabled
+      the standard feature-controller is used. This is done to avoid adding
+      extra logic in the standard feature-controller to keep the toggle
+      unchecked when Chrome Sync is on but Wifi Sync host is enabled on the
+      backend -->
+    <cr-toggle disabled="true"
+        slot="feature-controller">
+    </cr-toggle>
   </template>
-  <script src="multidevice_wifi_sync_item.js"></script>
-</dom-module>
+</settings-multidevice-feature-item>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.js
index 9cec310..92efa01 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.js
@@ -2,6 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './multidevice_feature_item.js';
+import './multidevice_wifi_sync_disabled_link.js';
+import '//resources/cr_elements/cr_toggle/cr_toggle.m.js';
+import '../../settings_shared_css.js';
+
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {SyncBrowserProxyImpl} from '../../people_page/sync_browser_proxy.js';
+import {OsSyncBrowserProxy, OsSyncBrowserProxyImpl, OsSyncPrefs} from '../os_people_page/os_sync_browser_proxy.m.js';
+
+import {MultiDeviceFeatureBehavior} from './multidevice_feature_behavior.js';
+
 /**
  * @fileoverview 'settings-multidevice-wifi-sync-item' encapsulates special
  * logic for the wifi sync item used in the multidevice subpage.
@@ -15,6 +29,7 @@
  * special case containing two links.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-multidevice-wifi-sync-item',
 
   behaviors: [
@@ -27,10 +42,10 @@
     isWifiSyncV1Enabled_: Boolean,
   },
 
-  /** @private {?settings.OsSyncBrowserProxy} */
+  /** @private {?OsSyncBrowserProxy} */
   osSyncBrowserProxy_: null,
 
-  /** @private {?settings.SyncBrowserProxy} */
+  /** @private {?SyncBrowserProxy} */
   syncBrowserProxy_: null,
 
   /** @override */
@@ -49,15 +64,15 @@
   /** @override */
   created() {
     if (loadTimeData.getBoolean('syncSettingsCategorizationEnabled')) {
-      this.osSyncBrowserProxy_ = settings.OsSyncBrowserProxyImpl.getInstance();
+      this.osSyncBrowserProxy_ = OsSyncBrowserProxyImpl.getInstance();
     } else {
-      this.syncBrowserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
+      this.syncBrowserProxy_ = SyncBrowserProxyImpl.getInstance();
     }
   },
 
   /**
    * Handler for when the sync preferences are updated.
-   * @param {!settings.SyncPrefs} syncPrefs
+   * @param {!SyncPrefs} syncPrefs
    * @private
    */
   handleSyncPrefsChanged_(syncPrefs) {
@@ -67,7 +82,7 @@
 
   /**
    * Handler for when os sync preferences are updated.
-   * @param {!settings.OsSyncPrefs} osSyncPrefs
+   * @param {!OsSyncPrefs} osSyncPrefs
    * @private
    */
   handleOsSyncPrefsChanged_(osSyncPrefs) {
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
index bfddc1d..ce75d7f 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
@@ -87,7 +87,7 @@
     "..:os_route",
     "..:route_observer_behavior",
     "../..:router",
-    "../multidevice_page:multidevice_smartlock_item.m",
+    "../multidevice_page:multidevice_smartlock_item",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_components/chromeos/quick_unlock:lock_screen_constants.m",
     "//ui/webui/resources/js:assert.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
index 44965bd2..edcffb6 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
@@ -25,7 +25,7 @@
 import '../../prefs/prefs.js';
 import '../../settings_shared_css.js';
 import '../../settings_vars_css.js';
-import '../multidevice_page/multidevice_smartlock_item.m.js';
+import '../multidevice_page/multidevice_smartlock_item.js';
 
 import {LockScreenProgress, recordLockScreenProgress} from '//resources/cr_components/chromeos/quick_unlock/lock_screen_constants.m.js';
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 6c3d02f..ca62473 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -382,6 +382,25 @@
                                  "chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_add_account_dialog.html",
                                  "chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.html",
                                  "chrome/browser/resources/settings/chromeos/keyboard_shortcut_banner/keyboard_shortcut_banner.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_combined_setup_item.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_behavior.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_toggle.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_radio_button.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_tether_item.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_item.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_item.html",
+                                 "chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html",
                                  "chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_confirm_page.html",
                                  "chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_contact_visibility_dialog.html",
                                  "chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.html",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index dfa6a8a..9775c87 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -35,7 +35,7 @@
 import './internet_page/tether_connection_dialog.js';
 import './kerberos_page/kerberos_accounts.js';
 import './kerberos_page/kerberos_page.js';
-import './multidevice_page/multidevice_page.m.js';
+import './multidevice_page/multidevice_page.js';
 import './nearby_share_page/nearby_share_receive_dialog.js';
 import './nearby_share_page/nearby_share_subpage.js';
 import './personalization_page/change_picture.js';
@@ -121,10 +121,10 @@
 export {InternetPageBrowserProxy, InternetPageBrowserProxyImpl} from './internet_page/internet_page_browser_proxy.js';
 export {KerberosAccountsBrowserProxyImpl, KerberosConfigErrorCode, KerberosErrorType} from './kerberos_page/kerberos_accounts_browser_proxy.js';
 export {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from './metrics_recorder.m.js';
-export {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_page/multidevice_browser_proxy.m.js';
-export {MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, PhoneHubFeatureAccessProhibitedReason, PhoneHubFeatureAccessStatus, PhoneHubPermissionsSetupMode, SmartLockSignInEnabledState} from './multidevice_page/multidevice_constants.m.js';
-export {NotificationAccessSetupOperationStatus} from './multidevice_page/multidevice_notification_access_setup_dialog.m.js';
-export {PermissionsSetupStatus, SetupFlowStatus} from './multidevice_page/multidevice_permissions_setup_dialog.m.js';
+export {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_page/multidevice_browser_proxy.js';
+export {MultiDeviceFeature, MultiDeviceFeatureState, MultiDevicePageContentData, MultiDeviceSettingsMode, PhoneHubFeatureAccessProhibitedReason, PhoneHubFeatureAccessStatus, PhoneHubPermissionsSetupMode, SmartLockSignInEnabledState} from './multidevice_page/multidevice_constants.js';
+export {NotificationAccessSetupOperationStatus} from './multidevice_page/multidevice_notification_access_setup_dialog.js';
+export {PermissionsSetupStatus, SetupFlowStatus} from './multidevice_page/multidevice_permissions_setup_dialog.js';
 export {Account, NearbyAccountManagerBrowserProxy, NearbyAccountManagerBrowserProxyImpl} from './nearby_share_page/nearby_account_manager_browser_proxy.js';
 export {getReceiveManager, observeReceiveManager, setReceiveManagerForTesting} from './nearby_share_page/nearby_share_receive_manager.js';
 export {dataUsageStringToEnum, NearbyShareDataUsage} from './nearby_share_page/types.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
index 8ba15b43..e19229a2 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
@@ -28,7 +28,7 @@
     "../crostini_page:crostini_page",
     "../device_page:device_page",
     "../kerberos_page:kerberos_page",
-    "../multidevice_page:multidevice_page.m",
+    "../multidevice_page:multidevice_page",
     "../os_a11y_page:os_a11y_page",
     "../os_apps_page:android_apps_browser_proxy",
     "../os_apps_page:os_apps_page",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
index 0ce06668..91bdd27 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
@@ -25,7 +25,7 @@
 import '../device_page/device_page.js';
 import '../internet_page/internet_page.js';
 import '../kerberos_page/kerberos_page.js';
-import '../multidevice_page/multidevice_page.m.js';
+import '../multidevice_page/multidevice_page.js';
 import '../os_bluetooth_page/os_bluetooth_page.js';
 import '../os_icons.js';
 
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 7c4e46ec..9dcd2757 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -1,5 +1,6 @@
 # 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.
 
 import("//components/safe_browsing/buildflags.gni")
 import("//extensions/buildflags/buildflags.gni")
diff --git a/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer.cc b/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer.cc
index 3b1886a1..b30452b 100644
--- a/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer.cc
+++ b/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer.cc
@@ -27,17 +27,40 @@
 
 const int kThresholdForInFlowNotificationMinutes = 5;
 
-bool CanQueryTailoredSecurity(GURL url) {
+bool CanQueryTailoredSecurityForUrl(GURL url) {
   return url.DomainIs("google.com") || url.DomainIs("youtube.com");
 }
 
+bool CanQueryTailoredSecurity(Profile* profile) {
+  if (IsEnhancedProtectionEnabled(*profile->GetPrefs()))
+    return false;
+
+  // We should only trigger the unconsented UX if the user is not consented to
+  // sync. Syncing users have different UX, handled by the
+  // `ChromeTailoredSecurityService`.
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(profile);
+  if (!identity_manager ||
+      identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
+    return false;
+  }
+
+  if (profile->GetPrefs()->GetBoolean(
+          prefs::kAccountTailoredSecurityShownNotification)) {
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace
 
 TailoredSecurityUrlObserver::~TailoredSecurityUrlObserver() {
   if (service_) {
     service_->RemoveObserver(this);
-    if (focused_ && CanQueryTailoredSecurity(last_url_)) {
+    if (has_query_request_) {
       service_->RemoveQueryRequest();
+      has_query_request_ = false;
     }
   }
 }
@@ -135,13 +158,24 @@
 
 void TailoredSecurityUrlObserver::UpdateFocusAndURL(bool focused,
                                                     const GURL& url) {
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
+  if (!CanQueryTailoredSecurity(profile)) {
+    return;
+  }
+
   if (service_) {
-    bool should_query = focused && CanQueryTailoredSecurity(url);
-    bool old_should_query = focused_ && CanQueryTailoredSecurity(last_url_);
-    if (should_query && !old_should_query)
+    bool should_query = focused && CanQueryTailoredSecurityForUrl(url);
+    bool old_should_query =
+        focused_ && CanQueryTailoredSecurityForUrl(last_url_);
+    if (should_query && !old_should_query) {
       service_->AddQueryRequest();
-    if (!should_query && old_should_query)
+      has_query_request_ = true;
+    }
+    if (!should_query && old_should_query) {
       service_->RemoveQueryRequest();
+      has_query_request_ = false;
+    }
   }
 
   focused_ = focused;
diff --git a/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer.h b/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer.h
index 2331aa9f..e612aa3 100644
--- a/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer.h
+++ b/chrome/browser/safe_browsing/tailored_security/tailored_security_url_observer.h
@@ -71,6 +71,9 @@
   // The most recent URL the WebContents navigated to.
   GURL last_url_;
 
+  // Whether we currently have a query request.
+  bool has_query_request_ = false;
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
 
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextIPHController.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextIPHController.java
index b40968c..4ee0121 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextIPHController.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextIPHController.java
@@ -23,6 +23,7 @@
 import org.chromium.components.messages.MessageDispatcherProvider;
 import org.chromium.components.messages.MessageIdentifier;
 import org.chromium.components.messages.MessageScopeType;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
@@ -95,9 +96,10 @@
                 model, tab.getWebContents(), MessageScopeType.NAVIGATION, false);
     }
 
-    private void onMessageButtonClicked() {
+    private @PrimaryActionClickBehavior int onMessageButtonClicked() {
         onOpenInChrome(LinkToTextHelper.SHARED_HIGHLIGHTING_SUPPORT_URL);
         mTracker.dismissed(FEATURE_NAME);
+        return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
     }
 
     private void onMessageDismissed(Integer dismissReason) {
diff --git a/chrome/browser/signin/chrome_signin_helper.cc b/chrome/browser/signin/chrome_signin_helper.cc
index 01e8d788..87020f9 100644
--- a/chrome/browser/signin/chrome_signin_helper.cc
+++ b/chrome/browser/signin/chrome_signin_helper.cc
@@ -33,8 +33,9 @@
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/process_dice_header_delegate_impl.h"
 #include "chrome/browser/signin/signin_features.h"
+#include "chrome/browser/signin/signin_manager.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
 #include "chrome/browser/tab_contents/tab_util.h"
-#include "chrome/browser/ui/profile_picker.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
 #include "chrome/browser/ui/webui/signin/signin_ui_error.h"
@@ -70,11 +71,6 @@
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "chrome/browser/lacros/account_manager/account_manager_util.h"
-#include "chrome/browser/lacros/account_manager/account_profile_mapper.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
 #include "chrome/browser/ui/webui/signin/turn_sync_on_helper.h"
 #endif
@@ -165,25 +161,6 @@
 
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-void OnLacrosAccountsAvailableAsSecondaryFetched(
-    AccountProfileMapper* mapper,
-    const base::FilePath& profile_path,
-    const std::vector<account_manager::Account>& accounts) {
-  if (!accounts.empty()) {
-    // Pass in the current profile to signal that the user wants to select a
-    // _secondary_ account for this particular profile.
-    ProfilePicker::Show(ProfilePicker::Params::ForLacrosSelectAvailableAccount(
-        profile_path, base::OnceCallback<void(const std::string&)>()));
-    return;
-  }
-  mapper->ShowAddAccountDialog(profile_path,
-                               account_manager::AccountManagerFacade::
-                                   AccountAdditionSource::kOgbAddAccount,
-                               AccountProfileMapper::AddAccountCallback());
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 class RequestDestructionObserverUserData : public base::SupportsUserData::Data {
  public:
   explicit RequestDestructionObserverUserData(base::OnceClosure closure)
@@ -346,12 +323,9 @@
 
     AccountProfileMapper* mapper =
         g_browser_process->profile_manager()->GetAccountProfileMapper();
-    GetAccountsAvailableAsSecondary(
-        mapper, profile->GetPath(),
-        // It's safe to bind raw `mapper`, the callback gets called iff
-        // `mapper` is still valid.
-        base::BindOnce(&OnLacrosAccountsAvailableAsSecondaryFetched, mapper,
-                       profile->GetPath()));
+    SigninManagerFactory::GetForProfile(profile)->StartWebSigninFlow(
+        profile->GetPath(), mapper,
+        account_reconcilor->GetConsistencyCookieManager());
 #else
     ::GetAccountManagerFacade(profile->GetPath().value())
         ->ShowAddAccountDialog(account_manager::AccountManagerFacade::
diff --git a/chrome/browser/signin/signin_manager.cc b/chrome/browser/signin/signin_manager.cc
index 2e7736b6..8fa2b188 100644
--- a/chrome/browser/signin/signin_manager.cc
+++ b/chrome/browser/signin/signin_manager.cc
@@ -4,14 +4,17 @@
 
 #include "chrome/browser/signin/signin_manager.h"
 
-#include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
+#include "base/bind.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/account_manager/web_signin_helper_lacros.h"
+#endif
+
 SigninManager::SigninManager(PrefService* prefs,
                              signin::IdentityManager* identity_manager)
     : prefs_(prefs), identity_manager_(identity_manager) {
@@ -26,6 +29,27 @@
 
 SigninManager::~SigninManager() = default;
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+void SigninManager::StartWebSigninFlow(
+    const base::FilePath& profile_path,
+    AccountProfileMapper* account_profile_mapper,
+    signin::ConsistencyCookieManager* consistency_cookie_manager) {
+  if (web_signin_helper_lacros_) {
+    // There is already a signin flow in progress.
+    // TODO(https://crbug.com/1260291): Activate the profile picker if it's
+    // already open.
+    return;
+  }
+
+  web_signin_helper_lacros_ = std::make_unique<WebSigninHelperLacros>(
+      profile_path, account_profile_mapper, identity_manager_,
+      consistency_cookie_manager,
+      // Using `base::Unretained()` is fine because this owns the helper.
+      base::BindOnce(&SigninManager::OnWebSigninHelperLacrosComplete,
+                     base::Unretained(this)));
+}
+#endif
+
 void SigninManager::UpdateUnconsentedPrimaryAccount() {
   // Only update the unconsented primary account only after accounts are loaded.
   if (!identity_manager_->AreRefreshTokensLoaded()) {
@@ -218,3 +242,9 @@
 void SigninManager::OnSigninAllowedPrefChanged() {
   UpdateUnconsentedPrimaryAccount();
 }
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+void SigninManager::OnWebSigninHelperLacrosComplete() {
+  web_signin_helper_lacros_.reset();
+}
+#endif
diff --git a/chrome/browser/signin/signin_manager.h b/chrome/browser/signin/signin_manager.h
index 633e48a0..d992bad0 100644
--- a/chrome/browser/signin/signin_manager.h
+++ b/chrome/browser/signin/signin_manager.h
@@ -5,23 +5,47 @@
 #ifndef CHROME_BROWSER_SIGNIN_SIGNIN_MANAGER_H_
 #define CHROME_BROWSER_SIGNIN_SIGNIN_MANAGER_H_
 
+#include <memory>
+
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/prefs/pref_member.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
+namespace base {
+class FilePath;
+}
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+namespace signin {
+class ConsistencyCookieManager;
+}
+
+class AccountProfileMapper;
+class WebSigninHelperLacros;
+#endif
+
 class PrefService;
 
 class SigninManager : public KeyedService,
                       public signin::IdentityManager::Observer {
  public:
   SigninManager(PrefService* prefs, signin::IdentityManager* identity_manger);
+  ~SigninManager() override;
+
   SigninManager(const SigninManager&) = delete;
   SigninManager& operator=(const SigninManager&) = delete;
 
-  ~SigninManager() override;
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  void StartWebSigninFlow(
+      const base::FilePath& profile_path,
+      AccountProfileMapper* account_profile_mapper,
+      signin::ConsistencyCookieManager* consistency_cookie_manager);
+#endif
 
  private:
   // Updates the cached version of unconsented primary account and notifies the
@@ -63,6 +87,10 @@
 
   void OnSigninAllowedPrefChanged();
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  void OnWebSigninHelperLacrosComplete();
+#endif
+
   raw_ptr<PrefService> prefs_;
   raw_ptr<signin::IdentityManager> identity_manager_;
   base::ScopedObservation<signin::IdentityManager,
@@ -72,6 +100,10 @@
   // Helper object to listen for changes to the signin allowed preference.
   BooleanPrefMember signin_allowed_;
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  std::unique_ptr<WebSigninHelperLacros> web_signin_helper_lacros_;
+#endif
+
   base::WeakPtrFactory<SigninManager> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc
index 5a9db26c..db5611e 100644
--- a/chrome/browser/themes/theme_service_unittest.cc
+++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -807,8 +807,8 @@
   EXPECT_TRUE(registry_->GetInstalledExtension(scoper.extension_id()));
 }
 
-// TODO(crbug.com/1056953): Enable on Fuchsia, and Linux GTK.
-#if BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(USE_GTK)
+// TODO(crbug.com/1056953): Enable on Linux GTK.
+#if BUILDFLAG(USE_GTK)
 #define MAYBE_GetColor DISABLED_GetColor
 #else
 #define MAYBE_GetColor GetColor
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 1d6d643a..69b4edd5 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2935,6 +2935,8 @@
       "webui/signin/inline_login_handler_modal_delegate.h",
       "webui/signin/signin_helper_chromeos.cc",
       "webui/signin/signin_helper_chromeos.h",
+      "webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.cc",
+      "webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.h",
       "window_sizer/window_sizer_chromeos.cc",
       "window_sizer/window_sizer_chromeos.h",
     ]
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageController.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageController.java
index e92bc2b2..b5cb256 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageController.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageController.java
@@ -30,6 +30,7 @@
 import org.chromium.components.messages.MessageBannerProperties;
 import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -134,7 +135,10 @@
                         .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT,
                                 resources.getString(R.string.auto_dark_message_button))
                         .with(MessageBannerProperties.ON_PRIMARY_ACTION,
-                                () -> { onOptOutPrimaryAction(activity, settingsLauncher); })
+                                () -> {
+                                    onOptOutPrimaryAction(activity, settingsLauncher);
+                                    return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
+                                })
                         .with(MessageBannerProperties.ON_DISMISSED,
                                 (dismissReason) -> {
                                     onOptOutMessageDismissed(profile, dismissReason);
@@ -162,7 +166,10 @@
                         .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT,
                                 resources.getString(R.string.auto_dark_message_opt_in_button))
                         .with(MessageBannerProperties.ON_PRIMARY_ACTION,
-                                () -> { onOptInPrimaryAction(profile, webContents); })
+                                () -> {
+                                    onOptInPrimaryAction(profile, webContents);
+                                    return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
+                                })
                         .with(MessageBannerProperties.ON_DISMISSED,
                                 (dismissReason) -> {
                                     onOptInMessageDismissed(activity, profile, webContents,
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageControllerUnitTest.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageControllerUnitTest.java
index 539d5617..97dc5562 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageControllerUnitTest.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageControllerUnitTest.java
@@ -106,7 +106,7 @@
         }
 
         private void clickButton() {
-            mShownMessageModel.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
+            mShownMessageModel.get(MessageBannerProperties.ON_PRIMARY_ACTION).get();
         }
     }
 
diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn
index fb7c9bf1..6e7ea29 100644
--- a/chrome/browser/ui/android/omnibox/BUILD.gn
+++ b/chrome/browser/ui/android/omnibox/BUILD.gn
@@ -398,7 +398,7 @@
     "//chrome/test/android:chrome_java_test_support",
     "//components/browser_ui/site_settings/android:java",
     "//components/browser_ui/styles/android:java",
-    "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/theme/android:java_resources",
     "//components/browser_ui/widget/android:java",
     "//components/content_settings/android:content_settings_enums_java",
     "//components/embedder_support/android:util_java",
diff --git a/chrome/browser/ui/color/chrome_color_mixer.cc b/chrome/browser/ui/color/chrome_color_mixer.cc
index 6405b97ed..0417dea 100644
--- a/chrome/browser/ui/color/chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/chrome_color_mixer.cc
@@ -226,7 +226,7 @@
       kColorToolbarButtonIcon, kColorDownloadShelfBackground, 0x3A);
   mixer[kColorDownloadShelfForeground] = {kColorToolbarText};
   mixer[kColorDownloadToolbarButtonActive] = {ui::kColorThrobber};
-  mixer[kColorDownloadToolbarButtonInactive] = {ui::kColorMidground};
+  mixer[kColorDownloadToolbarButtonInactive] = {kColorToolbarButtonIcon};
   mixer[kColorDownloadToolbarButtonRingBackground] = {
       SkColorSetA(kColorDownloadToolbarButtonInactive, 0x33)};
   mixer[kColorEyedropperBoundary] = {SK_ColorDKGRAY};
diff --git a/chrome/browser/ui/side_search/side_search_tab_contents_helper.cc b/chrome/browser/ui/side_search/side_search_tab_contents_helper.cc
index 02fdd9b..d6a70da7 100644
--- a/chrome/browser/ui/side_search/side_search_tab_contents_helper.cc
+++ b/chrome/browser/ui/side_search/side_search_tab_contents_helper.cc
@@ -172,15 +172,27 @@
       net::DefineNetworkTrafficAnnotation("side_search_availability_test", R"(
         semantics {
           sender: "Side Search Tab Helper"
-          description: "Pings for the Side Search Google SRP page to check if "
-            "the page is available."
+          description:
+            "After the user has successfully navigated to a search results "
+            "page (SRP) belonging to their set default search provider, a HEAD "
+            "request is made to the side search SRP URL.\n"
+            "The side search SRP URL is generated by taking the original SRP "
+            "URL and appending the side search param specified in the search "
+            "engine's prepopulated_engines.json entry.\n"
+            "This is only done once per session for the currently set default "
+            "search engine to check the availability of the side search SRP "
+            "before enabling the feature. This is also gated on the current "
+            "default search engine signalling participation in the feature "
+            "with appropriate updates to its prepopulated_engines.json entry."
           trigger:
-            "After the user has successfully committed a navigation to a Google"
-            "SRP in a tab contents."
+            "After the user has successfully committed a navigation to a "
+            "default search engine SRP in a tab contents and the availability "
+            "bit for the default search engine has not already been set for "
+            "this session."
           data:
-            "No data sent except for the additional sidesearch URL parameter. "
-            "Data does not contain PII."
-          destination: GOOGLE_OWNED_SERVICE
+            "The HEAD request includes the original search URL with the "
+            "addition of the side search header but no PII data / cookies."
+          destination: WEBSITE
         }
         policy {
           cookies_allowed: NO
@@ -197,6 +209,8 @@
                                 ->GetDefaultStoragePartition()
                                 ->GetURLLoaderFactoryForBrowserProcess();
   auto request = std::make_unique<network::ResourceRequest>();
+  // Ensure cookies are not propagated with the request.
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   request->url = last_search_url_.value();
   // Make a HEAD request to avoid generating an actual SRP page when checking
   // for availability of the side panel SRP.
diff --git a/chrome/browser/ui/user_education/feature_promo_controller.h b/chrome/browser/ui/user_education/feature_promo_controller.h
index 18b296c..9c549f2 100644
--- a/chrome/browser/ui/user_education/feature_promo_controller.h
+++ b/chrome/browser/ui/user_education/feature_promo_controller.h
@@ -254,7 +254,7 @@
   // accelerator to focus the help bubble.
   virtual std::u16string GetFocusHelpBubbleScreenReaderHint(
       FeaturePromoSpecification::PromoType promo_type,
-      const ui::TrackedElement* anchor_element,
+      ui::TrackedElement* anchor_element,
       bool is_critical_promo) const = 0;
 
   FeaturePromoRegistry* registry() { return registry_; }
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
index 67ffdd0..ad543ba 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
@@ -481,7 +481,6 @@
         prompt_->GetUserCount(), CONTEXT_DIALOG_BODY_TEXT_SMALL,
         views::style::STYLE_SECONDARY);
     user_count->SetAutoColorReadabilityEnabled(false);
-    user_count->SetEnabledColor(SK_ColorGRAY);
     user_count->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     webstore_data_container->AddChildView(std::move(user_count));
 
@@ -601,16 +600,27 @@
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
   const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
-  auto extension_info_container = std::make_unique<CustomScrollableView>(this);
+  auto extension_info_and_justification_container =
+      std::make_unique<CustomScrollableView>(this);
   const gfx::Insets content_insets = provider->GetDialogInsetsForContentType(
       views::DialogContentType::kControl, views::DialogContentType::kControl);
-  extension_info_container->SetBorder(views::CreateEmptyBorder(
-      0, content_insets.left(), 0, content_insets.right()));
+  extension_info_and_justification_container->SetBorder(
+      views::CreateEmptyBorder(0, content_insets.left(), 0,
+                               content_insets.right()));
+  extension_info_and_justification_container->SetLayoutManager(
+      std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kVertical, gfx::Insets(),
+          provider->GetDistanceMetric(
+              views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
+  const int content_width =
+      GetPreferredSize().width() -
+      extension_info_and_justification_container->GetInsets().width();
+  auto* extension_info_container =
+      extension_info_and_justification_container->AddChildView(
+          std::make_unique<views::View>());
   extension_info_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical, gfx::Insets(),
       provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
-  const int content_width = GetPreferredSize().width() -
-                            extension_info_container->GetInsets().width();
 
   std::vector<ExtensionInfoSection> sections;
   if (prompt_->ShouldShowPermissions()) {
@@ -675,19 +685,16 @@
   // the |sections| vector since it is later referenced to extract the textfield
   // string.
   if (is_justification_field_enabled) {
-    std::unique_ptr<views::Separator> separator =
-        std::make_unique<views::Separator>();
-    separator->SetColor(SK_ColorTRANSPARENT);
-    extension_info_container->AddChildView(std::move(separator));
-
-    justification_view_ = extension_info_container->AddChildView(
-        std::make_unique<ExtensionJustificationView>(this));
+    justification_view_ =
+        extension_info_and_justification_container->AddChildView(
+            std::make_unique<ExtensionJustificationView>(this));
   }
 
   scroll_view_ = new views::ScrollView();
   scroll_view_->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
-  scroll_view_->SetContents(std::move(extension_info_container));
+  scroll_view_->SetContents(
+      std::move(extension_info_and_justification_container));
   scroll_view_->ClipHeightTo(
       0, provider->GetDistanceMetric(
              views::DISTANCE_DIALOG_SCROLLABLE_AREA_MAX_HEIGHT));
diff --git a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
index 0178c82..51a1ed26 100644
--- a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
+++ b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
@@ -54,29 +54,6 @@
       chrome::FindBrowserWithWebContents(web_contents));
 }
 
-class CircleImageSource : public gfx::CanvasImageSource {
- public:
-  CircleImageSource(int size, SkColor color)
-      : gfx::CanvasImageSource(gfx::Size(size, size)), color_(color) {}
-
-  CircleImageSource(const CircleImageSource&) = delete;
-  CircleImageSource& operator=(const CircleImageSource&) = delete;
-
-  ~CircleImageSource() override = default;
-
-  void Draw(gfx::Canvas* canvas) override {
-    float radius = size().width() / 2.0f;
-    cc::PaintFlags flags;
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    flags.setAntiAlias(true);
-    flags.setColor(color_);
-    canvas->DrawCircle(gfx::PointF(radius, radius), radius, flags);
-  }
-
- private:
-  SkColor color_;
-};
-
 class SuperimposedOffsetImageSource : public gfx::CanvasImageSource {
  public:
   SuperimposedOffsetImageSource(const gfx::ImageSkia& first,
@@ -168,11 +145,14 @@
       gfx::ImageSkiaOperations::CreateResizedImage(
           avatar_image, skia::ImageOperations::RESIZE_BEST,
           gfx::Size(kAvatarSize, kAvatarSize));
+  // The color used in `circle_mask` is irrelevant as long as it's opaque; only
+  // the alpha channel matters.
+  gfx::ImageSkia circle_mask =
+      gfx::ImageSkiaOperations::CreateImageWithCircleBackground(
+          kAvatarSize / 2, SK_ColorWHITE, gfx::ImageSkia());
   gfx::ImageSkia cropped_avatar_image =
-      gfx::ImageSkiaOperations::CreateMaskedImage(
-          sized_avatar_image,
-          gfx::CanvasImageSource::MakeImageSkia<CircleImageSource>(
-              sized_avatar_image.width(), SK_ColorWHITE));
+      gfx::ImageSkiaOperations::CreateMaskedImage(sized_avatar_image,
+                                                  circle_mask);
   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
   gfx::ImageSkia header_image =
       *bundle.GetImageSkiaNamed(IDR_TAILORED_SECURITY_UNCONSENTED);
diff --git a/chrome/browser/ui/views/side_panel/side_panel_combobox_model.cc b/chrome/browser/ui/views/side_panel/side_panel_combobox_model.cc
index 902eb2da..624d896 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_combobox_model.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_combobox_model.cc
@@ -33,6 +33,27 @@
             [](const auto& a, const auto& b) { return a.id < b.id; });
 }
 
+void SidePanelComboboxModel::AddItems(
+    const std::vector<std::unique_ptr<SidePanelEntry>>& entries) {
+  for (auto const& entry : entries) {
+    entries_.emplace_back(SidePanelComboboxModel::Item(
+        entry->id(), entry->name(), entry->icon()));
+  }
+  std::sort(entries_.begin(), entries_.end(),
+            [](const auto& a, const auto& b) { return a.id < b.id; });
+}
+
+void SidePanelComboboxModel::RemoveItems(
+    const std::vector<std::unique_ptr<SidePanelEntry>>& entries) {
+  for (auto const& current_entry : entries) {
+    SidePanelEntry::Id id = current_entry.get()->id();
+    auto position = std::find_if(entries_.begin(), entries_.end(),
+                                 [id](auto entry) { return entry.id == id; });
+    if (position != entries_.end())
+      entries_.erase(position);
+  }
+}
+
 SidePanelEntry::Id SidePanelComboboxModel::GetIdAt(int index) const {
   return entries_[index].id;
 }
diff --git a/chrome/browser/ui/views/side_panel/side_panel_combobox_model.h b/chrome/browser/ui/views/side_panel/side_panel_combobox_model.h
index 7891a86..ff59cba 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_combobox_model.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_combobox_model.h
@@ -5,7 +5,9 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_COMBOBOX_MODEL_H_
 #define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_COMBOBOX_MODEL_H_
 
+#include <memory>
 #include <string>
+#include <vector>
 
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "ui/base/models/combobox_model.h"
@@ -36,6 +38,8 @@
   ~SidePanelComboboxModel() override;
 
   void AddItem(SidePanelEntry* entry);
+  void AddItems(const std::vector<std::unique_ptr<SidePanelEntry>>& entries);
+  void RemoveItems(const std::vector<std::unique_ptr<SidePanelEntry>>& entries);
   SidePanelEntry::Id GetIdAt(int index) const;
 
   // Returns the index for the given side panel entry id, if the id doesn't
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
index fb94884..3d9837a9 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
@@ -46,6 +46,8 @@
 constexpr int kSidePanelContentViewId = 42;
 constexpr int kSidePanelContentWrapperViewId = 43;
 
+constexpr SidePanelEntry::Id kDefaultEntry = SidePanelEntry::Id::kReadingList;
+
 std::unique_ptr<views::ImageButton> CreateControlButton(
     views::View* host,
     base::RepeatingClosure pressed_callback,
@@ -84,6 +86,7 @@
     : browser_view_(browser_view), global_registry_(global_registry) {
   combobox_model_ = std::make_unique<SidePanelComboboxModel>();
   global_registry->AddObserver(this);
+  browser_view_->browser()->tab_strip_model()->AddObserver(this);
   // TODO(pbos): Consider moving creation of SidePanelEntry into other functions
   // that can easily be unit tested.
   global_registry->Register(std::make_unique<SidePanelEntry>(
@@ -114,18 +117,19 @@
   }
 }
 
-SidePanelCoordinator::~SidePanelCoordinator() = default;
+SidePanelCoordinator::~SidePanelCoordinator() {
+  browser_view_->browser()->tab_strip_model()->RemoveObserver(this);
+}
 
 void SidePanelCoordinator::Show(absl::optional<SidePanelEntry::Id> entry_id) {
-  if (!entry_id.has_value()) {
-    // TODO(corising): Handle choosing between last active entries when there
-    // are multiple registries.
-    entry_id = GetLastActiveEntry();
-  }
+  if (!entry_id.has_value())
+    entry_id = GetLastActiveEntryId().value_or(kDefaultEntry);
 
   SidePanelEntry* entry = GetEntryForId(entry_id.value());
-  if (!entry)
+  if (!entry || (GetContentView() && GetLastActiveEntryId().has_value() &&
+                 GetLastActiveEntryId().value() == entry->id())) {
     return;
+  }
 
   if (GetContentView() == nullptr) {
     InitializeSidePanel();
@@ -148,6 +152,17 @@
   if (!content_view)
     return;
 
+  if (global_registry_->active_entry().has_value()) {
+    last_active_global_entry_id_ =
+        global_registry_->active_entry().value()->id();
+  }
+  // Reset active entry values for all registries since existence of an
+  // active_entry for a tab in any registry will trigger the side panel to be
+  // shown.
+  global_registry_->ResetActiveEntry();
+  if (auto* contextual_registry = GetActiveContextualRegistry())
+    contextual_registry->ResetActiveEntry();
+
   // TODO(pbos): Make this button observe panel-visibility state instead.
   browser_view_->toolbar()->side_panel_button()->SetTooltipText(
       l10n_util::GetStringUTF16(IDS_TOOLTIP_SIDE_PANEL_SHOW));
@@ -171,9 +186,11 @@
 
 SidePanelEntry* SidePanelCoordinator::GetEntryForId(
     SidePanelEntry::Id entry_id) {
-  for (auto const& entry : global_registry_->entries()) {
-    if (entry.get()->id() == entry_id)
-      return entry.get();
+  if (auto* entry = global_registry_->GetEntryForId(entry_id))
+    return entry;
+  if (auto* contextual_registry = GetActiveContextualRegistry()) {
+    if (auto* entry = contextual_registry->GetEntryForId(entry_id))
+      return entry;
   }
   return nullptr;
 }
@@ -212,13 +229,35 @@
   DCHECK(content_wrapper);
   content_wrapper->RemoveAllChildViews();
   content_wrapper->AddChildView(entry->CreateContent());
+  if (auto* contextual_registry = GetActiveContextualRegistry())
+    contextual_registry->ResetActiveEntry();
   entry->OnEntryShown();
 }
 
-SidePanelEntry::Id SidePanelCoordinator::GetLastActiveEntry() const {
-  return global_registry_->last_active_entry().has_value()
-             ? global_registry_->last_active_entry().value()
-             : SidePanelEntry::Id::kReadingList;
+absl::optional<SidePanelEntry::Id> SidePanelCoordinator::GetLastActiveEntryId()
+    const {
+  // If a contextual entry is active, return that. If not, return the last
+  // active global entry. If neither exist, fall back to kReadingList.
+  if (GetActiveContextualRegistry() &&
+      GetActiveContextualRegistry()->active_entry().has_value()) {
+    return GetActiveContextualRegistry()->active_entry().value()->id();
+  }
+
+  if (global_registry_->active_entry().has_value())
+    return global_registry_->active_entry().value()->id();
+
+  if (last_active_global_entry_id_.has_value())
+    return last_active_global_entry_id_.value();
+
+  return absl::nullopt;
+}
+
+SidePanelRegistry* SidePanelCoordinator::GetActiveContextualRegistry() const {
+  if (auto* web_contents =
+          browser_view_->browser()->tab_strip_model()->GetActiveWebContents()) {
+    return SidePanelRegistry::Get(web_contents);
+  }
+  return nullptr;
 }
 
 std::unique_ptr<views::View> SidePanelCoordinator::CreateHeader() {
@@ -269,8 +308,8 @@
 
 std::unique_ptr<views::Combobox> SidePanelCoordinator::CreateCombobox() {
   auto combobox = std::make_unique<views::Combobox>(combobox_model_.get());
-  combobox->SetSelectedIndex(
-      combobox_model_->GetIndexForId(GetLastActiveEntry()));
+  combobox->SetSelectedIndex(combobox_model_->GetIndexForId(
+      GetLastActiveEntryId().value_or(kDefaultEntry)));
   // TODO(corising): Replace this with something appropriate.
   combobox->SetAccessibleName(
       combobox_model_->GetItemAt(combobox->GetSelectedIndex()));
@@ -320,3 +359,49 @@
 void SidePanelCoordinator::OnEntryRegistered(SidePanelEntry* entry) {
   combobox_model_->AddItem(entry);
 }
+
+void SidePanelCoordinator::OnEntryWillDeregister(SidePanelEntry* entry) {
+  // Update the current entry to make sure we don't show an entry that is being
+  // removed.
+  if (GetContentView())
+    Show(GetLastActiveEntryId().value_or(kDefaultEntry));
+}
+
+void SidePanelCoordinator::OnTabStripModelChanged(
+    TabStripModel* tab_strip_model,
+    const TabStripModelChange& change,
+    const TabStripSelectionChange& selection) {
+  if (change.type() != TabStripModelChange::kSelectionOnly ||
+      !selection.active_tab_changed()) {
+    return;
+  }
+  // Handle removing the previous tab's contextual registry if one exists and
+  // update the combobox.
+  if (auto* old_contextual_registry =
+          SidePanelRegistry::Get(selection.old_contents)) {
+    old_contextual_registry->RemoveObserver(this);
+    combobox_model_->RemoveItems(old_contextual_registry->entries());
+  }
+
+  // Add the current tab's contextual registry and update the combobox.
+  auto* new_contextual_registry =
+      SidePanelRegistry::Get(selection.new_contents);
+  if (new_contextual_registry) {
+    new_contextual_registry->AddObserver(this);
+    combobox_model_->AddItems(new_contextual_registry->entries());
+  }
+
+  // If an active entry is available, show it. If not, close the panel.
+  if (GetContentView()) {
+    if ((!new_contextual_registry ||
+         !new_contextual_registry->active_entry().has_value()) &&
+        !global_registry_->active_entry().has_value()) {
+      Close();
+    } else {
+      Show(GetLastActiveEntryId().value_or(kDefaultEntry));
+    }
+  } else if (new_contextual_registry &&
+             new_contextual_registry->active_entry().has_value()) {
+    Show(new_contextual_registry->active_entry().value()->id());
+  }
+}
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
index d4af393..8fe8ff5 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_COORDINATOR_H_
 
 #include "base/memory/raw_ptr.h"
+#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry_observer.h"
@@ -25,7 +26,13 @@
 // This class is also responsible for consolidating multiple SidePanelEntry
 // classes across multiple SidePanelRegistry instances, potentially merging them
 // into a single unified side panel.
-class SidePanelCoordinator final : public SidePanelRegistryObserver {
+// Existence and value of registries' active_entry() determines which entry is
+// visible for a given tab where the order of precedence is contextual
+// registry's active_entry() then global registry's. These values are reset when
+// the side panel is closed and |last_active_global_entry_id_| is used to
+// determine what entry is seen when the panel is reopened.
+class SidePanelCoordinator final : public SidePanelRegistryObserver,
+                                   public TabStripModelObserver {
  public:
   explicit SidePanelCoordinator(BrowserView* browser_view,
                                 SidePanelRegistry* global_registry);
@@ -38,6 +45,8 @@
   void Toggle();
 
  private:
+  friend class SidePanelCoordinatorTest;
+
   views::View* GetContentView();
   SidePanelEntry* GetEntryForId(SidePanelEntry::Id entry_id);
 
@@ -50,7 +59,9 @@
 
   // Returns the last active entry or the reading list entry if no last active
   // entry exists.
-  SidePanelEntry::Id GetLastActiveEntry() const;
+  absl::optional<SidePanelEntry::Id> GetLastActiveEntryId() const;
+
+  SidePanelRegistry* GetActiveContextualRegistry() const;
 
   std::unique_ptr<views::View> CreateHeader();
   std::unique_ptr<views::Combobox> CreateCombobox();
@@ -61,9 +72,17 @@
 
   // SidePanelRegistryObserver:
   void OnEntryRegistered(SidePanelEntry* entry) override;
+  void OnEntryWillDeregister(SidePanelEntry* entry) override;
+
+  // TabStripModelObserver:
+  void OnTabStripModelChanged(
+      TabStripModel* tab_strip_model,
+      const TabStripModelChange& change,
+      const TabStripSelectionChange& selection) override;
 
   const raw_ptr<BrowserView> browser_view_;
   raw_ptr<SidePanelRegistry> global_registry_;
+  absl::optional<SidePanelEntry::Id> last_active_global_entry_id_;
 
   // Used to update SidePanelEntry options in the header_combobox_ based on
   // their availability in the observed side panel registries.
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
index e0ef87ce..3cc04f1 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
@@ -5,11 +5,14 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
 
 #include "base/feature_list.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/test_with_browser_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
 
 class SidePanelCoordinatorTest : public TestWithBrowserView {
  public:
@@ -19,17 +22,75 @@
         {features::kSidePanel, features::kUnifiedSidePanel}, {});
     TestWithBrowserView::SetUp();
 
-    // Create an active web contents.
-    AddTab(browser_view()->browser(), GURL("about:blank"));
+    AddTab(browser_view()->browser(), GURL("http://foo1.com"));
+    AddTab(browser_view()->browser(), GURL("http://foo2.com"));
+
+    // Add a kSideSearch entry to the contextual registry for the first tab.
+    browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+    content::WebContents* active_contents =
+        browser_view()->GetActiveWebContents();
+    auto* registry = SidePanelRegistry::Get(active_contents);
+    registry->Register(std::make_unique<SidePanelEntry>(
+        SidePanelEntry::Id::kSideSearch, u"testing1",
+        ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
+        base::BindRepeating([]() { return std::make_unique<views::View>(); })));
+    contextual_registries_.push_back(registry);
+
+    // Add a kLens entry to the contextual registry for the second tab.
+    browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
+    active_contents = browser_view()->GetActiveWebContents();
+    registry = SidePanelRegistry::Get(active_contents);
+    registry->Register(std::make_unique<SidePanelEntry>(
+        SidePanelEntry::Id::kLens, u"testing1",
+        ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
+        base::BindRepeating([]() { return std::make_unique<views::View>(); })));
+    contextual_registries_.push_back(SidePanelRegistry::Get(active_contents));
+
     coordinator_ = browser_view()->side_panel_coordinator();
+    global_registry_ = coordinator_->global_registry_;
+
+    // Verify the first tab has one entry, kSideSearch.
+    browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+    active_contents = browser_view()->GetActiveWebContents();
+    SidePanelRegistry* contextual_registry =
+        SidePanelRegistry::Get(active_contents);
+    EXPECT_EQ(contextual_registry->entries().size(), 1u);
+    EXPECT_EQ(contextual_registry->entries()[0]->id(),
+              SidePanelEntry::Id::kSideSearch);
+
+    // Verify the second tab has one entry, kLens.
+    browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
+    active_contents = browser_view()->GetActiveWebContents();
+    contextual_registry = SidePanelRegistry::Get(active_contents);
+    EXPECT_EQ(contextual_registry->entries().size(), 1u);
+    EXPECT_EQ(contextual_registry->entries()[0]->id(),
+              SidePanelEntry::Id::kLens);
   }
 
-  absl::optional<SidePanelEntry::Id> GetLastActiveEntry() {
-    return browser_view()->global_side_panel_registry()->last_active_entry();
+  void VerifyEntryExistanceAndValue(absl::optional<SidePanelEntry*> entry,
+                                    SidePanelEntry::Id id) {
+    EXPECT_TRUE(entry.has_value());
+    EXPECT_EQ(entry.value()->id(), id);
+  }
+
+  void VerifyEntryExistanceAndValue(absl::optional<SidePanelEntry::Id> entry,
+                                    SidePanelEntry::Id id) {
+    EXPECT_TRUE(entry.has_value());
+    EXPECT_EQ(entry.value(), id);
+  }
+
+  absl::optional<SidePanelEntry::Id> GetLastActiveEntryId() {
+    return coordinator_->GetLastActiveEntryId();
+  }
+
+  absl::optional<SidePanelEntry::Id> GetLastActiveGlobalEntry() {
+    return coordinator_->last_active_global_entry_id_;
   }
 
  protected:
   raw_ptr<SidePanelCoordinator> coordinator_;
+  raw_ptr<SidePanelRegistry> global_registry_;
+  std::vector<raw_ptr<SidePanelRegistry>> contextual_registries_;
 };
 
 TEST_F(SidePanelCoordinatorTest, ToggleSidePanel) {
@@ -40,17 +101,347 @@
   EXPECT_FALSE(browser_view()->right_aligned_side_panel()->GetVisible());
 }
 
-TEST_F(SidePanelCoordinatorTest, SwitchBetweenSidePanelEntries) {
+TEST_F(SidePanelCoordinatorTest, SidePanelReopensToLastSeenGlobalEntry) {
   coordinator_->Toggle();
   EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
 
   coordinator_->Show(SidePanelEntry::Id::kBookmarks);
-  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
-  EXPECT_TRUE(GetLastActiveEntry().has_value());
-  EXPECT_EQ(GetLastActiveEntry().value(), SidePanelEntry::Id::kBookmarks);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
 
+  coordinator_->Toggle();
+  EXPECT_FALSE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+
+  coordinator_->Toggle();
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+}
+
+TEST_F(SidePanelCoordinatorTest, ShowOpensSidePanel) {
+  coordinator_->Show(SidePanelEntry::Id::kBookmarks);
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+}
+
+TEST_F(SidePanelCoordinatorTest, SwapBetweenTabsWithReadingListOpen) {
+  // Verify side panel opens to kReadingList by default.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Toggle();
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+
+  // Verify switching tabs does not change side panel visibility or entry seen
+  // if it is in the global registry.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+}
+
+TEST_F(SidePanelCoordinatorTest, SwapBetweenTabsWithBookmarksOpen) {
+  // Open side panel and switch to kBookmarks and verify the active entry is
+  // updated.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Toggle();
+  coordinator_->Show(SidePanelEntry::Id::kBookmarks);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+
+  // Verify switching tabs does not change entry seen if it is in the global
+  // registry.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+}
+
+TEST_F(SidePanelCoordinatorTest, ContextualEntryDeregistered) {
+  // Verify the first tab has one entry, kSideSearch.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  EXPECT_EQ(contextual_registries_[0]->entries().size(), 1u);
+  EXPECT_EQ(contextual_registries_[0]->entries()[0]->id(),
+            SidePanelEntry::Id::kSideSearch);
+
+  // Deregister kSideSearch from the first tab.
+  contextual_registries_[0]->Deregister(SidePanelEntry::Id::kSideSearch);
+  EXPECT_EQ(contextual_registries_[0]->entries().size(), 0u);
+}
+
+TEST_F(SidePanelCoordinatorTest, ContextualEntryDeregisteredWhileVisible) {
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Deregister kSideSearch from the first tab.
+  contextual_registries_[0]->Deregister(SidePanelEntry::Id::kSideSearch);
+  EXPECT_EQ(contextual_registries_[0]->entries().size(), 0u);
+
+  // Verify the panel defaults back to the last visible global entry or the
+  // reading list.
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+}
+
+TEST_F(SidePanelCoordinatorTest, ShowContextualEntry) {
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+}
+
+TEST_F(SidePanelCoordinatorTest,
+       SwapBetweenTabsAfterNavaigatingToContextualEntry) {
+  // Open side panel and verify it opens to kReadingList by default.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Toggle();
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to a different global entry and verify the active entry is updated.
+  coordinator_->Show(SidePanelEntry::Id::kBookmarks);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kBookmarks);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to a contextual entry and verify the active entry is updated.
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to a tab where this contextual entry is not available and verify we
+  // fall back to the last seen global entry.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch back to the tab where the contextual entry was visible and verify it
+  // is shown.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+}
+
+TEST_F(SidePanelCoordinatorTest, TogglePanelWithContextualEntryShowing) {
+  // Open side panel and verify it opens to kReadingList by default.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Toggle();
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to a different global entry and verify the active entry is updated.
+  coordinator_->Show(SidePanelEntry::Id::kBookmarks);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kBookmarks);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to a contextual entry and verify the active entry is updated.
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Close the side panel and verify the contextual registry's last active entry
+  // is reset.
+  coordinator_->Toggle();
+  EXPECT_FALSE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(GetLastActiveGlobalEntry(),
+                               SidePanelEntry::Id::kBookmarks);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Reopen the side panel and verify it reopens to the last active global
+  // entry.
+  coordinator_->Toggle();
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kBookmarks);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kBookmarks);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+}
+
+TEST_F(SidePanelCoordinatorTest,
+       SwitchBetweenTabWithContextualEntryAndTabWithNoEntry) {
+  // Open side panel to contextual entry and verify.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to another tab and verify the side panel is closed.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
+  EXPECT_FALSE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_FALSE(GetLastActiveEntryId().has_value());
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch back to the tab with the contextual entry open and verify the side
+  // panel is then open.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+}
+
+TEST_F(
+    SidePanelCoordinatorTest,
+    SwitchBetweenTabWithContextualEntryAndTabWithNoEntryWhenThereIsALastActiveGlobalEntry) {
+  coordinator_->Toggle();
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  coordinator_->Toggle();
+  EXPECT_FALSE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  VerifyEntryExistanceAndValue(GetLastActiveGlobalEntry(),
+                               SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Open side panel to contextual entry and verify.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to another tab and verify the side panel is closed.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
+  EXPECT_FALSE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch back to the tab with the contextual entry open and verify the side
+  // panel is then open.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+}
+
+TEST_F(SidePanelCoordinatorTest,
+       SwitchBackToTabWithPreviouslyVisibleContextualEntry) {
+  // Open side panel to contextual entry and verify.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  coordinator_->Show(SidePanelEntry::Id::kSideSearch);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(global_registry_->active_entry().has_value());
+  VerifyEntryExistanceAndValue(contextual_registries_[0]->active_entry(),
+                               SidePanelEntry::Id::kSideSearch);
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to a global entry and verify the contextual entry is no longer
+  // active.
   coordinator_->Show(SidePanelEntry::Id::kReadingList);
   EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
-  EXPECT_TRUE(GetLastActiveEntry().has_value());
-  EXPECT_EQ(GetLastActiveEntry().value(), SidePanelEntry::Id::kReadingList);
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch to a different tab and verify state.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
+
+  // Switch back to the original tab and verify the contextual entry is not
+  // active or showing.
+  browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), SidePanelEntry::Id::kReadingList);
+  VerifyEntryExistanceAndValue(global_registry_->active_entry(),
+                               SidePanelEntry::Id::kReadingList);
+  EXPECT_FALSE(contextual_registries_[0]->active_entry().has_value());
+  EXPECT_FALSE(contextual_registries_[1]->active_entry().has_value());
 }
diff --git a/chrome/browser/ui/views/side_panel/side_panel_entry.cc b/chrome/browser/ui/views/side_panel/side_panel_entry.cc
index 4c55fe9..0ae2e91 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_entry.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_entry.cc
@@ -28,7 +28,7 @@
 
 void SidePanelEntry::OnEntryShown() {
   for (SidePanelEntryObserver& observer : observers_)
-    observer.OnEntryShown(id_);
+    observer.OnEntryShown(this);
 }
 
 void SidePanelEntry::AddObserver(SidePanelEntryObserver* observer) {
diff --git a/chrome/browser/ui/views/side_panel/side_panel_entry.h b/chrome/browser/ui/views/side_panel/side_panel_entry.h
index 2ea279a0..d61aef4 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_entry.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_entry.h
@@ -20,7 +20,7 @@
 class SidePanelEntry final {
  public:
   // Note this order matches that of the combobox options in the side panel.
-  enum class Id { kReadingList, kBookmarks, kReaderMode };
+  enum class Id { kReadingList, kBookmarks, kReaderMode, kSideSearch, kLens };
 
   // TODO(pbos): Add an icon ImageModel here.
   SidePanelEntry(Id id,
diff --git a/chrome/browser/ui/views/side_panel/side_panel_entry_observer.h b/chrome/browser/ui/views/side_panel/side_panel_entry_observer.h
index 0a187e2..8a5c0c87c 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_entry_observer.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_entry_observer.h
@@ -11,7 +11,7 @@
 class SidePanelEntryObserver : public base::CheckedObserver {
  public:
   // Called when a SidePanelEntry is shown.
-  virtual void OnEntryShown(SidePanelEntry::Id id) {}
+  virtual void OnEntryShown(SidePanelEntry* entry) {}
 
  protected:
   ~SidePanelEntryObserver() override = default;
diff --git a/chrome/browser/ui/views/side_panel/side_panel_registry.cc b/chrome/browser/ui/views/side_panel/side_panel_registry.cc
index 2f67b00..ab95f075 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_registry.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_registry.cc
@@ -4,13 +4,45 @@
 
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
 
+#include "base/containers/cxx20_erase.h"
+#include "base/containers/unique_ptr_adapters.h"
 #include "base/observer_list.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry_observer.h"
+#include "content/public/browser/web_contents.h"
+
+const char kSidePanelRegistryKey[] = "side_panel_registry_key";
 
 SidePanelRegistry::SidePanelRegistry() = default;
 
 SidePanelRegistry::~SidePanelRegistry() = default;
 
+// static
+SidePanelRegistry* SidePanelRegistry::Get(content::WebContents* web_contents) {
+  if (!web_contents)
+    return nullptr;
+  SidePanelRegistry* registry = static_cast<SidePanelRegistry*>(
+      web_contents->GetUserData(kSidePanelRegistryKey));
+  if (!registry) {
+    auto new_registry = std::make_unique<SidePanelRegistry>();
+    registry = new_registry.get();
+    web_contents->SetUserData(kSidePanelRegistryKey, std::move(new_registry));
+  }
+  return registry;
+}
+
+SidePanelEntry* SidePanelRegistry::GetEntryForId(SidePanelEntry::Id entry_id) {
+  auto it =
+      std::find_if(entries_.begin(), entries_.end(),
+                   [entry_id](const std::unique_ptr<SidePanelEntry>& entry) {
+                     return entry.get()->id() == entry_id;
+                   });
+  return it == entries_.end() ? nullptr : it->get();
+}
+
+void SidePanelRegistry::ResetActiveEntry() {
+  active_entry_.reset();
+}
+
 void SidePanelRegistry::AddObserver(SidePanelRegistryObserver* observer) {
   observers_.AddObserver(observer);
 }
@@ -26,6 +58,27 @@
   entries_.push_back(std::move(entry));
 }
 
-void SidePanelRegistry::OnEntryShown(SidePanelEntry::Id id) {
-  last_active_entry_ = id;
+void SidePanelRegistry::Deregister(SidePanelEntry::Id id) {
+  for (auto const& entry : entries_) {
+    if (entry.get()->id() == id) {
+      entry.get()->RemoveObserver(this);
+      if (active_entry_.has_value() &&
+          entry.get()->id() == active_entry_.value()->id()) {
+        active_entry_.reset();
+      }
+      for (SidePanelRegistryObserver& observer : observers_) {
+        observer.OnEntryWillDeregister(entry.get());
+      }
+      RemoveEntry(entry.get());
+      return;
+    }
+  }
+}
+
+void SidePanelRegistry::OnEntryShown(SidePanelEntry* entry) {
+  active_entry_ = entry;
+}
+
+void SidePanelRegistry::RemoveEntry(SidePanelEntry* entry) {
+  base::EraseIf(entries_, base::MatchesUniquePtr(entry));
 }
diff --git a/chrome/browser/ui/views/side_panel/side_panel_registry.h b/chrome/browser/ui/views/side_panel/side_panel_registry.h
index c4da24a..9cec2b1 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_registry.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_registry.h
@@ -9,35 +9,53 @@
 #include <vector>
 
 #include "base/observer_list.h"
+#include "base/supports_user_data.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry_observer.h"
 
+namespace content {
+class WebContents;
+}  // namespace content
+
 class SidePanelRegistryObserver;
 
 // This class is used for storing SidePanelEntries specific to a context. This
 // context can be one per tab or one per window. See also SidePanelCoordinator.
-class SidePanelRegistry final : public SidePanelEntryObserver {
+class SidePanelRegistry final : public base::SupportsUserData::Data,
+                                public SidePanelEntryObserver {
  public:
   SidePanelRegistry();
   SidePanelRegistry(const SidePanelRegistry&) = delete;
   SidePanelRegistry& operator=(const SidePanelRegistry&) = delete;
   ~SidePanelRegistry() override;
 
+  // Gets the contextual registry for the tab associated with |web_contents|.
+  // Can return null for non-tab contents.
+  static SidePanelRegistry* Get(content::WebContents* web_contents);
+
+  SidePanelEntry* GetEntryForId(SidePanelEntry::Id entry_id);
+  void ResetActiveEntry();
+
   void AddObserver(SidePanelRegistryObserver* observer);
   void RemoveObserver(SidePanelRegistryObserver* observer);
 
   void Register(std::unique_ptr<SidePanelEntry> entry);
+  void Deregister(SidePanelEntry::Id id);
 
-  absl::optional<SidePanelEntry::Id> last_active_entry() {
-    return last_active_entry_;
-  }
+  absl::optional<SidePanelEntry*> active_entry() { return active_entry_; }
   std::vector<std::unique_ptr<SidePanelEntry>>& entries() { return entries_; }
 
   // SidePanelEntryObserver:
-  void OnEntryShown(SidePanelEntry::Id id) override;
+  void OnEntryShown(SidePanelEntry* id) override;
 
  private:
-  absl::optional<SidePanelEntry::Id> last_active_entry_;
+  void RemoveEntry(SidePanelEntry* entry);
+
+  // The last active entry hosted in the side panel used to determine what entry
+  // should be visible. This is reset by the coordinator when the panel is
+  // closed. When there are multiple registries, this may not be the entry
+  // currently visible in the side panel.
+  absl::optional<SidePanelEntry*> active_entry_;
 
   std::vector<std::unique_ptr<SidePanelEntry>> entries_;
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel_registry_observer.h b/chrome/browser/ui/views/side_panel/side_panel_registry_observer.h
index 135cc56..341c148d 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_registry_observer.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_registry_observer.h
@@ -14,6 +14,10 @@
   // Called when a SidePanelEntry is added to the registry.
   virtual void OnEntryRegistered(SidePanelEntry* entry) {}
 
+  // Called immediately before a SidePanelEntry is being removed from the
+  // registry.
+  virtual void OnEntryWillDeregister(SidePanelEntry* entry) {}
+
  protected:
   ~SidePanelRegistryObserver() override = default;
 };
diff --git a/chrome/browser/ui/views/supervised_user/extension_install_blocked_by_parent_dialog_view.cc b/chrome/browser/ui/views/supervised_user/extension_install_blocked_by_parent_dialog_view.cc
index 52e3c70b..7752d0c 100644
--- a/chrome/browser/ui/views/supervised_user/extension_install_blocked_by_parent_dialog_view.cc
+++ b/chrome/browser/ui/views/supervised_user/extension_install_blocked_by_parent_dialog_view.cc
@@ -70,8 +70,6 @@
   set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
       views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
 
-  SetIcon(gfx::CreateVectorIcon(chromeos::kNotificationSupervisedUserIcon,
-                                SK_ColorDKGRAY));
   SetShowIcon(true);
   ConfigureTitle();
   CreateContents();
@@ -83,6 +81,12 @@
     std::move(done_callback_).Run();
 }
 
+void ExtensionInstallBlockedByParentDialogView::OnThemeChanged() {
+  views::DialogDelegateView::OnThemeChanged();
+  SetIcon(gfx::CreateVectorIcon(chromeos::kNotificationSupervisedUserIcon,
+                                GetColorProvider()->GetColor(ui::kColorIcon)));
+}
+
 void ExtensionInstallBlockedByParentDialogView::ConfigureTitle() {
   std::u16string title_string;
   switch (action_) {
@@ -121,9 +125,6 @@
       break;
   }
 
-  icon_ = gfx::CreateVectorIcon(chromeos::kNotificationSupervisedUserIcon,
-                                SK_ColorDKGRAY);
-
   const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
   const gfx::Insets content_insets = provider->GetDialogInsetsForContentType(
       views::DialogContentType::kText, views::DialogContentType::kText);
diff --git a/chrome/browser/ui/views/supervised_user/extension_install_blocked_by_parent_dialog_view.h b/chrome/browser/ui/views/supervised_user/extension_install_blocked_by_parent_dialog_view.h
index 0a20053..6b7ec98 100644
--- a/chrome/browser/ui/views/supervised_user/extension_install_blocked_by_parent_dialog_view.h
+++ b/chrome/browser/ui/views/supervised_user/extension_install_blocked_by_parent_dialog_view.h
@@ -43,6 +43,8 @@
       const ExtensionInstallBlockedByParentDialogView&) = delete;
   ~ExtensionInstallBlockedByParentDialogView() override;
 
+  void OnThemeChanged() override;
+
  private:
   void ConfigureTitle();
   void CreateContents();
@@ -50,7 +52,6 @@
 
   const extensions::Extension* extension_ = nullptr;
   chrome::ExtensionInstalledBlockedByParentDialogAction action_;
-  gfx::ImageSkia icon_;
   base::OnceClosure done_callback_;
 };
 
diff --git a/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.cc b/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.cc
index 22ed6a1..4f17b0a 100644
--- a/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.cc
+++ b/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.cc
@@ -289,6 +289,8 @@
 
   SetModalType(ui::MODAL_TYPE_WINDOW);
   SetShowCloseButton(true);
+  SetCloseCallback(base::BindOnce(&ParentPermissionDialogView::OnDialogClose,
+                                  base::Unretained(this)));
   set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
       views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
 
@@ -384,12 +386,24 @@
   GetBubbleFrameView()->SetTitleView(std::move(message_container).Build());
 }
 
+void ParentPermissionDialogView::OnDialogClose() {
+  // If the dialog is closed without the user clicking "approve" consider this
+  // as ParentPermissionCanceled to avoid showing an error message. If the
+  // user clicked "accept", then that async process will send the result, or if
+  // that doesn't complete, eventually the destructor will send a failure
+  // result.
+  if (!is_approve_clicked_) {
+    SendResultOnce(ParentPermissionDialog::Result::kParentPermissionCanceled);
+  }
+}
+
 bool ParentPermissionDialogView::Cancel() {
-  SendResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
+  SendResultOnce(ParentPermissionDialog::Result::kParentPermissionCanceled);
   return true;
 }
 
 bool ParentPermissionDialogView::Accept() {
+  is_approve_clicked_ = true;
   // Disable the dialog temporarily while we validate the parent's credentials,
   // which can take some time because it involves a series of async network
   // requests.
@@ -601,7 +615,7 @@
     supervised_user_metrics_recorder_.RecordParentPermissionDialogUmaMetrics(
         SupervisedUserExtensionsMetricsRecorder::ParentPermissionDialogState::
             kNoParentError);
-    SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
+    SendResultOnce(ParentPermissionDialog::Result::kParentPermissionFailed);
   }
 }
 
@@ -692,7 +706,7 @@
     signin::AccessTokenInfo access_token_info) {
   oauth2_access_token_fetcher_.reset();
   if (error.state() != GoogleServiceAuthError::NONE) {
-    SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
+    SendResultOnce(ParentPermissionDialog::Result::kParentPermissionFailed);
     CloseWithReason(views::Widget::ClosedReason::kUnspecified);
     return;
   }
@@ -714,7 +728,7 @@
       child_access_token, parent_obfuscated_gaia_id, credential);
 }
 
-void ParentPermissionDialogView::SendResult(
+void ParentPermissionDialogView::SendResultOnce(
     ParentPermissionDialog::Result result) {
   if (!params_->done_callback)
     return;
@@ -741,7 +755,7 @@
 
 void ParentPermissionDialogView::OnReAuthProofTokenSuccess(
     const std::string& reauth_proof_token) {
-  SendResult(ParentPermissionDialog::Result::kParentPermissionReceived);
+  SendResultOnce(ParentPermissionDialog::Result::kParentPermissionReceived);
   CloseWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
 }
 
@@ -764,7 +778,7 @@
       return;
     }
   }
-  SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
+  SendResultOnce(ParentPermissionDialog::Result::kParentPermissionFailed);
   CloseWithReason(views::Widget::ClosedReason::kUnspecified);
 }
 
diff --git a/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.h b/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.h
index 7d0a9350..4d71aaf 100644
--- a/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.h
+++ b/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view.h
@@ -108,6 +108,7 @@
   void OnExtensionIconLoaded(const gfx::Image& image);
   void LoadExtensionIcon();
   void CloseWithReason(views::Widget::ClosedReason reason);
+  void OnDialogClose();
 
   // Given an email address of the child's parent, return the parents'
   // obfuscated gaia id.
@@ -136,7 +137,9 @@
   void OnReAuthProofTokenFailure(
       const GaiaAuthConsumer::ReAuthProofTokenStatus error) override;
 
-  void SendResult(ParentPermissionDialog::Result result);
+  // The first time it is called, logs the result to UMA and passes it to the
+  // callback. No effect if called subsequent times.
+  void SendResultOnce(ParentPermissionDialog::Result result);
 
   // Sets the |extension| to be optionally displayed in the dialog.  This
   // causes the view to show several extension properties including the
@@ -173,6 +176,10 @@
   // Used to ensure we don't try to show same dialog twice.
   bool is_showing_ = false;
 
+  // Used to set close reason if the dialog is closed without clicking
+  // "approve."
+  bool is_approve_clicked_ = false;
+
   // Used to fetch the Reauth token.
   std::unique_ptr<GaiaAuthFetcher> reauth_token_fetcher_;
 
diff --git a/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view_browsertest.cc b/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view_browsertest.cc
index 8adfdb69..ba3e157 100644
--- a/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/supervised_user/parent_permission_dialog_view_browsertest.cc
@@ -56,6 +56,7 @@
   enum class NextDialogAction {
     kCancel,
     kAccept,
+    kClose,
   };
 
   ParentPermissionDialogViewTest()
@@ -123,6 +124,9 @@
         case NextDialogAction::kAccept:
           view_->AcceptDialog();
           break;
+        case NextDialogAction::kClose:
+          view_->CloseDialog();
+          break;
       }
     }
   }
@@ -313,6 +317,13 @@
   CheckResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
 }
 
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogViewTest, PermissionDialogClosed) {
+  set_next_dialog_action(
+      ParentPermissionDialogViewTest::NextDialogAction::kClose);
+  ShowPrompt();
+  CheckResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
+}
+
 IN_PROC_BROWSER_TEST_F(ParentPermissionDialogViewTest,
                        PermissionReceivedForExtension) {
   base::HistogramTester histogram_tester;
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index aed490a8..6c75eb6 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -100,6 +100,7 @@
 #include "ui/views/cascading_property.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/scroll_view.h"
+#include "ui/views/interaction/element_tracker_views.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/masked_targeter_delegate.h"
 #include "ui/views/mouse_watcher_view_host.h"
@@ -2063,6 +2064,8 @@
         // isn't the tab we just added.
         AnnounceTabAddedToGroup(target_group.value());
         controller_->AddTabToGroup(start_index, target_group.value());
+        views::ElementTrackerViews::GetInstance()->NotifyCustomEvent(
+            kTabGroupedCustomEventId, tab);
         return;
       }
     }
diff --git a/chrome/browser/ui/views/user_education/browser_feature_promo_controller.cc b/chrome/browser/ui/views/user_education/browser_feature_promo_controller.cc
index 55050b6..0b37f0e 100644
--- a/chrome/browser/ui/views/user_education/browser_feature_promo_controller.cc
+++ b/chrome/browser/ui/views/user_education/browser_feature_promo_controller.cc
@@ -11,8 +11,10 @@
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/interaction/element_tracker.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/views/accessible_pane_view.h"
 #include "ui/views/interaction/element_tracker_views.h"
 #include "ui/views/view.h"
+#include "ui/views/view_utils.h"
 
 BrowserFeaturePromoController::BrowserFeaturePromoController(
     BrowserView* browser_view,
@@ -88,7 +90,7 @@
 std::u16string
 BrowserFeaturePromoController::GetFocusHelpBubbleScreenReaderHint(
     FeaturePromoSpecification::PromoType promo_type,
-    const ui::TrackedElement* anchor_element,
+    ui::TrackedElement* anchor_element,
     bool is_critical_promo) const {
   // No message is required as this is a background bubble with a
   // screen reader-specific prompt and will dismiss itself.
@@ -105,7 +107,9 @@
 
   // Present the user with the full help bubble navigation shortcut.
   auto* const anchor_view = anchor_element->AsA<views::TrackedElementViews>();
-  if (anchor_view && anchor_view->view()->IsAccessibilityFocusable()) {
+  if (anchor_view &&
+      (anchor_view->view()->IsAccessibilityFocusable() ||
+       views::IsViewClass<views::AccessiblePaneView>(anchor_view->view()))) {
     return l10n_util::GetStringFUTF16(IDS_FOCUS_HELP_BUBBLE_TOGGLE_DESCRIPTION,
                                       accelerator_text);
   }
diff --git a/chrome/browser/ui/views/user_education/browser_feature_promo_controller.h b/chrome/browser/ui/views/user_education/browser_feature_promo_controller.h
index f63a3c2..94b87e1 100644
--- a/chrome/browser/ui/views/user_education/browser_feature_promo_controller.h
+++ b/chrome/browser/ui/views/user_education/browser_feature_promo_controller.h
@@ -89,7 +89,7 @@
   // accelerator to focus the help bubble.
   std::u16string GetFocusHelpBubbleScreenReaderHint(
       FeaturePromoSpecification::PromoType promo_type,
-      const ui::TrackedElement* anchor_element,
+      ui::TrackedElement* anchor_element,
       bool is_critical_promo) const override;
 
  private:
diff --git a/chrome/browser/ui/views/user_education/help_bubble_factory_views.cc b/chrome/browser/ui/views/user_education/help_bubble_factory_views.cc
index 24fe9fb..a29dbd6 100644
--- a/chrome/browser/ui/views/user_education/help_bubble_factory_views.cc
+++ b/chrome/browser/ui/views/user_education/help_bubble_factory_views.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -16,7 +17,28 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/interaction/element_tracker.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/interaction/element_tracker_views.h"
+#include "ui/views/view_utils.h"
+
+namespace {
+
+// Returns whether losing focus would cause a widget to be destroyed.
+// This prevents us from accidentally closing a widget a bubble is anchored to
+// at the cost of not being able to directly access the help bubble.
+bool BlurWouldCloseWidget(const views::Widget* widget) {
+  // Right now, we can only ask the question if we know the bubble is
+  // controlled by a BubbleDialogDelegateView, since runtime type information
+  // isn't present for any of the other objects involved.
+  auto* const contents = widget->widget_delegate()->GetContentsView();
+  return contents &&
+         views::IsViewClass<views::BubbleDialogDelegateView>(contents) &&
+         static_cast<const views::BubbleDialogDelegateView*>(contents)
+             ->close_on_deactivate();
+}
+
+}  // namespace
 
 DEFINE_FRAMEWORK_SPECIFIC_METADATA(HelpBubbleViews)
 DEFINE_FRAMEWORK_SPECIFIC_METADATA(HelpBubbleFactoryViews)
@@ -51,16 +73,34 @@
           ->GetFocusedView();
 
   // If the focus isn't in the help bubble, focus the help bubble.
-  if (is_focus_in_ancestor_widget) {
+  // Note that if is_focus_in_ancestor_widget is true, then anchor both exists
+  // and has a widget, so anchor->GetWidget() will always be valid.
+  if (is_focus_in_ancestor_widget &&
+      !BlurWouldCloseWidget(anchor->GetWidget())) {
     help_bubble_view_->GetWidget()->Activate();
     help_bubble_view_->RequestFocus();
     return true;
   }
 
-  // If the anchor isn't accessibility-focusable, we can't toggle focus.
-  if (!anchor || !anchor->IsAccessibilityFocusable())
+  if (!anchor)
     return false;
 
+  // An AccessiblePaneView can receive focus, but is not necessarily itself
+  // accessibility focusable. Use the built-in functionality for focusing
+  // elements of AccessiblePaneView instead.
+  if (!anchor->IsAccessibilityFocusable()) {
+    if (views::IsViewClass<views::AccessiblePaneView>(anchor)) {
+      // You can't focus an accessible pane if it's already in accessibility
+      // mode, so avoid doing that; the SetPaneFocus() call will go back into
+      // accessibility navigation mode.
+      anchor->GetFocusManager()->SetKeyboardAccessible(false);
+      return static_cast<views::AccessiblePaneView*>(anchor)->SetPaneFocus(
+          nullptr);
+    } else {
+      return false;
+    }
+  }
+
   // Focus the anchor. We can't request focus for an accessibility-only view
   // until we turn on keyboard accessibility for its focus manager.
   anchor->GetFocusManager()->SetKeyboardAccessible(true);
diff --git a/chrome/browser/ui/views/user_education/help_bubble_view.cc b/chrome/browser/ui/views/user_education/help_bubble_view.cc
index 973b200..dfedc65 100644
--- a/chrome/browser/ui/views/user_education/help_bubble_view.cc
+++ b/chrome/browser/ui/views/user_education/help_bubble_view.cc
@@ -203,6 +203,8 @@
   void OnThemeChanged() override {
     views::ImageButton::OnThemeChanged();
 
+    constexpr float kCloseButtonFocusRingHaloThickness = 1.25f;
+
     const auto* theme_provider = GetThemeProvider();
     SetImage(views::ImageButton::STATE_NORMAL,
              gfx::CreateVectorIcon(
@@ -211,6 +213,10 @@
                      ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_FOREGROUND)));
     views::InkDrop::Get(this)->SetBaseColor(theme_provider->GetColor(
         ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_CLOSE_BUTTON_INK_DROP));
+    views::FocusRing::Get(this)->SetColor(theme_provider->GetColor(
+        ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_FOREGROUND));
+    views::FocusRing::Get(this)->SetHaloThickness(
+        kCloseButtonFocusRingHaloThickness);
   }
 };
 
@@ -546,14 +552,17 @@
       views::FlexSpecification(button_layout.GetDefaultFlexRule()));
 
   // Want a consistent initial focused view if one is available.
-  if (close_button)
-    SetInitiallyFocusedView(close_button);
-  else if (!button_container->children().empty())
+  if (!button_container->children().empty()) {
     SetInitiallyFocusedView(button_container->children()[0]);
+  } else if (close_button) {
+    SetInitiallyFocusedView(close_button);
+  }
 
   set_margins(gfx::Insets());
   set_title_margins(gfx::Insets());
   SetButtons(ui::DIALOG_BUTTON_NONE);
+  set_close_on_deactivate(false);
+  set_focus_traversable_from_anchor_view(false);
 
   views::Widget* widget = views::BubbleDialogDelegateView::CreateBubble(this);
 
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 1805574..bb2804b 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -1556,13 +1556,13 @@
       GURL(ash::multidevice::kChromeUIProximityAuthURL),
       GURL(chrome::kOsUIRestartURL), GURL(chrome::kChromeUIScanningAppURL),
       GURL(chrome::kOsUIScanningAppURL), GURL(chrome::kChromeUISetTimeURL),
-      GURL(chrome::kChromeUIOSSettingsURL), GURL(chrome::kChromeUISettingsURL),
+      GURL(chrome::kChromeUIOSSettingsURL), GURL(chrome::kOsUISettingsURL),
       GURL(chrome::kOsUISettingsURL), GURL(chrome::kOsUISignInInternalsURL),
       GURL(chrome::kChromeUISlowURL), GURL(chrome::kChromeUISmbShareURL),
-      GURL(chrome::kOsUISyncInternalsURL),
-      GURL(chrome::kChromeUISysInternalsUrl), GURL(chrome::kOsUITermsURL),
-      GURL(chrome::kChromeUIUserImageURL), GURL(chrome::kOsUIVersionURL),
-      GURL(chrome::kChromeUIVmUrl), GURL(chrome::kOsUISystemURL),
+      GURL(chrome::kOsUISyncInternalsURL), GURL(chrome::kOsUISysInternalsUrl),
+      GURL(chrome::kOsUITermsURL), GURL(chrome::kChromeUIUserImageURL),
+      GURL(chrome::kOsUIVersionURL), GURL(chrome::kChromeUIVmUrl),
+      GURL(chrome::kOsUISystemURL),
       // The CL to land this didn't land yet. Once landed they need to be moved
       // to Lacros. However  - as the refactor might precede this, there is no
       // TODO for it.
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index 840e967..220c465 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -255,10 +255,6 @@
   AddCallback("launchIncognito", &SigninScreenHandler::HandleLaunchIncognito);
   AddCallback("offlineLogin", &SigninScreenHandler::HandleOfflineLogin);
 
-  // TODO(crbug.com/1168114): This is also called by GAIA screen,
-  // but might not be needed anymore
-  AddCallback("loginUIStateChanged",
-              &SigninScreenHandler::HandleLoginUIStateChanged);
   AddCallback("showLoadingTimeoutError",
               &SigninScreenHandler::HandleShowLoadingTimeoutError);
 }
@@ -510,28 +506,6 @@
   LoginDisplayHost::default_host()->StartWizard(OfflineLoginView::kScreenId);
 }
 
-void SigninScreenHandler::HandleToggleKioskAutolaunchScreen() {
-  if (delegate_ && !webui::IsEnterpriseManaged())
-    delegate_->ShowKioskAutolaunchScreen();
-}
-
-void SigninScreenHandler::HandleLoginUIStateChanged(const std::string& source,
-                                                    bool active) {
-  VLOG(0) << "Login WebUI >> active: " << active << ", "
-            << "source: " << source;
-
-  if (!KioskAppManager::Get()->GetAutoLaunchApp().empty() &&
-      KioskAppManager::Get()->IsAutoLaunchRequested()) {
-    VLOG(0) << "Showing auto-launch warning";
-    // On slow devices, the wallpaper animation is not shown initially, so we
-    // must explicitly load the wallpaper. This is also the case for the
-    // account-picker and gaia-signin UI states.
-    LoginDisplayHost::default_host()->LoadSigninWallpaper();
-    HandleToggleKioskAutolaunchScreen();
-    return;
-  }
-}
-
 void SigninScreenHandler::HandleShowLoadingTimeoutError() {
   UpdateState(NetworkError::ERROR_REASON_LOADING_TIMEOUT);
 }
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
index c148dae..dad44a3f 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
@@ -55,9 +55,6 @@
   // Returns true if sign in is in progress.
   virtual bool IsSigninInProgress() const = 0;
 
-  // Shows Reset screen.
-  virtual void ShowKioskAutolaunchScreen() = 0;
-
   // --------------- Rest of the methods.
 
   // Whether user sign in has completed.
@@ -134,11 +131,7 @@
   // WebUI message handlers.
   void HandleLaunchIncognito();
   void HandleOfflineLogin();
-  void HandleToggleEnrollmentScreen();
-  void HandleToggleResetScreen();
-  void HandleToggleKioskAutolaunchScreen();
 
-  void HandleLoginUIStateChanged(const std::string& source, bool active);
   void HandleShowLoadingTimeoutError();
 
   // Returns true if current visible screen is the Gaia sign-in page.
diff --git a/chrome/browser/ui/webui/feedback/feedback_ui.cc b/chrome/browser/ui/webui/feedback/feedback_ui.cc
index 9391c8a4..5e30c5a 100644
--- a/chrome/browser/ui/webui/feedback/feedback_ui.cc
+++ b/chrome/browser/ui/webui/feedback/feedback_ui.cc
@@ -7,6 +7,7 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/browser_resources.h"
 #include "chrome/grit/feedback_resources.h"
 #include "chrome/grit/feedback_resources_map.h"
 #include "chrome/grit/generated_resources.h"
@@ -71,7 +72,13 @@
       content::WebUIDataSource::Create(chrome::kChromeUIFeedbackHost);
   source->AddResourcePaths(
       base::make_span(kFeedbackResources, kFeedbackResourcesSize));
+
   source->AddResourcePath("", IDR_FEEDBACK_DEFAULT_HTML);
+
+  // Register the CSS file from chrome://system manually as that style is
+  // re-used by chrome://feedback/html/sys_info.html.
+  source->AddResourcePath("css/about_sys.css", IDR_ABOUT_SYS_CSS);
+
   source->UseStringsJs();
 
   AddStringResources(source, profile);
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index c99f8ed..eba14d7 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -279,8 +279,6 @@
     {"printToPDF", IDS_PRINT_PREVIEW_PRINT_TO_PDF},
     {"printing", IDS_PRINT_PREVIEW_PRINTING},
     {"recentDestinationsTitle", IDS_PRINT_PREVIEW_RECENT_DESTINATIONS_TITLE},
-    {"registerPrinterInformationMessage",
-     IDS_CLOUD_PRINT_REGISTER_PRINTER_INFORMATION},
     {"resolveExtensionUSBDialogTitle",
      IDS_PRINT_PREVIEW_RESOLVE_EXTENSION_USB_DIALOG_TITLE},
     {"resolveExtensionUSBErrorMessage",
diff --git a/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.cc b/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.cc
new file mode 100644
index 0000000..c1472ef5
--- /dev/null
+++ b/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.cc
@@ -0,0 +1,228 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.h"
+
+#include "base/json/json_string_value_serializer.h"
+#include "base/strings/stringprintf.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/base/load_flags.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace ash {
+
+namespace {
+
+const char kAuthorizationHeaderFormat[] = "Bearer %s";
+const char kSecureConnectApiGetSecondaryGoogleAccountUsageUrl[] =
+    "https://secureconnect-pa.clients6.google.com/"
+    "v1:getManagedAccountsSigninRestriction";
+const char kJsonContentType[] = "application/json";
+const char kChromeOSPolicyHeader[] = "x-chromeos-policy";
+const char kSecondaryGoogleAccountUsage[] = "SecondaryGoogleAccountUsage";
+// Presence of this key in the user info response indicates whether the user is
+// on a hosted domain.
+const char kHostedDomainKey[] = "hd";
+constexpr net::NetworkTrafficAnnotationTag annotation =
+    net::DefineNetworkTrafficAnnotation(
+        "managed_acccount_signin_restrictions_secure_connect",
+        R"(
+    semantics {
+      sender: "Chrome OS sign-in restrictions"
+      description:
+        "A request to the SecureConnect API to retrieve the value of the "
+        "SecondaryGoogleAccountUsage policy for the signed in user."
+      trigger:
+        "After a user signs into a managed account as a secondary account in "
+        "Chrome OS."
+      data:
+        "Gaia access token."
+      destination: GOOGLE_OWNED_SERVICE
+    }
+    policy {
+      cookies_allowed: NO
+      chrome_policy {
+        SigninInterceptionEnabled {
+          SigninInterceptionEnabled: false
+        }
+      }
+    })");
+
+std::unique_ptr<network::SimpleURLLoader> CreateUrlLoader(
+    const GURL& url,
+    const std::string& access_token,
+    const net::NetworkTrafficAnnotationTag& annotation) {
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+
+  resource_request->url = url;
+  resource_request->method = net::HttpRequestHeaders::kGetMethod;
+  resource_request->load_flags = net::LOAD_DISABLE_CACHE;
+  resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
+                                      kJsonContentType);
+
+  resource_request->headers.SetHeader(
+      net::HttpRequestHeaders::kAuthorization,
+      base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str()));
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  // Add header to fetch the SecondaryGoogleAccountUsage policy from the API.
+  resource_request->headers.SetHeader(kChromeOSPolicyHeader,
+                                      kSecondaryGoogleAccountUsage);
+  auto url_loader =
+      network::SimpleURLLoader::Create(std::move(resource_request), annotation);
+  return url_loader;
+}
+
+}  // namespace
+
+UserCloudSigninRestrictionPolicyFetcherChromeOS::
+    UserCloudSigninRestrictionPolicyFetcherChromeOS(
+        const std::string& email,
+        scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+    : email_(email), url_loader_factory_(url_loader_factory) {
+  DCHECK(url_loader_factory_);
+}
+
+UserCloudSigninRestrictionPolicyFetcherChromeOS::
+    ~UserCloudSigninRestrictionPolicyFetcherChromeOS() = default;
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::
+    GetSecondaryGoogleAccountUsage(
+        std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher,
+        PolicyInfoCallback callback) {
+  DCHECK(access_token_fetcher);
+  DCHECK(callback);
+  DCHECK(!callback_) << "A request is already in progress";
+  callback_ = std::move(callback);
+  if (policy::BrowserPolicyConnector::IsNonEnterpriseUser(email_)) {
+    // Non Enterprise accounts do not have restrictions.
+    std::move(callback_).Run(/*status=*/Status::kUnsupportedAccountTypeError,
+                             /*policy=*/absl::nullopt,
+                             /*domain=*/std::string());
+    return;
+  }
+  access_token_fetcher_ = std::move(access_token_fetcher);
+  FetchAccessToken();
+}
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::FetchAccessToken() {
+  access_token_fetcher_->Start(
+      GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+      GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+      {GaiaConstants::kGoogleUserInfoEmail,
+       GaiaConstants::kGoogleUserInfoProfile});
+}
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::OnGetTokenSuccess(
+    const TokenResponse& token_response) {
+  access_token_ = token_response.access_token;
+  FetchUserInfo();
+}
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::OnGetTokenFailure(
+    const GoogleServiceAuthError& error) {
+  // TODO(b/223628330): Implement retry strategy.
+  LOG(ERROR) << "Failed to fetch access token for consumer: "
+             << GetConsumerName() << " with error: " << error.ToString();
+  std::move(callback_).Run(/*status=*/Status::kGetTokenError,
+                           /*policy=*/absl::nullopt,
+                           /*domain=*/std::string());
+}
+
+std::string UserCloudSigninRestrictionPolicyFetcherChromeOS::GetConsumerName()
+    const {
+  return "signin_restriction_policy_fetcher";
+}
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::FetchUserInfo() {
+  // TODO(b/224743604): Inject UserInfoFetcher as a dependency.
+  user_info_fetcher_ =
+      std::make_unique<policy::UserInfoFetcher>(this, url_loader_factory_);
+  user_info_fetcher_->Start(access_token_);
+}
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::OnGetUserInfoSuccess(
+    const base::DictionaryValue* user_info) {
+  // Check if the user account has a hosted domain.
+  if (user_info->HasKey(kHostedDomainKey)) {
+    hosted_domain_ = user_info->FindKey(kHostedDomainKey)->GetString();
+    GetSecondaryGoogleAccountUsageInternal();
+  } else {
+    // Non Enterprise accounts do not have restrictions.
+    DVLOG(1) << "User account is not an Enterprise account";
+    std::move(callback_).Run(/*status=*/Status::kUnsupportedAccountTypeError,
+                             /*policy=*/absl::nullopt,
+                             /*domain=*/std::string());
+  }
+}
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::OnGetUserInfoFailure(
+    const GoogleServiceAuthError& error) {
+  LOG(ERROR) << "Failed to fetch user info: " << error.ToString();
+  std::move(callback_).Run(/*status=*/Status::kGetUserInfoError,
+                           /*policy=*/absl::nullopt,
+                           /*domain=*/std::string());
+}
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::
+    GetSecondaryGoogleAccountUsageInternal() {
+  // Each url loader can only be used for one request.
+  url_loader_ =
+      CreateUrlLoader(GURL(kSecureConnectApiGetSecondaryGoogleAccountUsageUrl),
+                      access_token_, annotation);
+  // base::Unretained is safe here because `url_loader_` is owned by `this`.
+  url_loader_->DownloadToString(
+      url_loader_factory_.get(),
+      base::BindOnce(&UserCloudSigninRestrictionPolicyFetcherChromeOS::
+                         OnSecondaryGoogleAccountUsageResult,
+                     base::Unretained(this)),
+      1024 * 1024 /* 1 MiB */);
+}
+
+void UserCloudSigninRestrictionPolicyFetcherChromeOS::
+    OnSecondaryGoogleAccountUsageResult(
+        std::unique_ptr<std::string> response_body) {
+  absl::optional<std::string> restriction;
+  Status status = Status::kUnknownError;
+  std::unique_ptr<network::SimpleURLLoader> url_loader = std::move(url_loader_);
+
+  GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone();
+  absl::optional<int> response_code;
+  if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers)
+    response_code = url_loader->ResponseInfo()->headers->response_code();
+
+  if (url_loader->NetError() != net::OK) {
+    if (response_code) {
+      LOG(ERROR) << "SecondaryGoogleAccountUsage request "
+                    "failed with HTTP code: "
+                 << response_code.value();
+      status = Status::kHttpError;
+    } else {
+      error =
+          GoogleServiceAuthError::FromConnectionError(url_loader->NetError());
+      LOG(ERROR) << "SecondaryGoogleAccountUsage request "
+                    "failed with error: "
+                 << url_loader->NetError();
+      status = Status::kNetworkError;
+    }
+  } else if (error.state() == GoogleServiceAuthError::NONE) {
+    auto result = base::JSONReader::Read(*response_body, base::JSON_PARSE_RFC);
+    constexpr base::StringPiece policy_value_key = "policyValue";
+    if (response_body && result && result->FindStringKey(policy_value_key)) {
+      restriction = *result->FindStringKey(policy_value_key);
+      status = Status::kSuccess;
+    } else {
+      LOG(ERROR) << "Failed to parse SecondaryGoogleAccountUsage response";
+      status = Status::kParsingResponseError;
+    }
+  }
+
+  std::move(callback_).Run(status, restriction, hosted_domain_);
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.h b/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.h
new file mode 100644
index 0000000..04d8c585
--- /dev/null
+++ b/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.h
@@ -0,0 +1,149 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_SIGNIN_USER_CLOUD_SIGNIN_RESTRICTION_POLICY_FETCHER_CHROMEOS_H_
+#define CHROME_BROWSER_UI_WEBUI_SIGNIN_USER_CLOUD_SIGNIN_RESTRICTION_POLICY_FETCHER_CHROMEOS_H_
+
+#include <string>
+
+#include "components/policy/core/common/cloud/user_info_fetcher.h"
+#include "google_apis/gaia/oauth2_access_token_consumer.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace network {
+class SimpleURLLoader;
+}
+
+namespace ash {
+
+// UserCloudSigninRestrictionPolicyFetcherChromeOS handles requesting
+// SecondaryGoogleAccountUsage policy value for Chrome OS.
+// During this fetch, two extra requests need to be made:
+//
+//   1 - Requesting access token with the following scopes:
+//       - GaiaConstants::kGoogleUserInfoEmail
+//       - GaiaConstants::kGoogleUserInfoProfile
+//   2 - Checking if the account is an Enterprise account
+//
+// After fetching the policy value, it will run `callback` with the fetched
+// value, `Status::kSuccess` status and the hosted domain of the account. If any
+// error occurs during the process of fetching the policy value, `callback` will
+// run with `absl::nullopt` and a proper error status value to inform about the
+// error. If the account is not an Enterprise account, `callback` will run with
+// `absl::nullopt` and `Status::kUnsupportedAccountTypeError`.
+//
+// Note: This class is meant to be used in a one-shot fashion and cannot handle
+// multiple requests at the same time.
+//
+// Example usage:
+//
+// std::unique_ptr<GaiaAccessTokenFetcher> access_token_fetcher = ...;
+// base::OnceCallback<void(
+// UserCloudSigninRestrictionPolicyFetcherChromeOS::Status,
+// absl::optional<std::string>, const std::string&)> callback = ...;
+//
+// UserCloudSigninRestrictionPolicyFetcherChromeOS
+//  restriction_fetcher("alice@example.com", url_loader_factory);
+// restriction_fetcher.GetSecondaryGoogleAccountUsage(
+//     std::move(access_token_fetcher), callback);
+// TODO(b/222695699): Refactor this class to share code with
+// UserCloudSigninRestrictionPolicyFetcher.
+class UserCloudSigninRestrictionPolicyFetcherChromeOS
+    : public policy::UserInfoFetcher::Delegate,
+      public OAuth2AccessTokenConsumer {
+ public:
+  enum Status {
+    kSuccess = 0,
+    kNetworkError,
+    kHttpError,
+    kParsingResponseError,
+    kUnsupportedAccountTypeError,
+    kGetTokenError,
+    kGetUserInfoError,
+    kUnknownError
+  };
+
+  // Callback invoked when SecondaryGoogleAccountUsage policy value
+  // is fetched. `policy` is the fetched policy value. `domain` is the
+  // Enterprise account hosted domain.
+  using PolicyInfoCallback =
+      base::OnceCallback<void(Status status,
+                              absl::optional<std::string> policy,
+                              const std::string& domain)>;
+
+  // `email` can be a raw email (abc.123.4@gmail.com) or a canonicalized email
+  // (abc1234@gmail.com). It's used to skip API requests for domains such as
+  // gmail.com and others since these type of users are known to be
+  // non-enterprise. For more information check
+  // policy::BrowserPolicyConnector::IsNonEnterpriseUser.
+  UserCloudSigninRestrictionPolicyFetcherChromeOS(
+      const std::string& email,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+  ~UserCloudSigninRestrictionPolicyFetcherChromeOS() override;
+
+  UserCloudSigninRestrictionPolicyFetcherChromeOS(
+      const UserCloudSigninRestrictionPolicyFetcherChromeOS&) = delete;
+  UserCloudSigninRestrictionPolicyFetcherChromeOS& operator=(
+      const UserCloudSigninRestrictionPolicyFetcherChromeOS&) = delete;
+
+  // Fetches the value of the SecondaryGoogleAccountUsage policy and runs
+  // `callback` with the fetched value and `Status::kSuccess` status.
+  //
+  // If the policy was not set, `callback` will run with `absl::nullopt` and
+  // `Status::kSuccess` status.
+  // If there was an error in fetching the policy, `callback` will run with
+  // `absl::nullopt` and the proper error status.
+  void GetSecondaryGoogleAccountUsage(
+      std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher,
+      PolicyInfoCallback callback);
+
+  // Protected for testing.
+ protected:
+  // UserInfoFetcher::Delegate.
+  void OnGetUserInfoSuccess(const base::DictionaryValue* user_info) override;
+  void OnGetUserInfoFailure(const GoogleServiceAuthError& error) override;
+
+  // UserInfoFetcher::OAuth2AccessTokenConsumer.
+  void OnGetTokenSuccess(const TokenResponse& token_response) override;
+  void OnGetTokenFailure(const GoogleServiceAuthError& error) override;
+  std::string GetConsumerName() const override;
+
+  // Retrieves the policy value from `response_body` and runs `callback` with
+  // the retrieved value and `Status::kSuccess` status if successful.
+  // If there was an error in fetching the policy, it will runs `callback` with
+  // `absl::nullopt` and the proper error status.
+  void OnSecondaryGoogleAccountUsageResult(
+      std::unique_ptr<std::string> response_body);
+
+ private:
+  // Fetch access token with `GaiaConstants::kGoogleUserInfoEmail` and
+  // `GaiaConstants::kGoogleUserInfoProfile` scopes to get the policy
+  // restriction value for the account.
+  // Virtual for testing.
+  virtual void FetchAccessToken();
+  // Fetch user info to check if the account is an Enterprise account or not.
+  // Virtual for testing.
+  virtual void FetchUserInfo();
+  // Calls the SecureConnect API to get the SecondaryGoogleAccountUsage
+  // policy using `access_token` for the authentication. Calls
+  // `OnSecondaryGoogleAccountUsageResult` with the result from the API.
+  void GetSecondaryGoogleAccountUsageInternal();
+
+  std::string email_;
+  std::string hosted_domain_;
+  std::string access_token_;
+
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher_;
+  std::unique_ptr<policy::UserInfoFetcher> user_info_fetcher_;
+  PolicyInfoCallback callback_;
+
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+  std::unique_ptr<network::SimpleURLLoader> url_loader_;
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SIGNIN_USER_CLOUD_SIGNIN_RESTRICTION_POLICY_FETCHER_CHROMEOS_H_
diff --git a/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos_unittest.cc b/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos_unittest.cc
new file mode 100644
index 0000000..03ffe01
--- /dev/null
+++ b/chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos_unittest.cc
@@ -0,0 +1,356 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "google_apis/gaia/gaia_access_token_fetcher.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace ash {
+
+namespace {
+const char kSecureConnectApiGetSecondaryGoogleAccountUsageUrl[] =
+    "https://secureconnect-pa.clients6.google.com/"
+    "v1:getManagedAccountsSigninRestriction";
+const char kFakeAccessToken[] = "fake-access-token";
+const char kFakeRefreshToken[] = "fake-refresh-token";
+const char kFakeEnterpriseAccount[] = "alice@acme.com";
+const char kFakeEnterpriseDomain[] = "acme.com";
+const char kFakeGmailAccount[] = "example@gmail.com";
+const char kFakeNonEnterpriseAccount[] = "alice@nonenterprise.com";
+const char KBadResponseBody[] = "bad-response-body";
+}  // namespace
+
+class MockUserCloudSigninRestrictionPolicyFetcherChromeOS
+    : public UserCloudSigninRestrictionPolicyFetcherChromeOS {
+ public:
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS(
+      const std::string& email,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+      : UserCloudSigninRestrictionPolicyFetcherChromeOS(email,
+                                                        url_loader_factory) {
+    ON_CALL(*this, FetchAccessToken).WillByDefault([this]() {
+      return this->OnGetTokenSuccess(
+          OAuth2AccessTokenConsumer::TokenResponse::Builder()
+              .WithAccessToken(kFakeAccessToken)
+              .build());
+    });
+    ON_CALL(*this, FetchUserInfo).WillByDefault([this]() {
+      return this->OnGetUserInfoSuccess(
+          std::move(user_info_response_dictionary_.get()));
+    });
+  }
+
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS(
+      const MockUserCloudSigninRestrictionPolicyFetcherChromeOS&) = delete;
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS& operator=(
+      const MockUserCloudSigninRestrictionPolicyFetcherChromeOS&) = delete;
+
+  // TODO(b/224747082): Remove overrides.
+  void OnGetTokenFailure(const GoogleServiceAuthError& error);
+  void OnGetUserInfoFailure(const GoogleServiceAuthError& error);
+
+  MOCK_METHOD(void, FetchAccessToken, ());
+  MOCK_METHOD(void, FetchUserInfo, ());
+
+  std::unique_ptr<base::DictionaryValue> user_info_response_dictionary_ =
+      std::make_unique<base::DictionaryValue>();
+};
+
+void MockUserCloudSigninRestrictionPolicyFetcherChromeOS::OnGetTokenFailure(
+    const GoogleServiceAuthError& error) {
+  UserCloudSigninRestrictionPolicyFetcherChromeOS::OnGetTokenFailure(error);
+}
+
+void MockUserCloudSigninRestrictionPolicyFetcherChromeOS::OnGetUserInfoFailure(
+    const GoogleServiceAuthError& error) {
+  UserCloudSigninRestrictionPolicyFetcherChromeOS::OnGetUserInfoFailure(error);
+}
+
+class UserCloudSigninRestrictionPolicyFetcherChromeOSTest
+    : public ::testing::Test {
+ public:
+  UserCloudSigninRestrictionPolicyFetcherChromeOSTest() = default;
+
+  scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory() {
+    return base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+        &url_loader_factory_);
+  }
+
+  // Set dictionary values that will be used by `OnGetUserInfoResponse` to fake
+  // API server response.
+  void SetHostedDomain(
+      MockUserCloudSigninRestrictionPolicyFetcherChromeOS& restriction_fetcher,
+      const std::string& hosted_domain) {
+    restriction_fetcher.user_info_response_dictionary_->DictClear();
+    restriction_fetcher.user_info_response_dictionary_->SetKey(
+        "hd", base::Value(hosted_domain));
+  }
+
+ protected:
+  // Get policy value for SecondaryGoogleAccountUsage.
+  void GetSecondaryGoogleAccountUsageBlocking(
+      MockUserCloudSigninRestrictionPolicyFetcherChromeOS* const
+          restriction_fetcher,
+      std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher) {
+    base::RunLoop run_loop;
+    restriction_fetcher->GetSecondaryGoogleAccountUsage(
+        std::move(access_token_fetcher),
+        base::BindLambdaForTesting(
+            [this, &run_loop](
+                MockUserCloudSigninRestrictionPolicyFetcherChromeOS::Status st,
+                absl::optional<std::string> res, const std::string& hd) {
+              this->policy_result_ = res;
+              this->status_ = st;
+              this->hosted_domain_ = hd;
+              run_loop.Quit();
+            }));
+    run_loop.Run();
+  }
+  std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
+      MockUserCloudSigninRestrictionPolicyFetcherChromeOS*
+          restriction_fetcher) {
+    return GaiaAccessTokenFetcher::
+        CreateExchangeRefreshTokenForAccessTokenInstance(
+            restriction_fetcher, GetSharedURLLoaderFactory(),
+            kFakeRefreshToken);
+  }
+
+  // Check base/test/task_environment.h. This must be the first member /
+  // declared before any member that cares about tasks.
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
+
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS::Status status_ =
+      MockUserCloudSigninRestrictionPolicyFetcherChromeOS::Status::
+          kUnknownError;
+  absl::optional<std::string> policy_result_;
+  std::string hosted_domain_;
+  network::TestURLLoaderFactory url_loader_factory_;
+};
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherChromeOSTest,
+       FetchingPolicyValueSucceeds) {
+  // Set API response.
+  base::Value expected_response(base::Value::Type::DICTIONARY);
+  expected_response.SetStringKey("policyValue", "primary_account_signin");
+  std::string response;
+  JSONStringValueSerializer serializer(&response);
+  ASSERT_TRUE(serializer.Serialize(expected_response));
+  url_loader_factory_.AddResponse(
+      kSecureConnectApiGetSecondaryGoogleAccountUsageUrl, std::move(response));
+
+  // Create policy fetcher.
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS restriction_fetcher(
+      kFakeEnterpriseAccount, GetSharedURLLoaderFactory());
+  SetHostedDomain(restriction_fetcher, kFakeEnterpriseDomain);
+
+  // Create access token fetcher.
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher =
+      CreateAccessTokenFetcher(&restriction_fetcher);
+
+  GetSecondaryGoogleAccountUsageBlocking(&restriction_fetcher,
+                                         std::move(access_token_fetcher));
+
+  EXPECT_TRUE(policy_result_.has_value());
+  EXPECT_EQ(policy_result_.value(), "primary_account_signin");
+  EXPECT_EQ(
+      status_,
+      MockUserCloudSigninRestrictionPolicyFetcherChromeOS::Status::kSuccess);
+  EXPECT_EQ(hosted_domain_, kFakeEnterpriseDomain);
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherChromeOSTest,
+       FetchingUserInfoFailsForNetworkConnectionErrors) {
+  // Create policy fetcher.
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS restriction_fetcher(
+      kFakeEnterpriseAccount, GetSharedURLLoaderFactory());
+
+  // Create access token fetcher.
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher =
+      CreateAccessTokenFetcher(&restriction_fetcher);
+
+  // TODO(b/224754860): Use mocked dependency injection and remove this.
+  // Fake a failed UserInfo fetch.
+  EXPECT_CALL(restriction_fetcher, FetchUserInfo())
+      .WillOnce([&restriction_fetcher]() {
+        return restriction_fetcher.OnGetUserInfoFailure(
+            GoogleServiceAuthError::FromConnectionError(0));
+      });
+
+  // Try to fetch policy value.
+  GetSecondaryGoogleAccountUsageBlocking(&restriction_fetcher,
+                                         std::move(access_token_fetcher));
+
+  EXPECT_FALSE(policy_result_.has_value());
+  EXPECT_EQ(status_, MockUserCloudSigninRestrictionPolicyFetcherChromeOS::
+                         Status::kGetUserInfoError);
+  EXPECT_EQ(hosted_domain_, std::string());
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherChromeOSTest,
+       FetchingAccessTokenFailsForNetworkConnectionErrors) {
+  // Create policy fetcher.
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS restriction_fetcher(
+      kFakeEnterpriseAccount, GetSharedURLLoaderFactory());
+
+  // Create access token fetcher.
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher =
+      CreateAccessTokenFetcher(&restriction_fetcher);
+
+  // TODO(b/224754860): Use mocked dependency injection and remove this.
+  // Fake a failed AccessToken fetch.
+  EXPECT_CALL(restriction_fetcher, FetchAccessToken())
+      .WillOnce([&restriction_fetcher]() {
+        return restriction_fetcher.OnGetTokenFailure(
+            GoogleServiceAuthError::FromConnectionError(0));
+      });
+
+  // Try to fetch policy value.
+  GetSecondaryGoogleAccountUsageBlocking(&restriction_fetcher,
+                                         std::move(access_token_fetcher));
+
+  EXPECT_FALSE(policy_result_.has_value());
+  EXPECT_EQ(status_, MockUserCloudSigninRestrictionPolicyFetcherChromeOS::
+                         Status::kGetTokenError);
+  EXPECT_EQ(hosted_domain_, std::string());
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherChromeOSTest,
+       FetchingPolicyValueFailsForNetworkErrors) {
+  // Fake network error.
+  url_loader_factory_.AddResponse(
+      GURL(kSecureConnectApiGetSecondaryGoogleAccountUsageUrl),
+      /*head=*/network::mojom::URLResponseHead::New(),
+      /*content=*/std::string(),
+      network::URLLoaderCompletionStatus(net::ERR_INTERNET_DISCONNECTED),
+      network::TestURLLoaderFactory::Redirects(),
+      network::TestURLLoaderFactory::ResponseProduceFlags::
+          kSendHeadersOnNetworkError);
+
+  // Create policy fetcher.
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS restriction_fetcher(
+      kFakeEnterpriseAccount, GetSharedURLLoaderFactory());
+  SetHostedDomain(restriction_fetcher, kFakeEnterpriseDomain);
+
+  // Create access token fetcher.
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher =
+      CreateAccessTokenFetcher(&restriction_fetcher);
+
+  // Try to fetch policy value.
+  GetSecondaryGoogleAccountUsageBlocking(&restriction_fetcher,
+                                         std::move(access_token_fetcher));
+
+  EXPECT_FALSE(policy_result_.has_value());
+  EXPECT_EQ(status_, MockUserCloudSigninRestrictionPolicyFetcherChromeOS::
+                         Status::kNetworkError);
+  EXPECT_EQ(hosted_domain_, kFakeEnterpriseDomain);
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherChromeOSTest,
+       FetchingPolicyValueFailsForHTTPErrors) {
+  url_loader_factory_.AddResponse(
+      kSecureConnectApiGetSecondaryGoogleAccountUsageUrl, std::string(),
+      net::HTTP_BAD_GATEWAY);
+
+  // Create policy fetcher.
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS restriction_fetcher(
+      kFakeEnterpriseAccount, GetSharedURLLoaderFactory());
+  SetHostedDomain(restriction_fetcher, kFakeEnterpriseDomain);
+
+  // Create access token fetcher.
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher =
+      CreateAccessTokenFetcher(&restriction_fetcher);
+
+  // Try to fetch policy value.
+  GetSecondaryGoogleAccountUsageBlocking(&restriction_fetcher,
+                                         std::move(access_token_fetcher));
+
+  EXPECT_FALSE(policy_result_.has_value());
+  EXPECT_EQ(
+      status_,
+      MockUserCloudSigninRestrictionPolicyFetcherChromeOS::Status::kHttpError);
+  EXPECT_EQ(hosted_domain_, kFakeEnterpriseDomain);
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherChromeOSTest,
+       FetchingPolicyReturnsEmptyPolicyForResponsesNotParsable) {
+  url_loader_factory_.AddResponse(
+      kSecureConnectApiGetSecondaryGoogleAccountUsageUrl, KBadResponseBody);
+
+  // Create policy fetcher.
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS restriction_fetcher(
+      kFakeEnterpriseAccount, GetSharedURLLoaderFactory());
+  SetHostedDomain(restriction_fetcher, kFakeEnterpriseDomain);
+
+  // Create access token fetcher.
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher =
+      CreateAccessTokenFetcher(&restriction_fetcher);
+
+  // Try to fetch policy value.
+  GetSecondaryGoogleAccountUsageBlocking(&restriction_fetcher,
+                                         std::move(access_token_fetcher));
+
+  EXPECT_FALSE(policy_result_.has_value());
+  EXPECT_EQ(status_, MockUserCloudSigninRestrictionPolicyFetcherChromeOS::
+                         Status::kParsingResponseError);
+  EXPECT_EQ(hosted_domain_, kFakeEnterpriseDomain);
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherChromeOSTest,
+       FetchingPolicyReturnsEmptyPolicyForNonEnterpriseAccounts) {
+  url_loader_factory_.AddResponse(
+      kSecureConnectApiGetSecondaryGoogleAccountUsageUrl, KBadResponseBody);
+
+  // Create policy fetcher.
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS restriction_fetcher(
+      kFakeGmailAccount, GetSharedURLLoaderFactory());
+
+  // Create access token fetcher.
+  std::unique_ptr<OAuth2AccessTokenFetcher> access_token_fetcher =
+      CreateAccessTokenFetcher(&restriction_fetcher);
+
+  EXPECT_CALL(restriction_fetcher, FetchUserInfo()).Times(0);
+
+  // Try to fetch policy value.
+  GetSecondaryGoogleAccountUsageBlocking(&restriction_fetcher,
+                                         std::move(access_token_fetcher));
+
+  EXPECT_FALSE(policy_result_.has_value());
+  EXPECT_EQ(status_, MockUserCloudSigninRestrictionPolicyFetcherChromeOS::
+                         Status::kUnsupportedAccountTypeError);
+  EXPECT_EQ(hosted_domain_, std::string());
+
+  MockUserCloudSigninRestrictionPolicyFetcherChromeOS restriction_fetcherother(
+      kFakeNonEnterpriseAccount, GetSharedURLLoaderFactory());
+  EXPECT_CALL(restriction_fetcherother, FetchUserInfo()).Times(1);
+
+  // Recreate access token fetcher.
+  access_token_fetcher =
+      GaiaAccessTokenFetcher::CreateExchangeRefreshTokenForAccessTokenInstance(
+          &restriction_fetcherother, GetSharedURLLoaderFactory(),
+          kFakeRefreshToken);
+
+  // Try to fetch policy value.
+  GetSecondaryGoogleAccountUsageBlocking(&restriction_fetcherother,
+                                         std::move(access_token_fetcher));
+
+  EXPECT_FALSE(policy_result_.has_value());
+  EXPECT_EQ(status_, MockUserCloudSigninRestrictionPolicyFetcherChromeOS::
+                         Status::kUnsupportedAccountTypeError);
+  EXPECT_EQ(hosted_domain_, std::string());
+}
+
+}  // namespace ash
diff --git a/chrome/browser/webapps/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenIPHController.java b/chrome/browser/webapps/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenIPHController.java
index 8fafe62..a5009d7e 100644
--- a/chrome/browser/webapps/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenIPHController.java
+++ b/chrome/browser/webapps/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenIPHController.java
@@ -32,6 +32,7 @@
 import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.messages.MessageIdentifier;
 import org.chromium.components.messages.MessageScopeType;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
 import org.chromium.components.webapk.lib.client.WebApkValidator;
 import org.chromium.components.webapps.AddToHomescreenCoordinator;
 import org.chromium.components.webapps.AppBannerManager;
@@ -186,7 +187,10 @@
                                         R.string.iph_message_add_to_home_screen_action))
                         .with(MessageBannerProperties.ON_DISMISSED, this::onMessageDismissed)
                         .with(MessageBannerProperties.ON_PRIMARY_ACTION,
-                                () -> onMessageAddButtonClicked(tab))
+                                () -> {
+                                    onMessageAddButtonClicked(tab);
+                                    return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
+                                })
                         .build();
         mMessageDispatcher.enqueueMessage(
                 model, tab.getWebContents(), MessageScopeType.NAVIGATION, false);
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 4cd66c3a..7e5b6f1 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1647345463-736d059e8af0251b8a386c0b345b3ea057c9a337.profdata
+chrome-linux-main-1647367193-3ef6ef3f3d4faa5595f47be738b88b95eca51ec1.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 2a994009..7e9d838 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1647345463-da66cb39271225e688567ed8e7aa84b009733159.profdata
+chrome-win32-main-1647356256-21e2b821f156a158c08bae662871bba15024150b.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index b73eead9..97c867d 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1647345463-13f8b0d86512c492de6a208c6ccace24196256ca.profdata
+chrome-win64-main-1647356256-ab140ee892f376cc05c3352af5873da75ff35bc5.profdata
diff --git a/chrome/common/extensions/api/tabs.json b/chrome/common/extensions/api/tabs.json
index f92fdcb..421c39e 100644
--- a/chrome/common/extensions/api/tabs.json
+++ b/chrome/common/extensions/api/tabs.json
@@ -206,6 +206,12 @@
                 "optional": true,
                 "minimum": 0,
                 "description": "Open a port to a specific <a href='webNavigation#frame_ids'>frame</a> identified by <code>frameId</code> instead of all frames in the tab."
+              },
+              "documentId": {
+                "type": "string",
+                "optional": true,
+                "nodoc": true,
+                "description": "Open a port to a specific <a href='webNavigation#document_ids'>document</a> identified by <code>documentId</code> instead of all frames in the tab."
               }
             },
             "optional": true
@@ -270,6 +276,12 @@
                 "optional": true,
                 "minimum": 0,
                 "description": "Send a message to a specific <a href='webNavigation#frame_ids'>frame</a> identified by <code>frameId</code> instead of all frames in the tab."
+              },
+              "documentId": {
+                "type": "string",
+                "optional": true,
+                "nodoc": true,
+                "description": "Send a message to a specific <a href='webNavigation#document_ids'>document</a> identified by <code>documentId</code> instead of all frames in the tab."
               }
             },
             "optional": true
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 7fae1ce..52dcf9b 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -377,6 +377,7 @@
 const char kOsUISettingsURL[] = "os://settings";
 const char kOsUISignInInternalsURL[] = "os://signin-internals";
 const char kOsUISyncInternalsURL[] = "os://sync-internals";
+const char kOsUISysInternalsUrl[] = "os://sys-internals";
 const char kOsUISystemURL[] = "os://system";
 const char kOsUITermsURL[] = "os://terms";
 
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 58dfe701..3b4bd8d 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -354,6 +354,7 @@
 extern const char kOsUISettingsURL[];
 extern const char kOsUISignInInternalsURL[];
 extern const char kOsUISyncInternalsURL[];
+extern const char kOsUISysInternalsUrl[];
 extern const char kOsUISystemURL[];
 extern const char kOsUITermsURL[];
 
diff --git a/chrome/renderer/extensions/tabs_hooks_delegate.cc b/chrome/renderer/extensions/tabs_hooks_delegate.cc
index 78c2857..5df7ef36 100644
--- a/chrome/renderer/extensions/tabs_hooks_delegate.cc
+++ b/chrome/renderer/extensions/tabs_hooks_delegate.cc
@@ -142,7 +142,8 @@
     response_callback = arguments[3].As<v8::Function>();
 
   v8::Local<v8::Promise> promise = messaging_service_->SendOneTimeMessage(
-      script_context, MessageTarget::ForTab(tab_id, options.frame_id),
+      script_context,
+      MessageTarget::ForTab(tab_id, options.frame_id, options.document_id),
       messaging_util::kSendMessageChannel, *message, parse_result.async_type,
       response_callback);
   DCHECK_EQ(parse_result.async_type == binding::AsyncResponseType::kPromise,
@@ -174,7 +175,8 @@
   }
 
   gin::Handle<GinPort> port = messaging_service_->Connect(
-      script_context, MessageTarget::ForTab(tab_id, options.frame_id),
+      script_context,
+      MessageTarget::ForTab(tab_id, options.frame_id, options.document_id),
       options.channel_name,
       messaging_util::GetSerializationFormat(*script_context));
   DCHECK(!port.IsEmpty());
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4ac1a7a..781c92d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5271,6 +5271,7 @@
       "../browser/performance_manager/policies/working_set_trimmer_policy_chromeos_unittest.cc",
       "../browser/support_tool/ash/ui_hierarchy_data_collector_unittest.cc",
       "../browser/ui/views/crostini/crostini_app_restart_dialog_unittest.cc",
+      "../browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos_unittest.cc",
     ]
 
     if (target_cpu == "x64") {
@@ -6725,7 +6726,6 @@
       "../browser/ui/views/passwords/password_save_unsynced_credentials_locally_view_unittest.cc",
       "../browser/ui/views/passwords/password_save_update_view_unittest.cc",
       "../browser/ui/views/passwords/post_save_compromised_bubble_view_unittest.cc",
-      "../browser/ui/views/side_panel/side_panel_coordinator_unittest.cc",
       "../renderer/media/webrtc_logging_agent_impl_unittest.cc",
     ]
 
@@ -6809,6 +6809,7 @@
       "../browser/lacros/account_manager/account_profile_mapper_unittest.cc",
       "../browser/lacros/account_manager/get_account_information_helper_unittest.cc",
       "../browser/lacros/account_manager/profile_account_manager_unittest.cc",
+      "../browser/lacros/account_manager/web_signin_helper_lacros_unittest.cc",
       "../browser/lacros/cert/client_cert_store_lacros_unittest.cc",
       "../browser/lacros/force_installed_tracker_lacros_unittest.cc",
       "../browser/lacros/lacros_memory_pressure_evaluator_unittest.cc",
@@ -8126,6 +8127,7 @@
       "../browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl_unittest.cc",
       "../browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view_unittest.cc",
       "../browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble_unittest.cc",
+      "../browser/ui/views/side_panel/side_panel_coordinator_unittest.cc",
       "../browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_unittest.cc",
       "../browser/ui/views/tabs/color_picker_view_unittest.cc",
       "../browser/ui/views/tabs/fake_base_tab_strip_controller.cc",
diff --git a/chrome/test/data/extensions/api_test/messaging/connect/test.js b/chrome/test/data/extensions/api_test/messaging/connect/test.js
index 9eddd7f2..d02d427 100644
--- a/chrome/test/data/extensions/api_test/messaging/connect/test.js
+++ b/chrome/test/data/extensions/api_test/messaging/connect/test.js
@@ -182,6 +182,62 @@
         'Could not establish connection. Receiving end does not exist.'));
     },
 
+    // connect with a valid documentId should trigger onConnect in that specific
+    // document only.
+    function sendMessageToDocumentInTab() {
+      chrome.webNavigation.getAllFrames({
+        tabId: testTab.id
+      }, function(details) {
+        var frames = details.filter(function(frame) {
+          return /\?testSendMessageFromFrame1$/.test(frame.url);
+        });
+        chrome.test.assertEq(1, frames.length);
+        connectToTabWithDocumentId(frames[0].documentId, ['from_1']);
+      });
+    },
+
+    // connect with a valid frameId and documentId should trigger onConnect in
+    // that specific document only.
+    function sendMessageToDocumentInTab() {
+      chrome.webNavigation.getAllFrames({
+        tabId: testTab.id
+      }, function(details) {
+        var frames = details.filter(function(frame) {
+          return /\?testSendMessageFromFrame1$/.test(frame.url);
+        });
+        chrome.test.assertEq(1, frames.length);
+        connectToTabWithOptions({documentId: frames[0].documentId,
+                                 frameId: frames[0].frameId
+                                }, ['from_1']);
+      });
+    },
+
+    // sendMessage with a valid documentId but invalid frameId should fail.
+    function sendMessageToInvalidDocumentFrameIdInTab() {
+      chrome.webNavigation.getAllFrames({
+        tabId: testTab.id
+      }, function(details) {
+        var frames = details.filter(function(frame) {
+          return /\?testSendMessageFromFrame1$/.test(frame.url);
+        });
+        chrome.test.assertEq(1, frames.length);
+        chrome.tabs.sendMessage(testTab.id, {}, {
+          documentId: frames[0].documentId,
+          // Some (hopefully) invalid frameId.
+          frameId: 999999999
+        }, chrome.test.callbackFail(
+          'Could not establish connection. Receiving end does not exist.'));
+      });
+    },
+
+    // sendMessage with an invalid documentId should fail.
+    function sendMessageToInvalidDocumentInTab() {
+      chrome.tabs.sendMessage(testTab.id, {}, {
+        documentId: '0123456789ABCDEF' // A truncated documentId.
+      }, chrome.test.callbackFail(
+        'Could not establish connection. Receiving end does not exist.'));
+    },
+
     // Tests error handling when sending a request from a content script to an
     // invalid extension.
     function sendMessageFromTabError() {
@@ -388,16 +444,15 @@
   ]);
 });
 
-function connectToTabWithFrameId(frameId, expectedMessages) {
-  var port = chrome.tabs.connect(testTab.id, {
-    frameId: frameId
-  });
+function connectToTabWithOptions(options, expectedMessages) {
+  var port = chrome.tabs.connect(testTab.id, options);
   var messages = [];
   var isDone = false;
   port.onMessage.addListener(function(message) {
     if (isDone) { // Should not get any messages after completing the test.
       chrome.test.fail(
-          'Unexpected message from port to frame ' + frameId + ': ' + message);
+          'Unexpected message from port to frame ' + JSON.stringify(options) +
+          ': ' + message);
       return;
     }
 
@@ -410,10 +465,24 @@
   });
   port.onDisconnect.addListener(function() {
     if (!isDone) // The event should never be triggered when we expect messages.
-      chrome.test.fail('Unexpected disconnect from port to frame ' + frameId);
+    chrome.test.fail('Unexpected disconnect from port to frame ' +
+                     JSON.stringify(options));
   });
   port.postMessage({testSendMessageToFrame: true});
-  chrome.test.log('connectToTabWithFrameId: port to frame ' + frameId);
+  chrome.test.log('connectToTabWithOptions: port to frame ' +
+                  JSON.stringify(options));
+}
+
+function connectToTabWithFrameId(frameId, expectedMessages) {
+  connectToTabWithOptions({
+    frameId: frameId
+  }, expectedMessages);
+}
+
+function connectToTabWithDocumentId(documentId, expectedMessages) {
+  connectToTabWithOptions({
+    documentId: documentId
+  }, expectedMessages);
 }
 
 // Listens to |event| and returns a callback to run to stop listening. While
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 13ea5055..4c30f58 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -17184,10 +17184,7 @@
     ]
   },
   "SideSearchEnabled": {
-    "os": [
-      "chromeos_ash",
-      "chromeos_lacros"
-    ],
+    "os": ["win", "linux", "mac", "chromeos_ash", "chromeos_lacros"],
     "policy_pref_mapping_tests": [
       {
         "policies": {
diff --git a/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_element_test.ts
index 22de30b9..ee8e5338 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_element_test.ts
@@ -9,7 +9,7 @@
 import {WallpaperGridItem} from 'chrome://personalization/trusted/wallpaper/wallpaper_grid_item_element.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {String16} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
-import {assertDeepEquals, assertEquals, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
+import {assertDeepEquals, assertEquals, assertNotEquals, assertNotReached} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender} from 'chrome://webui-test/test_util.js';
 
 import {baseSetup, initElement, teardownElement} from './personalization_app_test_utils.js';
@@ -39,6 +39,19 @@
     return matches ? [...matches] : null;
   }
 
+  /** Scrolls the specified |element| until |predicate| returns true. */
+  async function scrollElementUntil(
+      element: HTMLElement, predicate: () => boolean) {
+    const timeout = +new Date() + 1000;
+    while (!predicate()) {
+      element.scrollBy(0, 500);
+      await waitAfterNextRender(googlePhotosPhotosElement!);
+      if (+new Date() > timeout) {
+        assertNotReached('Timed out while scrolling.');
+      }
+    }
+  }
+
   /**
    * Returns a list of |GooglePhotosPhotosSection|'s for the specified |photos|
    * and number of |photosPerRow|.
@@ -300,6 +313,103 @@
     assertEquals(photoEls[1]!.selected, false);
   });
 
+  test('incrementally loads photos', async () => {
+    // Set photos count returned by |wallpaperProvider|.
+    const photosCount = 200;
+    wallpaperProvider.setGooglePhotosCount(photosCount);
+
+    // Set initial list of photos returned by |wallpaperProvider|.
+    let nextPhotoId = 1;
+    wallpaperProvider.setGooglePhotosPhotos(
+        Array.from({length: photosCount / 2}).map(() => {
+          return {
+            id: `id-${nextPhotoId}`,
+            name: `name-${nextPhotoId}`,
+            date: {data: []},
+            url: {url: `url-${nextPhotoId++}`},
+          };
+        }));
+
+    // Set initial photos resume token returned  by |wallpaperProvider|. When
+    // resume token is defined, it indicates additional photos exist.
+    const resumeToken = 'resumeToken';
+    wallpaperProvider.setGooglePhotosPhotosResumeToken(resumeToken);
+
+    // Initialize Google Photos data in |personalizationStore|.
+    await initializeGooglePhotosData(wallpaperProvider, personalizationStore);
+    assertDeepEquals(
+        await wallpaperProvider.whenCalled('fetchGooglePhotosPhotos'),
+        [/*itemId=*/ null, /*albumId=*/ null, /*resumeToken=*/ null]);
+
+    // Reset |wallpaperProvider| expectations.
+    wallpaperProvider.resetResolver('fetchGooglePhotosPhotos');
+
+    // Set the next list of photos returned by |wallpaperProvider|.
+    wallpaperProvider.setGooglePhotosPhotos(
+        Array.from({length: photosCount / 2}).map(() => {
+          return {
+            id: `id-${nextPhotoId}`,
+            name: `name-${nextPhotoId}`,
+            date: {data: []},
+            url: {url: `url-${nextPhotoId++}`},
+          };
+        }));
+
+    // Set the next photos resume token returned by |wallpaperProvider|. When
+    // resume token is undefined, it indicates no additional photos exist.
+    wallpaperProvider.setGooglePhotosPhotosResumeToken(undefined);
+
+    // Restrict the viewport so that |googlePhotosPhotosElement| will lazily
+    // create photos instead of creating them all at once.
+    const style = document.createElement('style');
+    style.appendChild(document.createTextNode(`
+      html,
+      body {
+        height: 100%;
+        width: 100%;
+      }
+    `));
+    document.head.appendChild(style);
+
+    // Initialize |googlePhotosPhotosElement|.
+    googlePhotosPhotosElement =
+        initElement(GooglePhotosPhotos, {hidden: false});
+    await waitAfterNextRender(googlePhotosPhotosElement);
+
+    // Register an event listener to cache whether the |gridScrollThreshold| has
+    // been reached.
+    let gridScrollThresholdReached = false;
+    const gridScrollThreshold = googlePhotosPhotosElement.$.gridScrollThreshold;
+    gridScrollThreshold.addEventListener('lower-threshold', () => {
+      gridScrollThresholdReached = true;
+    });
+
+    // Scroll until the |gridScrollThreshold| is reached.
+    await scrollElementUntil(gridScrollThreshold, () => {
+      return gridScrollThresholdReached;
+    });
+
+    // Wait for and verify that the next batch of photos have been requested.
+    assertDeepEquals(
+        await wallpaperProvider.whenCalled('fetchGooglePhotosPhotos'),
+        [/*itemId=*/ null, /*albumId=*/ null, /*resumeToken=*/ resumeToken]);
+    await waitAfterNextRender(googlePhotosPhotosElement);
+
+    // Reset |wallpaperProvider| expectations.
+    wallpaperProvider.resetResolver('fetchGooglePhotosPhotos');
+
+    // Scroll until the bottom of the grid is reached.
+    let scrollTop = -1;
+    await scrollElementUntil(gridScrollThreshold, () => {
+      const oldScrollTop = scrollTop;
+      scrollTop = gridScrollThreshold.scrollTop;
+      return scrollTop === oldScrollTop;
+    });
+
+    // Verify that no next batch of photos has been requested.
+    assertEquals(wallpaperProvider.getCallCount('fetchGooglePhotosPhotos'), 0);
+  });
+
   test('selects photo', async () => {
     const photo: GooglePhotosPhoto = {
       id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
index e7ffd75..476877b 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
@@ -85,8 +85,9 @@
               albums: expectedAlbums,
             },
             {
-              name: 'set_google_photos_photos',
+              name: 'append_google_photos_photos',
               photos: expectedPhotos,
+              resumeToken: null,
             },
           ],
           personalizationStore.actions);
@@ -106,6 +107,7 @@
                 albums: undefined,
                 photos: undefined,
                 photosByAlbumId: {},
+                resumeTokens: {photos: null},
               },
             },
             // SET_GOOGLE_PHOTOS_COUNT.
@@ -121,6 +123,7 @@
                 albums: undefined,
                 photos: undefined,
                 photosByAlbumId: {},
+                resumeTokens: {photos: null},
               },
             },
             // BEGIN_LOAD_GOOGLE_PHOTOS_ALBUMS.
@@ -136,6 +139,7 @@
                 albums: undefined,
                 photos: undefined,
                 photosByAlbumId: {},
+                resumeTokens: {photos: null},
               },
             },
             // BEGIN_LOAD_GOOGLE_PHOTOS_PHOTOS.
@@ -151,6 +155,7 @@
                 albums: undefined,
                 photos: undefined,
                 photosByAlbumId: {},
+                resumeTokens: {photos: null},
               },
             },
             // SET_GOOGLE_PHOTOS_ALBUMS.
@@ -166,9 +171,10 @@
                 albums: expectedAlbums,
                 photos: undefined,
                 photosByAlbumId: {},
+                resumeTokens: {photos: null},
               },
             },
-            // SET_GOOGLE_PHOTOS_PHOTOS.
+            // APPEND_GOOGLE_PHOTOS_PHOTOS.
             {
               'wallpaper.loading.googlePhotos': {
                 count: false,
@@ -181,6 +187,7 @@
                 albums: expectedAlbums,
                 photos: expectedPhotos,
                 photosByAlbumId: {},
+                resumeTokens: {photos: null},
               },
             },
           ],
@@ -253,6 +260,7 @@
               ],
               photos: undefined,
               photosByAlbumId: {},
+              resumeTokens: {photos: null},
             },
           },
           // SET_GOOGLE_PHOTOS_ALBUM
@@ -276,6 +284,7 @@
               photosByAlbumId: {
                 [album.id]: photos,
               },
+              resumeTokens: {photos: null},
             },
           },
         ],
diff --git a/chrome/test/data/webui/chromeos/personalization_app/test_wallpaper_interface_provider.ts b/chrome/test/data/webui/chromeos/personalization_app/test_wallpaper_interface_provider.ts
index 2695e2f5f..742e3ab 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/test_wallpaper_interface_provider.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/test_wallpaper_interface_provider.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {CurrentWallpaper, FetchGooglePhotosAlbumsResponse, FetchGooglePhotosPhotosResponse, GooglePhotosAlbum, GooglePhotosPhoto, OnlineImageType, WallpaperCollection, WallpaperImage, WallpaperLayout, WallpaperObserverInterface, WallpaperObserverRemote, WallpaperProviderInterface, WallpaperType} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js';
+import {CurrentWallpaper, FetchGooglePhotosAlbumsResponse, FetchGooglePhotosPhotosResponse, GooglePhotosAlbum, GooglePhotosEnablementState, GooglePhotosPhoto, OnlineImageType, WallpaperCollection, WallpaperImage, WallpaperLayout, WallpaperObserverInterface, WallpaperObserverRemote, WallpaperProviderInterface, WallpaperType} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
 import {assertTrue} from 'chrome://webui-test/chai_assert.js';
@@ -18,6 +18,7 @@
       'fetchImagesForCollection',
       'fetchGooglePhotosAlbums',
       'fetchGooglePhotosCount',
+      'fetchGooglePhotosEnabled',
       'fetchGooglePhotosPhotos',
       'getLocalImages',
       'getLocalImageThumbnail',
@@ -97,7 +98,10 @@
   private images_: WallpaperImage[]|null;
   private googlePhotosAlbums_: GooglePhotosAlbum[]|undefined = [];
   private googlePhotosCount_: number = 0;
+  private googlePhotosEnabled_: GooglePhotosEnablementState =
+      GooglePhotosEnablementState.kError;
   private googlePhotosPhotos_: GooglePhotosPhoto[]|undefined = [];
+  private googlePhotosPhotosResumeToken_: string|undefined;
   private googlePhotosPhotosByAlbumId_:
       Record<string, GooglePhotosPhoto[]|undefined> = {};
   localImages: FilePath[]|null;
@@ -156,15 +160,25 @@
     return Promise.resolve({count});
   }
 
-  fetchGooglePhotosPhotos(itemId: string, albumId: string) {
-    this.methodCalled('fetchGooglePhotosPhotos', itemId, albumId);
+  fetchGooglePhotosEnabled() {
+    this.methodCalled('fetchGooglePhotosEnabled');
+    const state = loadTimeData.getBoolean('isGooglePhotosIntegrationEnabled') ?
+        this.googlePhotosEnabled_ :
+        GooglePhotosEnablementState.kError;
+    return Promise.resolve({state});
+  }
+
+  fetchGooglePhotosPhotos(
+      itemId: string, albumId: string, resumeToken: string) {
+    this.methodCalled('fetchGooglePhotosPhotos', itemId, albumId, resumeToken);
     const response = new FetchGooglePhotosPhotosResponse();
     response.photos =
         loadTimeData.getBoolean('isGooglePhotosIntegrationEnabled') ?
         albumId ? this.googlePhotosPhotosByAlbumId_[albumId] :
                   this.googlePhotosPhotos_ :
         undefined;
-    response.resumeToken = undefined;
+    response.resumeToken =
+        albumId ? undefined : this.googlePhotosPhotosResumeToken_;
     return Promise.resolve({response});
   }
 
@@ -249,10 +263,18 @@
     this.googlePhotosCount_ = googlePhotosCount;
   }
 
+  setGooglePhotosEnabled(googlePhotosEnabled: number) {
+    this.googlePhotosEnabled_ = googlePhotosEnabled;
+  }
+
   setGooglePhotosPhotos(googlePhotosPhotos: GooglePhotosPhoto[]|undefined) {
     this.googlePhotosPhotos_ = googlePhotosPhotos;
   }
 
+  setGooglePhotosPhotosResumeToken(resumeToken: string|undefined) {
+    this.googlePhotosPhotosResumeToken_ = resumeToken;
+  }
+
   setGooglePhotosPhotosByAlbumId(
       albumId: string, googlePhotosPhotos: GooglePhotosPhoto[]|undefined) {
     this.googlePhotosPhotosByAlbumId_[albumId] = googlePhotosPhotos;
diff --git a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_grid_item_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_grid_item_element_test.ts
index 88a259b6..c8b09275 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_grid_item_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_grid_item_element_test.ts
@@ -46,6 +46,7 @@
 
     // Verify state.
     assertEquals(querySelector('img')?.getAttribute('auto-src'), imageSrc);
+    assertEquals(querySelector('img')?.hasAttribute('clear-src'), true);
     assertEquals(querySelector('img')?.hasAttribute('with-cookies'), true);
     assertEquals(querySelector('.text'), null);
     assertEquals(querySelector('.primary-text'), null);
diff --git a/chrome/updater/app/app_install.cc b/chrome/updater/app/app_install.cc
index cc0876f..55287a3d 100644
--- a/chrome/updater/app/app_install.cc
+++ b/chrome/updater/app/app_install.cc
@@ -91,9 +91,7 @@
 
 AppInstall::~AppInstall() = default;
 
-void AppInstall::Initialize() {
-  base::i18n::InitializeICU();
-}
+void AppInstall::Initialize() {}
 
 void AppInstall::Uninitialize() {
   if (update_service_) {
diff --git a/chrome/updater/app/server/mac/service_delegate.mm b/chrome/updater/app/server/mac/service_delegate.mm
index 2001673..308df520 100644
--- a/chrome/updater/app/server/mac/service_delegate.mm
+++ b/chrome/updater/app/server/mac/service_delegate.mm
@@ -144,6 +144,7 @@
 }
 
 - (void)checkForUpdateWithAppID:(NSString* _Nonnull)appID
+               installDataIndex:(NSString* _Nullable)installDataIndex
                        priority:(CRUPriorityWrapper* _Nonnull)priority
         policySameVersionUpdate:
             (CRUPolicySameVersionUpdateWrapper* _Nonnull)policySameVersionUpdate
@@ -189,7 +190,9 @@
   _callbackRunner->PostTask(
       FROM_HERE,
       base::BindOnce(&updater::UpdateService::Update, _service,
-                     base::SysNSStringToUTF8(appID), [priority priority],
+                     base::SysNSStringToUTF8(appID),
+                     base::SysNSStringToUTF8(installDataIndex),
+                     [priority priority],
                      [policySameVersionUpdate policySameVersionUpdate],
                      std::move(sccb), std::move(cb)));
 }
@@ -300,6 +303,7 @@
 }
 
 - (void)checkForUpdateWithAppID:(NSString* _Nonnull)appID
+               installDataIndex:(NSString* _Nullable)installDataIndex
                        priority:(CRUPriorityWrapper* _Nonnull)priority
         policySameVersionUpdate:
             (CRUPolicySameVersionUpdateWrapper* _Nonnull)policySameVersionUpdate
@@ -307,6 +311,7 @@
                           reply:(void (^_Nonnull)(int rc))reply {
   // This function may be called by any user.
   [_service checkForUpdateWithAppID:appID
+                   installDataIndex:installDataIndex
                            priority:priority
             policySameVersionUpdate:policySameVersionUpdate
                         updateState:updateState
diff --git a/chrome/updater/app/server/mac/service_protocol.h b/chrome/updater/app/server/mac/service_protocol.h
index c87d218..53ad351 100644
--- a/chrome/updater/app/server/mac/service_protocol.h
+++ b/chrome/updater/app/server/mac/service_protocol.h
@@ -43,6 +43,7 @@
 // Checks for update of a given app, with specified priority. Sends repeated
 // updates of progress and returns the result in the reply block.
 - (void)checkForUpdateWithAppID:(NSString* _Nonnull)appID
+               installDataIndex:(NSString* _Nullable)installDataIndex
                        priority:(CRUPriorityWrapper* _Nonnull)priority
         policySameVersionUpdate:
             (CRUPolicySameVersionUpdateWrapper* _Nonnull)policySameVersionUpdate
diff --git a/chrome/updater/app/server/mac/service_protocol.mm b/chrome/updater/app/server/mac/service_protocol.mm
index d86549f..6cf34a7 100644
--- a/chrome/updater/app/server/mac/service_protocol.mm
+++ b/chrome/updater/app/server/mac/service_protocol.mm
@@ -24,8 +24,9 @@
        setInterface:updateStateObservingInterface
         forSelector:@selector
         (checkForUpdateWithAppID:
-                        priority:policySameVersionUpdate:updateState:reply:)
-      argumentIndex:3
+                installDataIndex:priority:policySameVersionUpdate:updateState
+                                :reply:)
+      argumentIndex:4
             ofReply:NO];
 
   return updateCheckingInterface;
diff --git a/chrome/updater/app/server/win/com_classes.cc b/chrome/updater/app/server/win/com_classes.cc
index 98951dbd..7fd183c9 100644
--- a/chrome/updater/app/server/win/com_classes.cc
+++ b/chrome/updater/app/server/win/com_classes.cc
@@ -279,6 +279,7 @@
 // calls must be done through a task runner, bound to the closures provided
 // as parameters for the UpdateService::Update call.
 HRESULT UpdaterImpl::Update(const wchar_t* app_id,
+                            const wchar_t* install_data_index,
                             BOOL same_version_update_allowed,
                             IUpdaterObserver* observer) {
   // This task runner is responsible for sequencing the callbacks posted
@@ -296,10 +297,11 @@
       base::BindOnce(
           [](scoped_refptr<UpdateService> update_service,
              scoped_refptr<base::SequencedTaskRunner> task_runner,
-             const std::string& app_id, bool same_version_update_allowed,
-             IUpdaterObserverPtr observer) {
+             const std::string& app_id, const std::string& install_data_index,
+             bool same_version_update_allowed, IUpdaterObserverPtr observer) {
             update_service->Update(
-                app_id, UpdateService::Priority::kForeground,
+                app_id, install_data_index,
+                UpdateService::Priority::kForeground,
                 same_version_update_allowed
                     ? UpdateService::PolicySameVersionUpdate::kAllowed
                     : UpdateService::PolicySameVersionUpdate::kNotAllowed,
@@ -324,7 +326,8 @@
                     task_runner, observer));
           },
           com_server->update_service(), task_runner, base::WideToUTF8(app_id),
-          same_version_update_allowed, observer_local));
+          base::WideToUTF8(install_data_index), same_version_update_allowed,
+          observer_local));
 
   // Always return S_OK from this function. Errors must be reported using the
   // observer interface.
diff --git a/chrome/updater/app/server/win/com_classes.h b/chrome/updater/app/server/win/com_classes.h
index 033e1b9..423ad33 100644
--- a/chrome/updater/app/server/win/com_classes.h
+++ b/chrome/updater/app/server/win/com_classes.h
@@ -98,6 +98,7 @@
                              IUpdaterRegisterAppCallback* callback) override;
   IFACEMETHODIMP RunPeriodicTasks(IUpdaterCallback* callback) override;
   IFACEMETHODIMP Update(const wchar_t* app_id,
+                        const wchar_t* install_data_index,
                         BOOL same_version_update_allowed,
                         IUpdaterObserver* observer) override;
   IFACEMETHODIMP UpdateAll(IUpdaterObserver* observer) override;
diff --git a/chrome/updater/app/server/win/com_classes_legacy.cc b/chrome/updater/app/server/win/com_classes_legacy.cc
index 347c5681..69ff512 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy.cc
@@ -161,7 +161,7 @@
           [](scoped_refptr<UpdateService> update_service,
              LegacyOnDemandImplPtr obj) {
             update_service->Update(
-                obj->app_id(), UpdateService::Priority::kForeground,
+                obj->app_id(), "", UpdateService::Priority::kForeground,
                 UpdateService::PolicySameVersionUpdate::kNotAllowed,
                 base::BindRepeating(
                     [](LegacyOnDemandImplPtr obj,
diff --git a/chrome/updater/app/server/win/updater_idl.template b/chrome/updater/app/server/win/updater_idl.template
index 561dcd2..eabec2a 100644
--- a/chrome/updater/app/server/win/updater_idl.template
+++ b/chrome/updater/app/server/win/updater_idl.template
@@ -92,6 +92,7 @@
                       [in] IUpdaterRegisterAppCallback* callback);
   HRESULT RunPeriodicTasks([in] IUpdaterCallback* callback);
   HRESULT Update([in, string] const WCHAR* app_id,
+                 [in, string] const WCHAR* install_data_index,
                  [in] BOOL same_version_update_allowed,
                  [in] IUpdaterObserver* observer);
   HRESULT UpdateAll([in] IUpdaterObserver * observer);
diff --git a/chrome/updater/installer.cc b/chrome/updater/installer.cc
index e18e7771..de32b95 100644
--- a/chrome/updater/installer.cc
+++ b/chrome/updater/installer.cc
@@ -52,6 +52,7 @@
 
 Installer::Installer(
     const std::string& app_id,
+    const std::string& install_data_index,
     const std::string& target_channel,
     const std::string& target_version_prefix,
     bool rollback_allowed,
@@ -61,6 +62,7 @@
     crx_file::VerifierFormat crx_verifier_format)
     : updater_scope_(GetUpdaterScope()),
       app_id_(app_id),
+      install_data_index_(install_data_index),
       rollback_allowed_(rollback_allowed),
       target_channel_(target_channel),
       target_version_prefix_(target_version_prefix),
@@ -96,6 +98,7 @@
   component.requires_network_encryption = false;
   component.crx_format_requirement = crx_verifier_format_;
   component.app_id = app_id_;
+  component.install_data_index = install_data_index_;
   component.ap = ap_;
   component.brand = persisted_data_->GetBrandCode(app_id_);
   component.name = app_id_;
diff --git a/chrome/updater/installer.h b/chrome/updater/installer.h
index 129fec46..6cf30ad3 100644
--- a/chrome/updater/installer.h
+++ b/chrome/updater/installer.h
@@ -40,6 +40,7 @@
 class Installer final : public update_client::CrxInstaller {
  public:
   Installer(const std::string& app_id,
+            const std::string& install_data_index,
             const std::string& target_channel,
             const std::string& target_version_prefix,
             bool rollback_allowed,
@@ -110,6 +111,7 @@
   UpdaterScope updater_scope_;
 
   const std::string app_id_;
+  const std::string install_data_index_;
   const bool rollback_allowed_;
   const std::string target_channel_;
   const std::string target_version_prefix_;
diff --git a/chrome/updater/mac/keystone/ksadmin.mm b/chrome/updater/mac/keystone/ksadmin.mm
index eccfcbe..eb4729a 100644
--- a/chrome/updater/mac/keystone/ksadmin.mm
+++ b/chrome/updater/mac/keystone/ksadmin.mm
@@ -396,7 +396,7 @@
   }
 
   ServiceProxy(scope)->Update(
-      app_id,
+      app_id, GetInstallDataIndexFromAppArgs(app_id),
       HasSwitch(kCommandUserInitiated) ? UpdateService::Priority::kForeground
                                        : UpdateService::Priority::kBackground,
       UpdateService::PolicySameVersionUpdate::kNotAllowed,
@@ -590,7 +590,7 @@
   base::CommandLine::Init(argc, argv);
   std::map<std::string, std::string> command_line =
       ParseCommandLine(argc, argv);
-  updater::InitLogging(Scope(command_line), FILE_PATH_LITERAL("updater.log"));
+  updater::InitLogging(Scope(command_line));
   base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
   return base::MakeRefCounted<KSAdminApp>(command_line)->Run();
 }
diff --git a/chrome/updater/mac/keystone/ksinstall.mm b/chrome/updater/mac/keystone/ksinstall.mm
index 8d10df5..1790a60 100644
--- a/chrome/updater/mac/keystone/ksinstall.mm
+++ b/chrome/updater/mac/keystone/ksinstall.mm
@@ -111,7 +111,7 @@
   base::AtExitManager exit_manager;
 
   base::CommandLine::Init(argc, argv);
-  updater::InitLogging(GetUpdaterScope(), FILE_PATH_LITERAL("updater.log"));
+  updater::InitLogging(GetUpdaterScope());
 
   base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
   return MakeKSInstallApp(argc, argv)->Run();
diff --git a/chrome/updater/mac/update_service_proxy.h b/chrome/updater/mac/update_service_proxy.h
index c6406d8..d408f40 100644
--- a/chrome/updater/mac/update_service_proxy.h
+++ b/chrome/updater/mac/update_service_proxy.h
@@ -48,6 +48,7 @@
   void RunPeriodicTasks(base::OnceClosure callback) override;
   void UpdateAll(StateChangeCallback state_update, Callback callback) override;
   void Update(const std::string& app_id,
+              const std::string& install_data_index,
               Priority priority,
               PolicySameVersionUpdate policy_same_version_update,
               StateChangeCallback state_update,
diff --git a/chrome/updater/mac/update_service_proxy.mm b/chrome/updater/mac/update_service_proxy.mm
index 9bfb5bea..77cece90 100644
--- a/chrome/updater/mac/update_service_proxy.mm
+++ b/chrome/updater/mac/update_service_proxy.mm
@@ -151,6 +151,7 @@
 }
 
 - (void)checkForUpdateWithAppID:(NSString* _Nonnull)appID
+               installDataIndex:(NSString* _Nullable)installDataIndex
                        priority:(CRUPriorityWrapper* _Nonnull)priority
         policySameVersionUpdate:
             (CRUPolicySameVersionUpdateWrapper* _Nonnull)policySameVersionUpdate
@@ -165,6 +166,7 @@
 
   [[_updateCheckXPCConnection remoteObjectProxyWithErrorHandler:errorHandler]
       checkForUpdateWithAppID:appID
+             installDataIndex:installDataIndex
                      priority:priority
       policySameVersionUpdate:policySameVersionUpdate
                   updateState:updateState
@@ -276,6 +278,7 @@
 
 void UpdateServiceProxy::Update(
     const std::string& app_id,
+    const std::string& install_data_index,
     UpdateService::Priority priority,
     PolicySameVersionUpdate policy_same_version_update,
     StateChangeCallback state_update,
@@ -301,6 +304,7 @@
                      callbackRunner:callback_runner_]);
 
   [client_ checkForUpdateWithAppID:SysUTF8ToNSString(app_id)
+                  installDataIndex:SysUTF8ToNSString(install_data_index)
                           priority:priorityWrapper.get()
            policySameVersionUpdate:policySameVersionUpdateWrapper.get()
                        updateState:stateObserver.get()
diff --git a/chrome/updater/mac/update_service_proxy_test.mm b/chrome/updater/mac/update_service_proxy_test.mm
index 863dace..235c5d2 100644
--- a/chrome/updater/mac/update_service_proxy_test.mm
+++ b/chrome/updater/mac/update_service_proxy_test.mm
@@ -497,6 +497,7 @@
   OCMockObjectCapturer<CRUUpdateStateObserver> update_state_observer_capturer;
 
   const std::string test_app_id("test_app_id");
+  const std::string test_install_data_index("test_install_data_index");
   base::scoped_nsobject<CRUPriorityWrapper> wrapped_priority(
       [[CRUPriorityWrapper alloc]
           initWithPriority:UpdateService::Priority::kForeground]);
@@ -522,6 +523,8 @@
   auto* state_change_engine_ptr = &state_change_engine;
   OCMExpect([mock_remote_object
                 checkForUpdateWithAppID:base::SysUTF8ToNSString(test_app_id)
+                       installDataIndex:base::SysUTF8ToNSString(
+                                            test_install_data_index)
                                priority:wrapped_priority.get()
                 policySameVersionUpdate:wrapped_policySameVersionUpdate.get()
                             updateState:update_state_observer_capturer.Capture()
@@ -537,7 +540,8 @@
 
   base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindLambdaForTesting([this, &state_change_engine]() {
-        service_->Update("test_app_id", UpdateService::Priority::kForeground,
+        service_->Update("test_app_id", "test_install_data_index",
+                         UpdateService::Priority::kForeground,
                          UpdateService::PolicySameVersionUpdate::kNotAllowed,
                          state_change_engine.Watch(),
                          base::BindLambdaForTesting(
diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h
index 55402d3..da89160 100644
--- a/chrome/updater/test/integration_test_commands.h
+++ b/chrome/updater/test/integration_test_commands.h
@@ -40,6 +40,7 @@
   virtual void ExpectSelfUpdateSequence(ScopedServer* test_server) const = 0;
   virtual void ExpectUpdateSequence(ScopedServer* test_server,
                                     const std::string& app_id,
+                                    const std::string& install_data_index,
                                     const base::Version& from_version,
                                     const base::Version& to_version) const = 0;
   virtual void ExpectVersionActive(const std::string& version) const = 0;
@@ -59,7 +60,8 @@
                                 const base::Version& version) const = 0;
   virtual void RunWake(int exit_code) const = 0;
   virtual void RunWakeActive(int exit_code) const = 0;
-  virtual void Update(const std::string& app_id) const = 0;
+  virtual void Update(const std::string& app_id,
+                      const std::string& install_data_index) const = 0;
   virtual void UpdateAll() const = 0;
   virtual void PrintLog() const = 0;
   virtual base::FilePath GetDifferentUserPath() const = 0;
@@ -77,6 +79,7 @@
 #endif  // BUILDFLAG(IS_WIN)
   virtual void StressUpdateService() const = 0;
   virtual void CallServiceUpdate(const std::string& app_id,
+                                 const std::string& install_data_index,
                                  UpdateService::PolicySameVersionUpdate
                                      policy_same_version_update) const = 0;
 
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index c894a6c..2d69678 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -74,10 +74,12 @@
 
   void ExpectUpdateSequence(ScopedServer* test_server,
                             const std::string& app_id,
+                            const std::string& install_data_index,
                             const base::Version& from_version,
                             const base::Version& to_version) const override {
     updater::test::ExpectUpdateSequence(updater_scope_, test_server, app_id,
-                                        from_version, to_version);
+                                        install_data_index, from_version,
+                                        to_version);
   }
 
   void ExpectVersionActive(const std::string& version) const override {
@@ -151,8 +153,10 @@
                {Param("exit_code", base::NumberToString(expected_exit_code))});
   }
 
-  void Update(const std::string& app_id) const override {
-    RunCommand("update", {Param("app_id", app_id)});
+  void Update(const std::string& app_id,
+              const std::string& install_data_index) const override {
+    RunCommand("update", {Param("app_id", app_id),
+                          Param("install_data_index", install_data_index)});
   }
 
   void UpdateAll() const override { RunCommand("update_all", {}); }
@@ -213,10 +217,12 @@
   }
 
   void CallServiceUpdate(const std::string& app_id,
+                         const std::string& install_data_index,
                          UpdateService::PolicySameVersionUpdate
                              policy_same_version_update) const override {
     RunCommand("call_service_update",
                {Param("app_id", app_id),
+                Param("install_data_index", install_data_index),
                 Param("same_version_update_allowed",
                       policy_same_version_update ==
                               UpdateService::PolicySameVersionUpdate::kAllowed
diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc
index 0dbc0124..acbd9b2 100644
--- a/chrome/updater/test/integration_test_commands_user.cc
+++ b/chrome/updater/test/integration_test_commands_user.cc
@@ -67,10 +67,12 @@
 
   void ExpectUpdateSequence(ScopedServer* test_server,
                             const std::string& app_id,
+                            const std::string& install_data_index,
                             const base::Version& from_version,
                             const base::Version& to_version) const override {
     updater::test::ExpectUpdateSequence(updater_scope_, test_server, app_id,
-                                        from_version, to_version);
+                                        install_data_index, from_version,
+                                        to_version);
   }
 
   void ExpectVersionActive(const std::string& version) const override {
@@ -139,8 +141,9 @@
     updater::test::RunWakeActive(updater_scope_, exit_code);
   }
 
-  void Update(const std::string& app_id) const override {
-    updater::test::Update(updater_scope_, app_id);
+  void Update(const std::string& app_id,
+              const std::string& install_data_index) const override {
+    updater::test::Update(updater_scope_, app_id, install_data_index);
   }
 
   void UpdateAll() const override { updater::test::UpdateAll(updater_scope_); }
@@ -193,10 +196,11 @@
   }
 
   void CallServiceUpdate(const std::string& app_id,
+                         const std::string& install_data_index,
                          UpdateService::PolicySameVersionUpdate
                              policy_same_version_update) const override {
     updater::test::CallServiceUpdate(
-        updater_scope_, app_id,
+        updater_scope_, app_id, install_data_index,
         policy_same_version_update ==
             UpdateService::PolicySameVersionUpdate::kAllowed);
   }
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 847188e..3400339 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -221,7 +221,10 @@
     test_commands_->RunWakeActive(exit_code);
   }
 
-  void Update(const std::string& app_id) { test_commands_->Update(app_id); }
+  void Update(const std::string& app_id,
+              const std::string& install_data_index) {
+    test_commands_->Update(app_id, install_data_index);
+  }
 
   void UpdateAll() { test_commands_->UpdateAll(); }
 
@@ -245,10 +248,11 @@
 
   void ExpectUpdateSequence(ScopedServer* test_server,
                             const std::string& app_id,
+                            const std::string& install_data_index,
                             const base::Version& from_version,
                             const base::Version& to_version) {
-    test_commands_->ExpectUpdateSequence(test_server, app_id, from_version,
-                                         to_version);
+    test_commands_->ExpectUpdateSequence(
+        test_server, app_id, install_data_index, from_version, to_version);
   }
 
   void ExpectSelfUpdateSequence(ScopedServer* test_server) {
@@ -269,8 +273,10 @@
 
   void CallServiceUpdate(
       const std::string& app_id,
+      const std::string& install_data_index,
       UpdateService::PolicySameVersionUpdate policy_same_version_update) {
-    test_commands_->CallServiceUpdate(app_id, policy_same_version_update);
+    test_commands_->CallServiceUpdate(app_id, install_data_index,
+                                      policy_same_version_update);
   }
 
   void SetupFakeLegacyUpdaterData() {
@@ -346,8 +352,8 @@
   ExpectVersionNotActive(kUpdaterVersion);
 
   ExpectRegistrationEvent(&test_server, kQualificationAppId);
-  ExpectUpdateSequence(&test_server, kQualificationAppId, base::Version("0.1"),
-                       base::Version("0.2"));
+  ExpectUpdateSequence(&test_server, kQualificationAppId, "",
+                       base::Version("0.1"), base::Version("0.2"));
 
   RunWake(0);
   WaitForUpdaterExit();
@@ -372,7 +378,7 @@
   Install();
 
   base::Version next_version(base::StringPrintf("%s1", kUpdaterVersion));
-  ExpectUpdateSequence(&test_server, kUpdaterAppId,
+  ExpectUpdateSequence(&test_server, kUpdaterAppId, "",
                        base::Version(kUpdaterVersion), next_version);
 
   RunWake(0);
@@ -433,12 +439,13 @@
   ExpectRegistrationEvent(&test_server, kAppId);
   InstallApp(kAppId);
   base::Version v1("1");
-  ExpectUpdateSequence(&test_server, kAppId, base::Version("0.1"), v1);
+  ExpectUpdateSequence(&test_server, kAppId, "", base::Version("0.1"), v1);
   RunWake(0);
 
   base::Version v2("2");
-  ExpectUpdateSequence(&test_server, kAppId, v1, v2);
-  Update(kAppId);
+  const std::string kInstallDataIndex("test_install_data_index");
+  ExpectUpdateSequence(&test_server, kAppId, kInstallDataIndex, v1, v2);
+  Update(kAppId, kInstallDataIndex);
   WaitForUpdaterExit();
   ExpectAppVersion(kAppId, v2);
   ExpectLastChecked();
@@ -489,7 +496,7 @@
   ExpectNoUpdateSequence(&test_server, kAppId);
   ExpectLegacyUpdate3WebSucceeds(kAppId, STATE_NO_UPDATE, S_OK);
 
-  ExpectUpdateSequence(&test_server, kAppId, base::Version("0.1"),
+  ExpectUpdateSequence(&test_server, kAppId, "", base::Version("0.1"),
                        base::Version("0.2"));
   ExpectLegacyUpdate3WebSucceeds(kAppId, STATE_INSTALL_COMPLETE, S_OK);
 
@@ -637,8 +644,8 @@
 
   // Qualify the new instance.
   ExpectRegistrationEvent(&test_server, kQualificationAppId);
-  ExpectUpdateSequence(&test_server, kQualificationAppId, base::Version("0.1"),
-                       base::Version("0.2"));
+  ExpectUpdateSequence(&test_server, kQualificationAppId, "",
+                       base::Version("0.1"), base::Version("0.2"));
   RunWake(0);
   WaitForUpdaterExit();
 
@@ -689,17 +696,60 @@
           RequestMatcherRegex,
           R"(.*"updatecheck":{"sameversionupdate":true},"version":"0.1"}.*)")},
       response);
-  CallServiceUpdate(app_id, UpdateService::PolicySameVersionUpdate::kAllowed);
+  CallServiceUpdate(app_id, "",
+                    UpdateService::PolicySameVersionUpdate::kAllowed);
 
   test_server.ExpectOnce(
       {base::BindRepeating(RequestMatcherRegex,
                            R"(.*"updatecheck":{},"version":"0.1"}.*)")},
       response);
-  CallServiceUpdate(app_id,
+  CallServiceUpdate(app_id, "",
                     UpdateService::PolicySameVersionUpdate::kNotAllowed);
   Uninstall();
 }
 
+TEST_F(IntegrationTest, InstallDataIndex) {
+  ScopedServer test_server(test_commands_);
+  ExpectRegistrationEvent(&test_server, kUpdaterAppId);
+  Install();
+  ExpectInstalled();
+
+  const std::string app_id = "test-appid";
+  const std::string install_data_index = "test-install-data-index";
+
+  ExpectRegistrationEvent(&test_server, app_id);
+  InstallApp(app_id);
+
+  const std::string response = base::StringPrintf(
+      ")]}'\n"
+      R"({"response":{)"
+      R"(  "protocol":"3.1",)"
+      R"(  "app":[)"
+      R"(    {)"
+      R"(      "appid":"%s",)"
+      R"(      "status":"ok",)"
+      R"(      "updatecheck":{)"
+      R"(        "status":"noupdate")"
+      R"(      })"
+      R"(    })"
+      R"(  ])"
+      R"(}})",
+      app_id.c_str());
+
+  test_server.ExpectOnce(
+      {base::BindRepeating(
+          RequestMatcherRegex,
+          base::StringPrintf(
+              R"(.*"data":\[{"index":"%s","name":"install"}],.*)",
+              install_data_index.c_str()))},
+      response);
+
+  CallServiceUpdate(app_id, install_data_index,
+                    UpdateService::PolicySameVersionUpdate::kAllowed);
+
+  Uninstall();
+}
+
 TEST_F(IntegrationTest, MigrateLegacyUpdater) {
   SetupFakeLegacyUpdaterData();
   Install();
diff --git a/chrome/updater/test/integration_tests_helper.cc b/chrome/updater/test/integration_tests_helper.cc
index 98cd3c5..45f0a734 100644
--- a/chrome/updater/test/integration_tests_helper.cc
+++ b/chrome/updater/test/integration_tests_helper.cc
@@ -232,7 +232,9 @@
     {"run_wake", WithSwitch("exit_code", WithSystemScope(Wrap(&RunWake)))},
     {"run_wake_active",
      WithSwitch("exit_code", WithSystemScope(Wrap(&RunWakeActive)))},
-    {"update", WithSwitch("app_id", WithSystemScope(Wrap(&Update)))},
+    {"update",
+     WithSwitch("install_data_index",
+                (WithSwitch("app_id", WithSystemScope(Wrap(&Update)))))},
     {"update_all", WithSystemScope(Wrap(&UpdateAll))},
     {"install_app", WithSwitch("app_id", WithSystemScope(Wrap(&InstallApp)))},
     {"uninstall_app",
@@ -252,9 +254,10 @@
     {"stress_update_service", WithSystemScope(Wrap(&StressUpdateService))},
     {"uninstall", WithSystemScope(Wrap(&Uninstall))},
     {"call_service_update",
-     WithSwitch(
-         "same_version_update_allowed",
-         WithSwitch("app_id", WithSystemScope(Wrap(&CallServiceUpdate))))},
+     WithSwitch("same_version_update_allowed",
+                WithSwitch("install_data_index",
+                           WithSwitch("app_id", WithSystemScope(Wrap(
+                                                    &CallServiceUpdate)))))},
     {"setup_fake_legacy_updater_data",
      WithSystemScope(Wrap(&SetupFakeLegacyUpdaterData))},
     {"expect_legacy_updater_data_migrated",
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index 7b5e786..f94a477 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -90,6 +90,7 @@
 }
 
 std::string GetUpdateResponse(const std::string& app_id,
+                              const std::string& install_data_index,
                               const std::string& codebase,
                               const base::Version& version,
                               const base::FilePath& update_file,
@@ -103,6 +104,7 @@
       R"(    {)"
       R"(      "appid":"%s",)"
       R"(      "status":"ok",)"
+      R"(%s)"
       R"(      "updatecheck":{)"
       R"(        "status":"ok",)"
       R"(        "urls":{"url":[{"codebase":"%s"}]},)"
@@ -120,9 +122,16 @@
       R"(    })"
       R"(  ])"
       R"(}})",
-      app_id.c_str(), codebase.c_str(), version.GetString().c_str(),
-      run_action.c_str(), arguments.c_str(),
-      update_file.BaseName().AsUTF8Unsafe().c_str(),
+      app_id.c_str(),
+      !install_data_index.empty()
+          ? base::StringPrintf(
+                R"(     "data":[{ "status":"ok", "name":"install", )"
+                R"("index":"%s", "#text":"%s_text" }],)",
+                install_data_index.c_str(), install_data_index.c_str())
+                .c_str()
+          : "",
+      codebase.c_str(), version.GetString().c_str(), run_action.c_str(),
+      arguments.c_str(), update_file.BaseName().AsUTF8Unsafe().c_str(),
       GetHashHex(update_file).c_str());
 }
 
@@ -285,11 +294,13 @@
   EXPECT_EQ(exit_code, expected_exit_code);
 }
 
-void Update(UpdaterScope scope, const std::string& app_id) {
+void Update(UpdaterScope scope,
+            const std::string& app_id,
+            const std::string& install_data_index) {
   scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(scope);
   base::RunLoop loop;
   update_service->Update(
-      app_id, UpdateService::Priority::kForeground,
+      app_id, install_data_index, UpdateService::Priority::kForeground,
       UpdateService::PolicySameVersionUpdate::kNotAllowed, base::DoNothing(),
       base::BindOnce(base::BindLambdaForTesting(
           [&loop](UpdateService::Result result_unused) { loop.Quit(); })));
@@ -440,7 +451,7 @@
            base::StringPrintf(R"(.*"appid":"%s".*)", kUpdaterAppId)),
        GetScopePredicate(scope)},
       GetUpdateResponse(
-          kUpdaterAppId, test_server->base_url().spec(),
+          kUpdaterAppId, "", test_server->base_url().spec(),
           base::Version(kUpdaterVersion), crx_path, kSelfUpdateCRXRun,
           base::StrCat(
               {"--update", scope == UpdaterScope::kSystem ? " --system" : "",
@@ -467,6 +478,7 @@
 void ExpectUpdateSequence(UpdaterScope scope,
                           ScopedServer* test_server,
                           const std::string& app_id,
+                          const std::string& install_data_index,
                           const base::Version& from_version,
                           const base::Version& to_version) {
   base::FilePath test_data_path;
@@ -480,9 +492,20 @@
       {base::BindRepeating(
            RequestMatcherRegex,
            base::StringPrintf(R"(.*"appid":"%s".*)", app_id.c_str())),
+       base::BindRepeating(
+           RequestMatcherRegex,
+           base::StringPrintf(
+               R"(.*%s)",
+               !install_data_index.empty()
+                   ? base::StringPrintf(
+                         R"("data":\[{"index":"%s","name":"install"}],.*)",
+                         install_data_index.c_str())
+                         .c_str()
+                   : "")),
        GetScopePredicate(scope)},
-      GetUpdateResponse(app_id, test_server->base_url().spec(), to_version,
-                        crx_path, kDoNothingCRXRun, {}));
+      GetUpdateResponse(app_id, install_data_index,
+                        test_server->base_url().spec(), to_version, crx_path,
+                        kDoNothingCRXRun, {}));
 
   // Second request: update download.
   std::string crx_bytes;
@@ -572,6 +595,7 @@
 
 void CallServiceUpdate(UpdaterScope updater_scope,
                        const std::string& app_id,
+                       const std::string& install_data_index,
                        bool same_version_update_allowed) {
   UpdateService::PolicySameVersionUpdate policy_same_version_update =
       same_version_update_allowed
@@ -583,7 +607,8 @@
 
   base::RunLoop loop;
   service_proxy->Update(
-      app_id, UpdateService::Priority::kForeground, policy_same_version_update,
+      app_id, install_data_index, UpdateService::Priority::kForeground,
+      policy_same_version_update,
       base::BindLambdaForTesting([](const UpdateService::UpdateState&) {}),
       base::BindLambdaForTesting([&](UpdateService::Result result) {
         EXPECT_EQ(result, UpdateService::Result::kSuccess);
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index 9c6d619..a550605 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -88,7 +88,9 @@
 void RunWakeActive(UpdaterScope scope, int exit_code);
 
 // Invokes the active instance's UpdateService::Update (via RPC) for an app.
-void Update(UpdaterScope scope, const std::string& app_id);
+void Update(UpdaterScope scope,
+            const std::string& app_id,
+            const std::string& install_data_index);
 
 // Invokes the active instance's UpdateService::UpdateAll (via RPC).
 void UpdateAll(UpdaterScope scope);
@@ -184,6 +186,7 @@
 void ExpectUpdateSequence(UpdaterScope scope,
                           ScopedServer* test_server,
                           const std::string& app_id,
+                          const std::string& install_data_index,
                           const base::Version& from_version,
                           const base::Version& to_version);
 
@@ -191,6 +194,7 @@
 
 void CallServiceUpdate(UpdaterScope updater_scope,
                        const std::string& app_id,
+                       const std::string& install_data_index,
                        bool same_version_update_allowed);
 
 void SetupFakeLegacyUpdaterData(UpdaterScope scope);
diff --git a/chrome/updater/update_service.h b/chrome/updater/update_service.h
index fbe2019..e7c1c40 100644
--- a/chrome/updater/update_service.h
+++ b/chrome/updater/update_service.h
@@ -238,6 +238,7 @@
   // |callback| arg:
   //    Result: the final result from the update engine.
   virtual void Update(const std::string& app_id,
+                      const std::string& install_data_index,
                       Priority priority,
                       PolicySameVersionUpdate policy_same_version_update,
                       StateChangeCallback state_update,
diff --git a/chrome/updater/update_service_impl.cc b/chrome/updater/update_service_impl.cc
index 53e005f0..4ba2ee9 100644
--- a/chrome/updater/update_service_impl.cc
+++ b/chrome/updater/update_service_impl.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/updater/update_service_impl.h"
 
+#include <algorithm>
+#include <iterator>
 #include <string>
 #include <utility>
 #include <vector>
@@ -13,6 +15,7 @@
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/containers/contains.h"
+#include "base/containers/flat_map.h"
 #include "base/containers/queue.h"
 #include "base/logging.h"
 #include "base/run_loop.h"
@@ -148,6 +151,7 @@
 std::vector<absl::optional<update_client::CrxComponent>> GetComponents(
     scoped_refptr<Configurator> config,
     scoped_refptr<PersistedData> persisted_data,
+    const AppInstallDataIndex& app_install_data_index,
     bool foreground,
     bool update_blocked,
     UpdateService::PolicySameVersionUpdate policy_same_version_update,
@@ -160,6 +164,10 @@
     components.push_back(
         base::MakeRefCounted<Installer>(
             id,
+            [&app_install_data_index, &id]() {
+              auto it = app_install_data_index.find(id);
+              return it != app_install_data_index.end() ? it->second : "";
+            }(),
             [&config, &id]() {
               std::string component_channel;
               return config->GetPolicyService()->GetTargetChannel(
@@ -357,12 +365,15 @@
                 std::move(callback).Run(result);
               },
               std::move(callback), persisted_data_),
-          app_ids, priority,
-          UpdateService::PolicySameVersionUpdate::kNotAllowed));
+          base::MakeFlatMap<std::string, std::string>(
+              app_ids, {},
+              [](const auto& app_id) { return std::make_pair(app_id, ""); }),
+          priority, UpdateService::PolicySameVersionUpdate::kNotAllowed));
 }
 
 void UpdateServiceImpl::Update(
     const std::string& app_id,
+    const std::string& install_data_index,
     Priority priority,
     PolicySameVersionUpdate policy_same_version_update,
     StateChangeCallback state_update,
@@ -378,12 +389,13 @@
     return;
   }
 
-  std::vector<std::string> ids = {app_id};
   ShouldBlockUpdateForMeteredNetwork(
       priority,
-      base::BindOnce(&UpdateServiceImpl::OnShouldBlockUpdateForMeteredNetwork,
-                     this, state_update, std::move(callback), ids, priority,
-                     policy_same_version_update));
+      base::BindOnce(
+          &UpdateServiceImpl::OnShouldBlockUpdateForMeteredNetwork, this,
+          state_update, std::move(callback),
+          AppInstallDataIndex({std::make_pair(app_id, install_data_index)}),
+          priority, policy_same_version_update));
 }
 
 bool UpdateServiceImpl::IsUpdateDisabledByPolicy(
@@ -441,7 +453,7 @@
 void UpdateServiceImpl::OnShouldBlockUpdateForMeteredNetwork(
     StateChangeCallback state_update,
     Callback callback,
-    const std::vector<std::string>& ids,
+    const AppInstallDataIndex& app_install_data_index,
     Priority priority,
     PolicySameVersionUpdate policy_same_version_update,
     bool update_blocked) {
@@ -450,9 +462,19 @@
   main_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &update_client::UpdateClient::Update, update_client_, ids,
-          base::BindOnce(&GetComponents, config_, persisted_data_, false,
-                         update_blocked, policy_same_version_update),
+          &update_client::UpdateClient::Update, update_client_,
+          [&app_install_data_index]() {
+            std::vector<std::string> app_ids;
+            app_ids.reserve(app_install_data_index.size());
+            std::transform(app_install_data_index.begin(),
+                           app_install_data_index.end(),
+                           std::back_inserter(app_ids),
+                           [](const auto& param) { return param.first; });
+            return app_ids;
+          }(),
+          base::BindOnce(&GetComponents, config_, persisted_data_,
+                         app_install_data_index, false, update_blocked,
+                         policy_same_version_update),
           MakeUpdateClientCrxStateChangeCallback(config_, state_update),
           priority == Priority::kForeground,
           MakeUpdateClientCallback(std::move(callback))));
diff --git a/chrome/updater/update_service_impl.h b/chrome/updater/update_service_impl.h
index d47c509..3c2a726 100644
--- a/chrome/updater/update_service_impl.h
+++ b/chrome/updater/update_service_impl.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
 #include "base/containers/queue.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
@@ -29,6 +30,8 @@
 struct RegistrationRequest;
 struct RegistrationResponse;
 
+using AppInstallDataIndex = base::flat_map<std::string, std::string>;
+
 // All functions and callbacks must be called on the same sequence.
 class UpdateServiceImpl : public UpdateService {
  public:
@@ -45,6 +48,7 @@
   void RunPeriodicTasks(base::OnceClosure callback) override;
   void UpdateAll(StateChangeCallback state_update, Callback callback) override;
   void Update(const std::string& app_id,
+              const std::string& install_data_index,
               Priority priority,
               PolicySameVersionUpdate policy_same_version_update,
               StateChangeCallback state_update,
@@ -76,7 +80,7 @@
   void OnShouldBlockUpdateForMeteredNetwork(
       StateChangeCallback state_update,
       Callback callback,
-      const std::vector<std::string>& ids,
+      const AppInstallDataIndex& app_install_data_index,
       Priority priority,
       PolicySameVersionUpdate policy_same_version_update,
       bool update_blocked);
diff --git a/chrome/updater/update_service_impl_inactive.cc b/chrome/updater/update_service_impl_inactive.cc
index 30ef919..e6854ad 100644
--- a/chrome/updater/update_service_impl_inactive.cc
+++ b/chrome/updater/update_service_impl_inactive.cc
@@ -56,6 +56,7 @@
   }
 
   void Update(const std::string& app_id,
+              const std::string& install_data_index,
               Priority /*priority*/,
               PolicySameVersionUpdate /*policy_same_version_update*/,
               StateChangeCallback /*state_update*/,
diff --git a/chrome/updater/update_service_internal_impl_qualifying.cc b/chrome/updater/update_service_internal_impl_qualifying.cc
index 23773c5..2cb45f59 100644
--- a/chrome/updater/update_service_internal_impl_qualifying.cc
+++ b/chrome/updater/update_service_internal_impl_qualifying.cc
@@ -73,12 +73,13 @@
     // an `Update` task for `kQualificationAppId`.
     DVLOG(2) << "RegistrationResponse: " << response.status_code;
     base::MakeRefCounted<CheckForUpdatesTask>(
-        config_, base::BindOnce(
-                     &UpdateServiceImpl::Update,
-                     base::MakeRefCounted<UpdateServiceImpl>(config_),
-                     kQualificationAppId, UpdateService::Priority::kBackground,
-                     UpdateService::PolicySameVersionUpdate::kNotAllowed,
-                     base::DoNothing()))
+        config_,
+        base::BindOnce(&UpdateServiceImpl::Update,
+                       base::MakeRefCounted<UpdateServiceImpl>(config_),
+                       kQualificationAppId, "",
+                       UpdateService::Priority::kBackground,
+                       UpdateService::PolicySameVersionUpdate::kNotAllowed,
+                       base::DoNothing()))
         ->Run(base::BindOnce(
             &UpdateServiceInternalQualifyingImpl::QualificationDone, this,
             std::move(callback)));
diff --git a/chrome/updater/updater.cc b/chrome/updater/updater.cc
index 8a36b88..f68fbd69 100644
--- a/chrome/updater/updater.cc
+++ b/chrome/updater/updater.cc
@@ -61,25 +61,6 @@
 namespace updater {
 namespace {
 
-// The log file is created in DIR_LOCAL_APP_DATA or DIR_ROAMING_APP_DATA.
-void InitLogging(UpdaterScope updater_scope) {
-  logging::LoggingSettings settings;
-  const absl::optional<base::FilePath> log_dir =
-      GetBaseDirectory(updater_scope);
-  if (!log_dir) {
-    LOG(ERROR) << "Error getting base dir.";
-    return;
-  }
-  const auto log_file = log_dir->Append(FILE_PATH_LITERAL("updater.log"));
-  settings.log_file_path = log_file.value().c_str();
-  settings.logging_dest = logging::LOG_TO_ALL;
-  logging::InitLogging(settings);
-  logging::SetLogItems(true,    // enable_process_id
-                       true,    // enable_thread_id
-                       true,    // enable_timestamp
-                       false);  // enable_tickcount
-}
-
 void ReinitializeLoggingAfterCrashHandler(UpdaterScope updater_scope) {
   // Initializing the logging more than two times is not supported. In this
   // case, logging has been initialized once in the updater main, and the
diff --git a/chrome/updater/util.cc b/chrome/updater/util.cc
index 0ec579a..b0af7e1e 100644
--- a/chrome/updater/util.cc
+++ b/chrome/updater/util.cc
@@ -213,6 +213,11 @@
   return app_args ? app_args->ap : std::string();
 }
 
+std::string GetInstallDataIndexFromAppArgs(const std::string& app_id) {
+  const absl::optional<tagging::AppArgs> app_args = GetAppArgs(app_id);
+  return app_args ? app_args->install_data_index : std::string();
+}
+
 base::CommandLine MakeElevated(base::CommandLine command_line) {
 #if BUILDFLAG(IS_MAC)
   command_line.PrependWrapper("/usr/bin/sudo");
@@ -221,8 +226,7 @@
 }
 
 // The log file is created in DIR_LOCAL_APP_DATA or DIR_ROAMING_APP_DATA.
-void InitLogging(UpdaterScope updater_scope,
-                 const base::FilePath::StringType& filename) {
+void InitLogging(UpdaterScope updater_scope) {
   logging::LoggingSettings settings;
   const absl::optional<base::FilePath> log_dir =
       GetBaseDirectory(updater_scope);
@@ -230,7 +234,8 @@
     LOG(ERROR) << "Error getting base dir.";
     return;
   }
-  const base::FilePath log_file = log_dir->Append(filename);
+  const base::FilePath log_file =
+      log_dir->Append(FILE_PATH_LITERAL("updater.log"));
   settings.log_file_path = log_file.value().c_str();
   settings.logging_dest = logging::LOG_TO_ALL;
   logging::InitLogging(settings);
diff --git a/chrome/updater/util.h b/chrome/updater/util.h
index a252d15..8dc15e5 100644
--- a/chrome/updater/util.h
+++ b/chrome/updater/util.h
@@ -120,12 +120,13 @@
 // empty string if no tag or "ap" is specified.
 std::string GetAPFromAppArgs(const std::string& app_id);
 
+std::string GetInstallDataIndexFromAppArgs(const std::string& app_id);
+
 // Returns true if the user running the updater also owns the `path`.
 bool PathOwnedByUser(const base::FilePath& path);
 
 // Initializes logging for an executable.
-void InitLogging(UpdaterScope updater_scope,
-                 const base::FilePath::StringType& filename);
+void InitLogging(UpdaterScope updater_scope);
 
 // Wraps the 'command_line' to be executed in an elevated context.
 // On macOS this is done with 'sudo'.
diff --git a/chrome/updater/win/app_install_controller.cc b/chrome/updater/win/app_install_controller.cc
index a3e941a..4535dbb 100644
--- a/chrome/updater/win/app_install_controller.cc
+++ b/chrome/updater/win/app_install_controller.cc
@@ -500,7 +500,8 @@
             DCHECK(response.status_code == kRegistrationSuccess ||
                    response.status_code == kRegistrationAlreadyRegistered);
             self->update_service_->Update(
-                self->app_id_, UpdateService::Priority::kForeground,
+                self->app_id_, GetInstallDataIndexFromAppArgs(self->app_id_),
+                UpdateService::Priority::kForeground,
                 UpdateService::PolicySameVersionUpdate::kAllowed,
                 base::BindRepeating(&AppInstallControllerImpl::StateChange,
                                     self),
diff --git a/chrome/updater/win/installer/BUILD.gn b/chrome/updater/win/installer/BUILD.gn
index e9f55e0..d06a951b 100644
--- a/chrome/updater/win/installer/BUILD.gn
+++ b/chrome/updater/win/installer/BUILD.gn
@@ -93,7 +93,6 @@
     ]
 
     deps = invoker.archive_deps
-    deps += [ "//third_party/icu:icudata" ]
 
     if (is_component_build) {
       args += [ "--component_build=1" ]
diff --git a/chrome/updater/win/installer/updater.release b/chrome/updater/win/installer/updater.release
index 903b54b2..9d20d88 100644
--- a/chrome/updater/win/installer/updater.release
+++ b/chrome/updater/win/installer/updater.release
@@ -1,4 +1,3 @@
 [GENERAL]
-icudtl.dat: %(UpdaterDir)s\
 updater.exe: %(UpdaterDir)s\
 gen\chrome\updater\win\uninstall.cmd: %(UpdaterDir)s\
diff --git a/chrome/updater/win/installer/updater_test.release b/chrome/updater/win/installer/updater_test.release
index c3fd7918..980f5342 100644
--- a/chrome/updater/win/installer/updater_test.release
+++ b/chrome/updater/win/installer/updater_test.release
@@ -1,4 +1,3 @@
 [GENERAL]

-icudtl.dat: %(UpdaterDir)s\

 updater_test.exe: %(UpdaterDir)s\updater.exe

 gen\chrome\updater\win\uninstall.cmd: %(UpdaterDir)s\

diff --git a/chrome/updater/win/task_scheduler_unittest.cc b/chrome/updater/win/task_scheduler_unittest.cc
index 69b76d83..6ecbfa4 100644
--- a/chrome/updater/win/task_scheduler_unittest.cc
+++ b/chrome/updater/win/task_scheduler_unittest.cc
@@ -128,9 +128,7 @@
     return command_line;
   }
 
-  static void SetUpTestCase() {
-    InitLogging(GetTestScope(), FILE_PATH_LITERAL("updater.log"));
-  }
+  static void SetUpTestCase() { InitLogging(GetTestScope()); }
 
  protected:
   std::unique_ptr<TaskScheduler> task_scheduler_;
diff --git a/chrome/updater/win/test/test_process_main.cc b/chrome/updater/win/test/test_process_main.cc
index d70ed5e..b15f7c0a 100644
--- a/chrome/updater/win/test/test_process_main.cc
+++ b/chrome/updater/win/test/test_process_main.cc
@@ -27,8 +27,7 @@
   if (command_line->HasSwitch(updater::kEnableLoggingSwitch)) {
     InitLogging(command_line->HasSwitch(updater::kSystemSwitch)
                     ? updater::UpdaterScope::kSystem
-                    : updater::UpdaterScope::kUser,
-                FILE_PATH_LITERAL("updater.log"));
+                    : updater::UpdaterScope::kUser);
   }
 
   updater::NotifyInitializationDoneForTesting();
diff --git a/chrome/updater/win/update_service_proxy.cc b/chrome/updater/win/update_service_proxy.cc
index 55870ae..d3ae0821 100644
--- a/chrome/updater/win/update_service_proxy.cc
+++ b/chrome/updater/win/update_service_proxy.cc
@@ -414,6 +414,7 @@
 
 void UpdateServiceProxy::Update(
     const std::string& app_id,
+    const std::string& install_data_index,
     UpdateService::Priority /*priority*/,
     PolicySameVersionUpdate policy_same_version_update,
     StateChangeCallback state_update,
@@ -427,7 +428,7 @@
       base::BindOnce(&UpdateServiceProxy::InitializeSTA, this)
           .Then(base::BindOnce(
               &UpdateServiceProxy::UpdateOnSTA, this, app_id,
-              policy_same_version_update,
+              install_data_index, policy_same_version_update,
               base::BindPostTask(main_task_runner_, state_update),
               base::BindPostTask(main_task_runner_, std::move(callback)))));
 }
@@ -606,6 +607,7 @@
 
 void UpdateServiceProxy::UpdateOnSTA(
     const std::string& app_id,
+    const std::string& install_data_index,
     PolicySameVersionUpdate policy_same_version_update,
     StateChangeCallback state_update,
     Callback callback,
@@ -625,6 +627,7 @@
                                                         std::move(callback));
   HRESULT hr =
       updater->Update(base::UTF8ToWide(app_id).c_str(),
+                      base::UTF8ToWide(install_data_index).c_str(),
                       policy_same_version_update ==
                           UpdateService::PolicySameVersionUpdate::kAllowed,
                       observer.Get());
diff --git a/chrome/updater/win/update_service_proxy.h b/chrome/updater/win/update_service_proxy.h
index d491fb2..93ab95bd 100644
--- a/chrome/updater/win/update_service_proxy.h
+++ b/chrome/updater/win/update_service_proxy.h
@@ -54,6 +54,7 @@
   void RunPeriodicTasks(base::OnceClosure callback) override;
   void UpdateAll(StateChangeCallback state_update, Callback callback) override;
   void Update(const std::string& app_id,
+              const std::string& install_data_index,
               Priority priority,
               PolicySameVersionUpdate policy_same_version_update,
               StateChangeCallback state_update,
@@ -79,6 +80,7 @@
                       Callback callback,
                       HRESULT prev_hr);
   void UpdateOnSTA(const std::string& app_id,
+                   const std::string& install_data_index,
                    PolicySameVersionUpdate policy_same_version_update,
                    StateChangeCallback state_update,
                    Callback callback,
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index 48ad21b38..c644ec3 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-101-4918.0-1646651687-benchmark-101.0.4940.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-101-4928.0-1647252934-benchmark-101.0.4943.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt
index a1a8c7f..2635ae6 100644
--- a/chromeos/profiles/bigcore.afdo.newest.txt
+++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-bigcore-101-4896.16-1646649426-benchmark-101.0.4940.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-bigcore-101-4928.0-1647252364-benchmark-101.0.4943.0-r1-redacted.afdo.xz
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
index c780d9c..d8d44370 100644
--- a/components/browser_ui/styles/android/BUILD.gn
+++ b/components/browser_ui/styles/android/BUILD.gn
@@ -221,7 +221,6 @@
     "java/res/values-night/dimens.xml",
     "java/res/values-night/drawables.xml",
     "java/res/values-night/styles.xml",
-    "java/res/values-night/themes.xml",
     "java/res/values-night/values.xml",
     "java/res/values-sw600dp-v27/styles.xml",
     "java/res/values-v27/styles.xml",
diff --git a/components/browser_ui/styles/android/java/res/values/styles.xml b/components/browser_ui/styles/android/java/res/values/styles.xml
index cfa567d..ea1d824c 100644
--- a/components/browser_ui/styles/android/java/res/values/styles.xml
+++ b/components/browser_ui/styles/android/java/res/values/styles.xml
@@ -4,30 +4,6 @@
      found in the LICENSE file. -->
 
 <resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- This theme is used instead of android:style/Theme.NoDisplay so that it has the required
-         attributes in case the context ends up being used to inflate views. -->
-    <style name="Theme.BrowserUI.NoDisplay" parent="Theme.BrowserUI.DayNight">
-        <item name="android:windowBackground">@null</item>
-        <item name="android:windowContentOverlay">@null</item>
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:windowAnimationStyle">@null</item>
-        <item name="android:windowDisablePreview">true</item>
-        <item name="android:windowNoDisplay">true</item>
-    </style>
-
-    <!-- These themes are used instead of android:style/Theme.Translucent* so that they have the
-         required attributes in case the context ends up being used to inflate views. -->
-    <style name="Theme.BrowserUI.Translucent" parent="Theme.BrowserUI.DayNight">
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:colorBackgroundCacheHint">@null</item>
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:windowAnimationStyle">@android:style/Animation</item>
-    </style>
-    <style name="Theme.BrowserUI.Translucent.NoTitleBar">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowContentOverlay">@null</item>
-    </style>
-
     <!-- Control styles -->
     <style name="Widget.BrowserUI.CheckBox" parent="Widget.Material3.CompoundButton.CheckBox">
         <item name="buttonTint">@color/selection_control_button_tint</item>
diff --git a/components/browser_ui/styles/android/java/res/values/themes.xml b/components/browser_ui/styles/android/java/res/values/themes.xml
index 19762889..0b9e3ee 100644
--- a/components/browser_ui/styles/android/java/res/values/themes.xml
+++ b/components/browser_ui/styles/android/java/res/values/themes.xml
@@ -4,157 +4,6 @@
      found in the LICENSE file. -->
 
 <resources xmlns:tools="http://schemas.android.com/tools">
-    <!-- Full themes. -->
-
-    <!-- Colors should be mirrored by Theme.BrowserUI.DialogWhenLarge. -->
-    <style name="Base.V21.Theme.BrowserUI" parent="Theme.MaterialComponents.DayNight.NoActionBar">
-        <item name="dynamicColorThemeOverlay">@style/ThemeOverlay.BrowserUI.DynamicColors</item>
-
-        <!-- Color palettes -->
-        <item name="colorPrimary">@color/baseline_primary_600</item>
-        <item name="colorPrimaryDark">@android:color/black</item>
-        <item name="colorPrimaryInverse">@color/baseline_primary_200</item>
-        <item name="colorOnPrimary">@color/baseline_primary_0</item>
-        <item name="colorPrimaryContainer">@color/baseline_primary_100</item>
-        <item name="colorOnPrimaryContainer">@color/baseline_primary_900</item>
-        <item name="colorSecondaryContainer">@color/baseline_secondary_100</item>
-        <item name="colorOnSecondaryContainer">@color/baseline_secondary_900</item>
-        <item name="colorAccent">@macro/default_control_color_active</item>
-        <item name="android:colorBackground">@color/baseline_neutral_0</item>
-        <item name="colorOnBackground">@color/baseline_neutral_900</item>
-        <item name="colorSurface">@color/baseline_neutral_0</item>
-        <item name="colorOnSurface">@color/baseline_neutral_900</item>
-        <item name="colorSurfaceVariant">@color/baseline_neutral_variant_100</item>
-        <item name="colorOnSurfaceVariant">@color/baseline_neutral_variant_700</item>
-        <item name="colorOnSurfaceInverse">@color/baseline_neutral_50</item>
-        <item name="colorOutline">@color/baseline_neutral_variant_500</item>
-        <item name="colorError">@color/baseline_error_600</item>
-
-        <!-- Text colors-->
-        <item name="android:textColorPrimary">@color/default_text_color_list</item>
-        <item name="android:textColorSecondary">@color/default_text_color_secondary_list</item>
-        <item name="android:textColorLink">@macro/default_text_color_link</item>
-        <item name="android:textColorHighlight">@color/text_highlight_color</item>
-        <item name="android:textColorHint">@color/default_text_color_hint_list</item>
-
-        <!-- Widget colors: checkboxes, switches, buttons, etc. -->
-        <item name="colorControlNormal">@macro/default_control_color_normal</item>
-        <item name="colorControlActivated">@macro/default_control_color_active</item>
-        <item name="colorControlHighlight">@color/control_highlight_color</item>
-
-        <!-- Elevation overlays -->
-        <item name="elevationOverlayEnabled">true</item>
-        <item name="elevationOverlayColor">@color/baseline_neutral_600</item>
-        <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
-
-        <!-- Custom semantic names -->
-        <!-- Supports dynamic colors now. -->
-        <item name="default_bg_color_dynamic">?attr/colorSurface</item>
-        <item name="divider_line_bg_color_dynamic">?attr/colorSurfaceVariant</item>
-
-        <!-- Baseline values for the theme attributes in ThemeOverlay.DynamicButtons, used to
-             prevent crashes when the DynamicColorCTAAndroid flag is disabled. -->
-        <item name="globalFilledButtonBgColor">@color/filled_button_bg</item>
-        <item name="globalFilledButtonTextColor">@color/default_text_color_on_accent1_baseline_list</item>
-        <item name="globalTextButtonTextColor">@color/blue_when_enabled</item>
-        <item name="globalLinkTextColor">@color/default_text_color_link_baseline</item>
-        <item name="globalClickableSpanColor">@color/default_text_color_blue_baseline</item>
-    </style>
-    <style name="Base.V31.Theme.BrowserUI" parent="Base.V21.Theme.BrowserUI" />
-    <style name="Base.Theme.BrowserUI" parent="Base.V31.Theme.BrowserUI" />
-    <style name="Theme.BrowserUI" parent="Base.Theme.BrowserUI">
-        <!-- Control styles -->
-        <item name="checkboxStyle">@style/Widget.BrowserUI.CheckBox</item>
-        <item name="radioButtonStyle">@style/Widget.BrowserUI.RadioButton</item>
-        <item name="switchStyle">@style/Widget.BrowserUI.Switch</item>
-
-        <!-- Window Properties -->
-        <item name="android:windowBackground">@macro/default_bg_color</item>
-
-        <!-- Status bar color -->
-        <item name="android:statusBarColor" tools:targetApi="21">@android:color/black</item>
-        <item name="android:windowLightStatusBar" tools:targetApi="23">false</item>
-
-        <!-- Spinner styles -->
-        <item name="spinnerStyle">@style/SpinnerStyle</item>
-        <item name="android:progressBarStyle">@style/ProgressBarStyle</item>
-
-        <!-- Popup styles -->
-        <!-- Set android popup menu attributes for context menu styles because the context menus are
-             OS-dependent. -->
-        <item name="android:popupMenuStyle">@style/PopupMenuStyle</item>
-        <item name="android:textAppearanceLargePopupMenu">
-            @style/TextAppearance.TextLarge.Primary
-        </item>
-        <item name="android:textAppearanceSmallPopupMenu">
-            @style/TextAppearance.TextLarge.Primary
-        </item>
-        <item name="android:contextPopupMenuStyle" tools:targetApi="24">@style/PopupMenuStyle</item>
-
-        <!-- This is for keeping the current TextInputLayout style.
-             TODO(crbug.com/1206024): Remove or update once the design for the app is updated. -->
-        <item name="textInputStyle">@style/Widget.BrowserUI.TextInputLayout</item>
-    </style>
-    <!-- Overridden by night mode. -->
-    <style name="Theme.BrowserUI.DayNight" parent="Theme.BrowserUI" />
-
-    <!-- Colors should be mirrored by Base.V21.Theme.BrowserUI. -->
-    <style name="Theme.BrowserUI.DialogWhenLarge" parent="Theme.MaterialComponents.DayNight.DialogWhenLarge">
-        <item name="dynamicColorThemeOverlay">@style/ThemeOverlay.BrowserUI.DynamicColors</item>
-
-         <!-- Color palettes -->
-        <item name="colorPrimary">@color/baseline_primary_600</item>
-        <item name="colorPrimaryInverse">@color/baseline_primary_200</item>
-        <item name="colorPrimaryDark">@android:color/black</item>
-        <item name="colorOnPrimary">@color/baseline_primary_0</item>
-        <item name="colorPrimaryContainer">@color/baseline_primary_100</item>
-        <item name="colorOnPrimaryContainer">@color/baseline_primary_900</item>
-        <item name="colorSecondaryContainer">@color/baseline_secondary_100</item>
-        <item name="colorOnSecondaryContainer">@color/baseline_secondary_900</item>
-        <item name="colorAccent">@macro/default_control_color_active</item>
-        <item name="android:colorBackground">@color/baseline_neutral_0</item>
-        <item name="colorOnBackground">@color/baseline_neutral_900</item>
-        <item name="colorSurface">@color/baseline_neutral_0</item>
-        <item name="colorOnSurface">@color/baseline_neutral_900</item>
-        <item name="colorSurfaceVariant">@color/baseline_neutral_variant_100</item>
-        <item name="colorOnSurfaceVariant">@color/baseline_neutral_variant_700</item>
-        <item name="colorOnSurfaceInverse">@color/baseline_neutral_50</item>
-        <item name="colorOutline">@color/baseline_neutral_variant_500</item>
-        <item name="colorError">@color/baseline_error_600</item>
-
-        <!-- Text colors-->
-        <item name="android:textColorPrimary">@color/default_text_color_list</item>
-        <item name="android:textColorSecondary">@color/default_text_color_secondary_list</item>
-        <item name="android:textColorLink">@macro/default_text_color_link</item>
-        <item name="android:textColorHighlight">@color/text_highlight_color</item>
-        <item name="android:textColorHint">@color/default_text_color_hint_list</item>
-
-        <!-- Widget colors: checkboxes, switches, buttons, etc. -->
-        <item name="colorControlNormal">@macro/default_control_color_normal</item>
-        <item name="colorControlActivated">@macro/default_control_color_active</item>
-        <item name="colorControlHighlight">@color/control_highlight_color</item>
-
-        <!-- Elevation overlays -->
-        <item name="elevationOverlayEnabled">true</item>
-        <item name="elevationOverlayColor">@color/baseline_neutral_600</item>
-        <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
-
-        <!-- Custom semantic names -->
-        <!-- Supports dynamic colors now. -->
-        <item name="default_bg_color_dynamic">?attr/colorSurface</item>
-        <item name="divider_line_bg_color_dynamic">?attr/colorSurfaceVariant</item>
-
-        <!-- Baseline values for the theme attributes in ThemeOverlay.DynamicButtons, used to
-             prevent crashes when the DynamicColorCTAAndroid flag is disabled. -->
-        <item name="globalFilledButtonBgColor">@color/filled_button_bg</item>
-        <item name="globalFilledButtonTextColor">@color/default_text_color_on_accent1_baseline_list</item>
-        <item name="globalTextButtonTextColor">@color/blue_when_enabled</item>
-        <item name="globalLinkTextColor">@color/default_text_color_link_baseline</item>
-        <item name="globalClickableSpanColor">@color/default_text_color_blue_baseline</item>
-    </style>
-    <!-- Overridden by night mode. -->
-    <style name="Theme.BrowserUI.DialogWhenLarge.DayNight" parent="Theme.BrowserUI.DialogWhenLarge"/>
-
     <!-- Theme overlays -->
 
     <!-- Fullscreen -->
diff --git a/components/browser_ui/test/android/BUILD.gn b/components/browser_ui/test/android/BUILD.gn
index 1d4e7de..f1c352d7 100644
--- a/components/browser_ui/test/android/BUILD.gn
+++ b/components/browser_ui/test/android/BUILD.gn
@@ -10,6 +10,7 @@
   sources = [ "src/org/chromium/components/browser_ui/test/BrowserUiDummyFragmentActivity.java" ]
   deps = [
     "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/theme/android:java_resources",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_fragment_fragment_java",
     "//ui/android:ui_java_test_support",
diff --git a/components/browser_ui/theme/android/BUILD.gn b/components/browser_ui/theme/android/BUILD.gn
new file mode 100644
index 0000000..bbb1f9a
--- /dev/null
+++ b/components/browser_ui/theme/android/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2022 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("//build/config/android/rules.gni")
+
+android_resources("java_resources") {
+  sources = [
+    "java/res/values-night/themes.xml",
+    "java/res/values/themes.xml",
+  ]
+  deps = [
+    "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/widget/android:java_resources",
+  ]
+}
diff --git a/components/browser_ui/theme/android/OWNERS b/components/browser_ui/theme/android/OWNERS
new file mode 100644
index 0000000..1f427c66
--- /dev/null
+++ b/components/browser_ui/theme/android/OWNERS
@@ -0,0 +1,2 @@
+sinansahin@google.com
+skym@chromium.org
diff --git a/components/browser_ui/styles/android/java/res/values-night/themes.xml b/components/browser_ui/theme/android/java/res/values-night/themes.xml
similarity index 96%
rename from components/browser_ui/styles/android/java/res/values-night/themes.xml
rename to components/browser_ui/theme/android/java/res/values-night/themes.xml
index 4e100109..512dc28 100644
--- a/components/browser_ui/styles/android/java/res/values-night/themes.xml
+++ b/components/browser_ui/theme/android/java/res/values-night/themes.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+<!-- Copyright 2022 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. -->
 
diff --git a/components/browser_ui/theme/android/java/res/values/themes.xml b/components/browser_ui/theme/android/java/res/values/themes.xml
new file mode 100644
index 0000000..ef61a1e
--- /dev/null
+++ b/components/browser_ui/theme/android/java/res/values/themes.xml
@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Colors should be mirrored by Theme.BrowserUI.DialogWhenLarge. -->
+    <style name="Base.V21.Theme.BrowserUI" parent="Theme.MaterialComponents.DayNight.NoActionBar">
+        <item name="dynamicColorThemeOverlay">@style/ThemeOverlay.BrowserUI.DynamicColors</item>
+
+        <!-- Color palettes -->
+        <item name="colorPrimary">@color/baseline_primary_600</item>
+        <item name="colorPrimaryDark">@android:color/black</item>
+        <item name="colorPrimaryInverse">@color/baseline_primary_200</item>
+        <item name="colorOnPrimary">@color/baseline_primary_0</item>
+        <item name="colorPrimaryContainer">@color/baseline_primary_100</item>
+        <item name="colorOnPrimaryContainer">@color/baseline_primary_900</item>
+        <item name="colorSecondaryContainer">@color/baseline_secondary_100</item>
+        <item name="colorOnSecondaryContainer">@color/baseline_secondary_900</item>
+        <item name="colorAccent">@macro/default_control_color_active</item>
+        <item name="android:colorBackground">@color/baseline_neutral_0</item>
+        <item name="colorOnBackground">@color/baseline_neutral_900</item>
+        <item name="colorSurface">@color/baseline_neutral_0</item>
+        <item name="colorOnSurface">@color/baseline_neutral_900</item>
+        <item name="colorSurfaceVariant">@color/baseline_neutral_variant_100</item>
+        <item name="colorOnSurfaceVariant">@color/baseline_neutral_variant_700</item>
+        <item name="colorOnSurfaceInverse">@color/baseline_neutral_50</item>
+        <item name="colorOutline">@color/baseline_neutral_variant_500</item>
+        <item name="colorError">@color/baseline_error_600</item>
+
+        <!-- Text colors-->
+        <item name="android:textColorPrimary">@color/default_text_color_list</item>
+        <item name="android:textColorSecondary">@color/default_text_color_secondary_list</item>
+        <item name="android:textColorLink">@macro/default_text_color_link</item>
+        <item name="android:textColorHighlight">@color/text_highlight_color</item>
+        <item name="android:textColorHint">@color/default_text_color_hint_list</item>
+
+        <!-- Widget colors: checkboxes, switches, buttons, etc. -->
+        <item name="colorControlNormal">@macro/default_control_color_normal</item>
+        <item name="colorControlActivated">@macro/default_control_color_active</item>
+        <item name="colorControlHighlight">@color/control_highlight_color</item>
+
+        <!-- Elevation overlays -->
+        <item name="elevationOverlayEnabled">true</item>
+        <item name="elevationOverlayColor">@color/baseline_neutral_600</item>
+        <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
+
+        <!-- Custom semantic names -->
+        <!-- Supports dynamic colors now. -->
+        <item name="default_bg_color_dynamic">?attr/colorSurface</item>
+        <item name="divider_line_bg_color_dynamic">?attr/colorSurfaceVariant</item>
+
+        <!-- Baseline values for the theme attributes in ThemeOverlay.DynamicButtons, used to
+             prevent crashes when the DynamicColorCTAAndroid flag is disabled. -->
+        <item name="globalFilledButtonBgColor">@color/filled_button_bg</item>
+        <item name="globalFilledButtonTextColor">@color/default_text_color_on_accent1_baseline_list</item>
+        <item name="globalTextButtonTextColor">@color/blue_when_enabled</item>
+        <item name="globalLinkTextColor">@color/default_text_color_link_baseline</item>
+        <item name="globalClickableSpanColor">@color/default_text_color_blue_baseline</item>
+    </style>
+    <style name="Base.V31.Theme.BrowserUI" parent="Base.V21.Theme.BrowserUI" />
+    <style name="Base.Theme.BrowserUI" parent="Base.V31.Theme.BrowserUI" />
+    <style name="Theme.BrowserUI" parent="Base.Theme.BrowserUI">
+        <!-- Control styles -->
+        <item name="checkboxStyle">@style/Widget.BrowserUI.CheckBox</item>
+        <item name="radioButtonStyle">@style/Widget.BrowserUI.RadioButton</item>
+        <item name="switchStyle">@style/Widget.BrowserUI.Switch</item>
+
+        <!-- Window Properties -->
+        <item name="android:windowBackground">@macro/default_bg_color</item>
+
+        <!-- Status bar color -->
+        <item name="android:statusBarColor" tools:targetApi="21">@android:color/black</item>
+        <item name="android:windowLightStatusBar" tools:targetApi="23">false</item>
+
+        <!-- Spinner styles -->
+        <item name="spinnerStyle">@style/SpinnerStyle</item>
+        <item name="android:progressBarStyle">@style/ProgressBarStyle</item>
+
+        <!-- Popup styles -->
+        <!-- Set android popup menu attributes for context menu styles because the context menus are
+             OS-dependent. -->
+        <item name="android:popupMenuStyle">@style/PopupMenuStyle</item>
+        <item name="android:textAppearanceLargePopupMenu">
+            @style/TextAppearance.TextLarge.Primary
+        </item>
+        <item name="android:textAppearanceSmallPopupMenu">
+            @style/TextAppearance.TextLarge.Primary
+        </item>
+        <item name="android:contextPopupMenuStyle" tools:targetApi="24">@style/PopupMenuStyle</item>
+
+        <!-- This is for keeping the current TextInputLayout style.
+             TODO(crbug.com/1206024): Remove or update once the design for the app is updated. -->
+        <item name="textInputStyle">@style/Widget.BrowserUI.TextInputLayout</item>
+    </style>
+    <!-- Overridden by night mode. -->
+    <style name="Theme.BrowserUI.DayNight" parent="Theme.BrowserUI" />
+
+    <!-- Colors should be mirrored by Base.V21.Theme.BrowserUI. -->
+    <style name="Theme.BrowserUI.DialogWhenLarge" parent="Theme.MaterialComponents.DayNight.DialogWhenLarge">
+        <item name="dynamicColorThemeOverlay">@style/ThemeOverlay.BrowserUI.DynamicColors</item>
+
+         <!-- Color palettes -->
+        <item name="colorPrimary">@color/baseline_primary_600</item>
+        <item name="colorPrimaryInverse">@color/baseline_primary_200</item>
+        <item name="colorPrimaryDark">@android:color/black</item>
+        <item name="colorOnPrimary">@color/baseline_primary_0</item>
+        <item name="colorPrimaryContainer">@color/baseline_primary_100</item>
+        <item name="colorOnPrimaryContainer">@color/baseline_primary_900</item>
+        <item name="colorSecondaryContainer">@color/baseline_secondary_100</item>
+        <item name="colorOnSecondaryContainer">@color/baseline_secondary_900</item>
+        <item name="colorAccent">@macro/default_control_color_active</item>
+        <item name="android:colorBackground">@color/baseline_neutral_0</item>
+        <item name="colorOnBackground">@color/baseline_neutral_900</item>
+        <item name="colorSurface">@color/baseline_neutral_0</item>
+        <item name="colorOnSurface">@color/baseline_neutral_900</item>
+        <item name="colorSurfaceVariant">@color/baseline_neutral_variant_100</item>
+        <item name="colorOnSurfaceVariant">@color/baseline_neutral_variant_700</item>
+        <item name="colorOnSurfaceInverse">@color/baseline_neutral_50</item>
+        <item name="colorOutline">@color/baseline_neutral_variant_500</item>
+        <item name="colorError">@color/baseline_error_600</item>
+
+        <!-- Text colors-->
+        <item name="android:textColorPrimary">@color/default_text_color_list</item>
+        <item name="android:textColorSecondary">@color/default_text_color_secondary_list</item>
+        <item name="android:textColorLink">@macro/default_text_color_link</item>
+        <item name="android:textColorHighlight">@color/text_highlight_color</item>
+        <item name="android:textColorHint">@color/default_text_color_hint_list</item>
+
+        <!-- Widget colors: checkboxes, switches, buttons, etc. -->
+        <item name="colorControlNormal">@macro/default_control_color_normal</item>
+        <item name="colorControlActivated">@macro/default_control_color_active</item>
+        <item name="colorControlHighlight">@color/control_highlight_color</item>
+
+        <!-- Elevation overlays -->
+        <item name="elevationOverlayEnabled">true</item>
+        <item name="elevationOverlayColor">@color/baseline_neutral_600</item>
+        <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
+
+        <!-- Custom semantic names -->
+        <!-- Supports dynamic colors now. -->
+        <item name="default_bg_color_dynamic">?attr/colorSurface</item>
+        <item name="divider_line_bg_color_dynamic">?attr/colorSurfaceVariant</item>
+
+        <!-- Baseline values for the theme attributes in ThemeOverlay.DynamicButtons, used to
+             prevent crashes when the DynamicColorCTAAndroid flag is disabled. -->
+        <item name="globalFilledButtonBgColor">@color/filled_button_bg</item>
+        <item name="globalFilledButtonTextColor">@color/default_text_color_on_accent1_baseline_list</item>
+        <item name="globalTextButtonTextColor">@color/blue_when_enabled</item>
+        <item name="globalLinkTextColor">@color/default_text_color_link_baseline</item>
+        <item name="globalClickableSpanColor">@color/default_text_color_blue_baseline</item>
+    </style>
+    <!-- Overridden by night mode. -->
+    <style name="Theme.BrowserUI.DialogWhenLarge.DayNight" parent="Theme.BrowserUI.DialogWhenLarge"/>
+
+    <!-- Unlike |Theme.Chromium.AlertDialog|, this is a complete theme that can be used as an
+         activity theme on its own. This should be kept in sync with |Theme.Chromium.AlertDialog|.
+         -->
+    <style name="Theme.BrowserUI.AlertDialog.NoActionBar"
+        parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
+        <item name="android:textColorPrimary">@macro/default_text_color</item>
+        <item name="android:windowBackground">@drawable/dialog_bg_no_shadow</item>
+        <item name="android:windowTitleStyle">@style/TextAppearance.AlertDialogTitleStyle</item>
+        <item name="android:textColorHighlight">@color/text_highlight_color</item>
+
+        <!--  Overriding AppCompat values -->
+        <item name="colorAccent">@macro/default_control_color_active</item>
+        <item name="colorControlNormal">@macro/default_control_color_normal</item>
+        <item name="colorControlActivated">@macro/default_control_color_active</item>
+        <item name="colorControlHighlight">@color/control_highlight_color</item>
+        <item name="spinnerStyle">@style/SpinnerStyle</item>
+
+        <!-- Depending on if the support library or framework is inflating the
+             dialog, a different layout is used, that names this style slightly
+             differently. WebView will use the framework version for the
+             foreseeable future, so both of these need to be specified. See
+             https://crbug.com/1234129. -->
+        <item name="android:buttonBarButtonStyle">@style/AlertDialogButtonStyle</item>
+        <item name="buttonBarButtonStyle">@style/AlertDialogButtonStyle</item>
+
+        <!-- NoActionBar -->
+        <item name="windowNoTitle">true</item>
+        <item name="windowActionBar">false</item>
+    </style>
+
+  <!-- This theme is used instead of android:style/Theme.NoDisplay so that it has the required
+        attributes in case the context ends up being used to inflate views. -->
+  <style name="Theme.BrowserUI.NoDisplay" parent="Theme.BrowserUI.DayNight">
+      <item name="android:windowBackground">@null</item>
+      <item name="android:windowContentOverlay">@null</item>
+      <item name="android:windowIsTranslucent">true</item>
+      <item name="android:windowAnimationStyle">@null</item>
+      <item name="android:windowDisablePreview">true</item>
+      <item name="android:windowNoDisplay">true</item>
+  </style>
+
+  <!-- These themes are used instead of android:style/Theme.Translucent* so that they have the
+        required attributes in case the context ends up being used to inflate views. -->
+  <style name="Theme.BrowserUI.Translucent" parent="Theme.BrowserUI.DayNight">
+      <item name="android:windowBackground">@android:color/transparent</item>
+      <item name="android:colorBackgroundCacheHint">@null</item>
+      <item name="android:windowIsTranslucent">true</item>
+      <item name="android:windowAnimationStyle">@android:style/Animation</item>
+  </style>
+  <style name="Theme.BrowserUI.Translucent.NoTitleBar">
+      <item name="android:windowNoTitle">true</item>
+      <item name="android:windowContentOverlay">@null</item>
+  </style>
+</resources>
diff --git a/components/browser_ui/widget/android/BUILD.gn b/components/browser_ui/widget/android/BUILD.gn
index 6912044..2db5ca7 100644
--- a/components/browser_ui/widget/android/BUILD.gn
+++ b/components/browser_ui/widget/android/BUILD.gn
@@ -265,7 +265,6 @@
     "java/res/values/drawables.xml",
     "java/res/values/ids.xml",
     "java/res/values/styles.xml",
-    "java/res/values/themes.xml",
     "java/res/values/values.xml",
   ]
   deps = [
@@ -324,6 +323,7 @@
     "//base:base_java",
     "//base:base_java_test_support",
     "//components/browser_ui/test/android:test_support_java",
+    "//components/browser_ui/theme/android:java_resources",
     "//content/public/test/android:content_java_test_support",
     "//third_party/android_support_test_runner:rules_java",
     "//third_party/android_support_test_runner:runner_java",
diff --git a/components/browser_ui/widget/android/java/res/values/themes.xml b/components/browser_ui/widget/android/java/res/values/themes.xml
deleted file mode 100644
index 80acd7a43..0000000
--- a/components/browser_ui/widget/android/java/res/values/themes.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2022 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-
-<resources>
-    <!-- Unlike |Theme.Chromium.AlertDialog|, this is a complete theme that can be used as an
-         activity theme on its own. This should be kept in sync with |Theme.Chromium.AlertDialog|.
-         -->
-    <style name="Theme.BrowserUI.AlertDialog.NoActionBar"
-        parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
-        <item name="android:textColorPrimary">@macro/default_text_color</item>
-        <item name="android:windowBackground">@drawable/dialog_bg_no_shadow</item>
-        <item name="android:windowTitleStyle">@style/TextAppearance.AlertDialogTitleStyle</item>
-        <item name="android:textColorHighlight">@color/text_highlight_color</item>
-
-        <!--  Overriding AppCompat values -->
-        <item name="colorAccent">@macro/default_control_color_active</item>
-        <item name="colorControlNormal">@macro/default_control_color_normal</item>
-        <item name="colorControlActivated">@macro/default_control_color_active</item>
-        <item name="colorControlHighlight">@color/control_highlight_color</item>
-        <item name="spinnerStyle">@style/SpinnerStyle</item>
-
-        <!-- Depending on if the support library or framework is inflating the
-             dialog, a different layout is used, that names this style slightly
-             differently. WebView will use the framework version for the
-             foreseeable future, so both of these need to be specified. See
-             https://crbug.com/1234129. -->
-        <item name="android:buttonBarButtonStyle">@style/AlertDialogButtonStyle</item>
-        <item name="buttonBarButtonStyle">@style/AlertDialogButtonStyle</item>
-
-        <!-- NoActionBar -->
-        <item name="windowNoTitle">true</item>
-        <item name="windowActionBar">false</item>
-    </style>
-</resources>
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighterTest.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighterTest.java
index 1247682..e2d99f4 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighterTest.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighterTest.java
@@ -27,7 +27,7 @@
 
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
-import org.chromium.components.browser_ui.widget.R;
+import org.chromium.components.browser_ui.widget.test.R;
 
 /**
  * Tests the utility methods for highlighting of a view.
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java
index 9114a57..c6889715 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java
@@ -56,8 +56,8 @@
         //  is based on material theme. For now we need the theme wrapper to inflate the layout;
         //  because we are not setting our theme overlay for the test apk
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            ContextThemeWrapper wrapperTheme = new ContextThemeWrapper(mContext,
-                    org.chromium.components.browser_ui.widget.R.style.Theme_BrowserUI_DayNight);
+            ContextThemeWrapper wrapperTheme =
+                    new ContextThemeWrapper(mContext, R.style.Theme_BrowserUI_DayNight);
             mPromoCardCoordinator =
                     new PromoCardCoordinator(wrapperTheme, mModel, "test-feature", layoutStyle);
             mView = (PromoCardView) mPromoCardCoordinator.getView();
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index 530e9f7..01d75e5 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@
 {
-  "version": "6.10",
-  "log_list_timestamp": "2022-03-14T01:34:10Z",
+  "version": "7.1",
+  "log_list_timestamp": "2022-03-15T01:34:41Z",
   "operators": [
     {
       "name": "Google",
@@ -312,6 +312,22 @@
               "timestamp": "2019-02-16T00:00:00Z"
             }
           }
+        },
+        {
+          "description": "DigiCert Yeti2022-2 Log",
+          "log_id": "BZwB0yDgB4QTlYBJjRF8kDJmr69yULWvO0akPhGEDUo=",
+          "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHWlePwrycXfNnV3DNEkA7mB34XJ2dKh8XH0J8jIdBX4u/lsx1Tr9czRuSRROUFiWWsTH9L4FZKT31+WxbTMMww==",
+          "url": "https://yeti2022-2.ct.digicert.com/log/",
+          "mmd": 86400,
+          "state": {
+            "qualified": {
+              "timestamp": "2022-03-11T00:00:00Z"
+            }
+          },
+          "temporal_interval": {
+            "start_inclusive": "2022-01-01T00:00:00Z",
+            "end_exclusive": "2023-01-01T00:00:00Z"
+          }
         }
       ]
     },
diff --git a/components/content_settings/browser/page_specific_content_settings.cc b/components/content_settings/browser/page_specific_content_settings.cc
index 5088539..6802b52 100644
--- a/components/content_settings/browser/page_specific_content_settings.cc
+++ b/components/content_settings/browser/page_specific_content_settings.cc
@@ -29,6 +29,7 @@
 #include "components/content_settings/core/browser/content_settings_utils.h"
 #include "components/prefs/pref_service.h"
 #include "components/privacy_sandbox/canonical_topic.h"
+#include "components/privacy_sandbox/privacy_sandbox_features.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/cookie_access_details.h"
 #include "content/public/browser/navigation_handle.h"
@@ -916,6 +917,7 @@
 std::vector<privacy_sandbox::CanonicalTopic>
 PageSpecificContentSettings::GetAccessedTopics() const {
   if (accessed_topics_.empty() &&
+      privacy_sandbox::kPrivacySandboxSettings3ShowSampleDataForTesting.Get() &&
       page().GetMainDocument().GetLastCommittedURL().host() == "example.com") {
     // TODO(crbug.com/1286276): Remove sample topic when API is ready.
     return {privacy_sandbox::CanonicalTopic(
diff --git a/components/feed/core/v2/config.h b/components/feed/core/v2/config.h
index 0b942e17..95b16cc 100644
--- a/components/feed/core/v2/config.h
+++ b/components/feed/core/v2/config.h
@@ -103,13 +103,7 @@
   // Set of optional capabilities included in requests. See
   // CreateFeedQueryRequest() for required capabilities.
   base::flat_set<feedwire::Capability> experimental_capabilities = {
-      feedwire::Capability::DISMISS_COMMAND,
-      feedwire::Capability::INFINITE_FEED,
       feedwire::Capability::MATERIAL_NEXT_BASELINE,
-      feedwire::Capability::PREFETCH_METADATA,
-      feedwire::Capability::REQUEST_SCHEDULE,
-      feedwire::Capability::UI_THEME_V2,
-      feedwire::Capability::UNDO_FOR_DISMISS_COMMAND,
       feedwire::Capability::CONTENT_LIFETIME,
   };
 
diff --git a/components/feed/core/v2/proto_util.cc b/components/feed/core/v2/proto_util.cc
index 7cab5fc..c565ee1 100644
--- a/components/feed/core/v2/proto_util.cc
+++ b/components/feed/core/v2/proto_util.cc
@@ -33,6 +33,8 @@
 
 namespace feed {
 namespace {
+using feedwire::Capability;
+
 feedwire::Version::Architecture GetBuildArchitecture() {
 #if defined(ARCH_CPU_X86_64)
   return feedwire::Version::X86_64;
@@ -126,44 +128,42 @@
   request.set_request_version(feedwire::Request::FEED_QUERY);
 
   feedwire::FeedRequest& feed_request = *request.mutable_feed_request();
-  feed_request.add_client_capability(feedwire::Capability::CARD_MENU);
-  feed_request.add_client_capability(feedwire::Capability::LOTTIE_ANIMATIONS);
-  feed_request.add_client_capability(
-      feedwire::Capability::LONG_PRESS_CARD_MENU);
-  feed_request.add_client_capability(feedwire::Capability::SHARE);
 
-  feed_request.add_client_capability(feedwire::Capability::OPEN_IN_TAB);
-  feed_request.add_client_capability(feedwire::Capability::OPEN_IN_INCOGNITO);
+  for (Capability capability :
+       {Capability::CARD_MENU, Capability::LOTTIE_ANIMATIONS,
+        Capability::LONG_PRESS_CARD_MENU, Capability::SHARE,
+        Capability::OPEN_IN_TAB, Capability::OPEN_IN_INCOGNITO,
+        Capability::DISMISS_COMMAND, Capability::INFINITE_FEED,
+        Capability::PREFETCH_METADATA, Capability::REQUEST_SCHEDULE,
+        Capability::UI_THEME_V2, Capability::UNDO_FOR_DISMISS_COMMAND}) {
+    feed_request.add_client_capability(capability);
+  }
 
   for (auto capability : GetFeedConfig().experimental_capabilities)
     feed_request.add_client_capability(capability);
+
   if (base::FeatureList::IsEnabled(kInterestFeedV2Hearts)) {
-    feed_request.add_client_capability(feedwire::Capability::HEART);
+    feed_request.add_client_capability(Capability::HEART);
   }
   if (request_metadata.autoplay_enabled) {
-    feed_request.add_client_capability(
-        feedwire::Capability::INLINE_VIDEO_AUTOPLAY);
-    feed_request.add_client_capability(
-        feedwire::Capability::OPEN_VIDEO_COMMAND);
+    feed_request.add_client_capability(Capability::INLINE_VIDEO_AUTOPLAY);
+    feed_request.add_client_capability(Capability::OPEN_VIDEO_COMMAND);
   }
 
   if (base::FeatureList::IsEnabled(kFeedStamp)) {
-    feed_request.add_client_capability(
-        feedwire::Capability::SILK_AMP_OPEN_COMMAND);
-    feed_request.add_client_capability(feedwire::Capability::AMP_STORY_PLAYER);
-    feed_request.add_client_capability(
-        feedwire::Capability::AMP_GROUP_DATASTORE);
+    feed_request.add_client_capability(Capability::SILK_AMP_OPEN_COMMAND);
+    feed_request.add_client_capability(Capability::AMP_STORY_PLAYER);
+    feed_request.add_client_capability(Capability::AMP_GROUP_DATASTORE);
   }
 
   if (base::FeatureList::IsEnabled(reading_list::switches::kReadLater)) {
-    feed_request.add_client_capability(feedwire::Capability::READ_LATER);
+    feed_request.add_client_capability(Capability::READ_LATER);
   } else {
-    feed_request.add_client_capability(feedwire::Capability::DOWNLOAD_LINK);
+    feed_request.add_client_capability(Capability::DOWNLOAD_LINK);
   }
 
   if (base::FeatureList::IsEnabled(kPersonalizeFeedUnsignedUsers)) {
-    feed_request.add_client_capability(
-        feedwire::Capability::ON_DEVICE_USER_PROFILE);
+    feed_request.add_client_capability(Capability::ON_DEVICE_USER_PROFILE);
   }
 
   *feed_request.mutable_client_info() = CreateClientInfo(request_metadata);
diff --git a/components/feed/core/v2/proto_util_unittest.cc b/components/feed/core/v2/proto_util_unittest.cc
index faae10e..2c9a56d8 100644
--- a/components/feed/core/v2/proto_util_unittest.cc
+++ b/components/feed/core/v2/proto_util_unittest.cc
@@ -106,7 +106,7 @@
   // Try to disable _INFINITE_FEED.
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeatureWithParameters(
-      kInterestFeedV2, {{"enable_INFINITE_FEED", "false"}});
+      kInterestFeedV2, {{"enable_MATERIAL_NEXT_BASELINE", "false"}});
   OverrideConfigWithFinchForTesting();
 
   feedwire::FeedRequest request =
@@ -117,19 +117,11 @@
           .feed_request();
 
   // Additional features may be present based on the current testing config.
-  ASSERT_THAT(
-      request.client_capability(),
-      testing::IsSupersetOf(
-          {feedwire::Capability::REQUEST_SCHEDULE,
-           feedwire::Capability::LOTTIE_ANIMATIONS,
-           feedwire::Capability::LONG_PRESS_CARD_MENU,
-           feedwire::Capability::OPEN_IN_TAB, feedwire::Capability::CARD_MENU,
-           feedwire::Capability::DISMISS_COMMAND, feedwire::Capability::SHARE,
-           feedwire::Capability::MATERIAL_NEXT_BASELINE,
-           feedwire::Capability::UI_THEME_V2,
-           feedwire::Capability::UNDO_FOR_DISMISS_COMMAND,
-           feedwire::Capability::PREFETCH_METADATA,
-           feedwire::Capability::CONTENT_LIFETIME}));
+  ASSERT_THAT(request.client_capability(),
+              Not(Contains(feedwire::Capability::MATERIAL_NEXT_BASELINE)));
+
+  ASSERT_THAT(request.client_capability(),
+              Contains(feedwire::Capability::CONTENT_LIFETIME));
 }
 
 TEST(ProtoUtilTest, PrivacyNoticeCardAcknowledged) {
diff --git a/components/feed/core/v2/xsurface_datastore.cc b/components/feed/core/v2/xsurface_datastore.cc
index 60df155..fe55ec1 100644
--- a/components/feed/core/v2/xsurface_datastore.cc
+++ b/components/feed/core/v2/xsurface_datastore.cc
@@ -42,11 +42,13 @@
   return entries_;
 }
 
-void XsurfaceDatastoreSlice::AddObserver(Observer* o) {
+void XsurfaceDatastoreSlice::AddObserver(
+    XsurfaceDatastoreDataReader::Observer* o) {
   observers_.AddObserver(o);
 }
 
-void XsurfaceDatastoreSlice::RemoveObserver(Observer* o) {
+void XsurfaceDatastoreSlice::RemoveObserver(
+    XsurfaceDatastoreDataReader::Observer* o) {
   observers_.RemoveObserver(o);
 }
 
@@ -63,11 +65,13 @@
     s->RemoveObserver(this);
   }
 }
-void XsurfaceDatastoreAggregate::AddObserver(Observer* o) {
+void XsurfaceDatastoreAggregate::AddObserver(
+    XsurfaceDatastoreDataReader::Observer* o) {
   observers_.AddObserver(o);
 }
 
-void XsurfaceDatastoreAggregate::RemoveObserver(Observer* o) {
+void XsurfaceDatastoreAggregate::RemoveObserver(
+    XsurfaceDatastoreDataReader::Observer* o) {
   observers_.RemoveObserver(o);
 }
 
@@ -86,7 +90,7 @@
 void XsurfaceDatastoreAggregate::DatastoreEntryUpdated(
     XsurfaceDatastoreDataReader* source,
     const std::string& key) {
-  for (Observer& o : observers_) {
+  for (XsurfaceDatastoreDataReader::Observer& o : observers_) {
     o.DatastoreEntryUpdated(this, key);
   }
 }
@@ -94,7 +98,7 @@
 void XsurfaceDatastoreAggregate::DatastoreEntryRemoved(
     XsurfaceDatastoreDataReader* source,
     const std::string& key) {
-  for (Observer& o : observers_) {
+  for (XsurfaceDatastoreDataReader::Observer& o : observers_) {
     o.DatastoreEntryRemoved(this, key);
   }
 }
diff --git a/components/feed/core/v2/xsurface_datastore.h b/components/feed/core/v2/xsurface_datastore.h
index 8b3f1c8..7a079adb 100644
--- a/components/feed/core/v2/xsurface_datastore.h
+++ b/components/feed/core/v2/xsurface_datastore.h
@@ -41,8 +41,8 @@
 
   // Begin observing. Data already present before `AddObserver()` is called can
   // be read using `GetAllEntries()`.
-  virtual void AddObserver(Observer* o) = 0;
-  virtual void RemoveObserver(Observer* o) = 0;
+  virtual void AddObserver(XsurfaceDatastoreDataReader::Observer* o) = 0;
+  virtual void RemoveObserver(XsurfaceDatastoreDataReader::Observer* o) = 0;
   // Find an entry with the given key. Returns nullptr if the entry does not
   // exist.
   virtual const std::string* FindEntry(const std::string& key) const = 0;
@@ -91,8 +91,8 @@
       delete;
 
   // XsurfaceDatastoreDataReader.
-  void AddObserver(Observer* o) override;
-  void RemoveObserver(Observer* o) override;
+  void AddObserver(XsurfaceDatastoreDataReader::Observer* o) override;
+  void RemoveObserver(XsurfaceDatastoreDataReader::Observer* o) override;
   const std::string* FindEntry(const std::string& key) const override;
   std::map<std::string, std::string> GetAllEntries() const override;
 
@@ -103,7 +103,7 @@
                              const std::string& key) override;
 
  private:
-  base::ObserverList<Observer> observers_;
+  base::ObserverList<XsurfaceDatastoreDataReader::Observer> observers_;
   std::vector<raw_ptr<XsurfaceDatastoreDataReader>> sources_;
 };
 
diff --git a/components/infobars/android/BUILD.gn b/components/infobars/android/BUILD.gn
index a4871503..9b47155 100644
--- a/components/infobars/android/BUILD.gn
+++ b/components/infobars/android/BUILD.gn
@@ -108,7 +108,7 @@
     ":java",
     "//base:base_java",
     "//base:base_java_test_support",
-    "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/theme/android:java_resources",
     "//components/browser_ui/widget/android:java",
     "//third_party/android_support_test_runner:rules_java",
     "//third_party/android_support_test_runner:runner_java",
@@ -117,4 +117,5 @@
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/junit:junit",
   ]
+  resources_package = "org.chromium.components.infobars.test"
 }
diff --git a/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java b/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java
index b3edb2c..58e8dc47 100644
--- a/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java
+++ b/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java
@@ -20,6 +20,7 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.UiThreadTest;
 import org.chromium.components.infobars.InfoBarControlLayout.ControlLayoutParams;
+import org.chromium.components.infobars.test.R;
 
 /**
  * Tests for InfoBarControlLayout.  This suite doesn't check for specific details, like margins
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageDispatcherUnitTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageDispatcherUnitTest.java
index d9633357..418e8f5 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageDispatcherUnitTest.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageDispatcherUnitTest.java
@@ -53,7 +53,8 @@
                 .with(MessageBannerProperties.TITLE, "test")
                 .with(MessageBannerProperties.DESCRIPTION, "Description")
                 .with(MessageBannerProperties.ICON, null)
-                .with(MessageBannerProperties.ON_PRIMARY_ACTION, () -> {})
+                .with(MessageBannerProperties.ON_PRIMARY_ACTION,
+                        () -> PrimaryActionClickBehavior.DISMISS_IMMEDIATELY)
                 .with(MessageBannerProperties.ON_DISMISSED, (dismissReason) -> {})
                 .build();
     }
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessage.java b/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessage.java
index e08a071..09b481d 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessage.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessage.java
@@ -150,12 +150,14 @@
         }
         return true;
     }
-
     private void handlePrimaryAction(View v) {
         // Avoid running the primary action callback if the message has already been dismissed.
         if (mMessageDismissed) return;
-        mModel.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
-        mDismissHandler.invoke(mModel, DismissReason.PRIMARY_ACTION);
+
+        if (mModel.get(MessageBannerProperties.ON_PRIMARY_ACTION).get()
+                == PrimaryActionClickBehavior.DISMISS_IMMEDIATELY) {
+            mDismissHandler.invoke(mModel, DismissReason.PRIMARY_ACTION);
+        }
     }
 
     private void handleSecondaryAction() {
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessageTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessageTest.java
index 874da49..718654d8 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessageTest.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/SingleActionMessageTest.java
@@ -248,7 +248,10 @@
                         ApiCompatibilityUtils.getDrawable(
                                 sActivity.getResources(), android.R.drawable.ic_menu_add))
                 .with(MessageBannerProperties.ON_PRIMARY_ACTION,
-                        () -> { mPrimaryActionCallback.notifyCalled(); })
+                        () -> {
+                            mPrimaryActionCallback.notifyCalled();
+                            return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
+                        })
                 .with(MessageBannerProperties.ON_SECONDARY_ACTION,
                         () -> { mSecondaryActionCallback.notifyCalled(); })
                 .with(MessageBannerProperties.ON_TOUCH_RUNNABLE, () -> {})
diff --git a/components/messages/android/java/src/org/chromium/components/messages/MessageBannerProperties.java b/components/messages/android/java/src/org/chromium/components/messages/MessageBannerProperties.java
index 48a7995f..cbc86b0 100644
--- a/components/messages/android/java/src/org/chromium/components/messages/MessageBannerProperties.java
+++ b/components/messages/android/java/src/org/chromium/components/messages/MessageBannerProperties.java
@@ -12,6 +12,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.BooleanSupplier;
+import org.chromium.base.supplier.Supplier;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.ReadableIntPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
@@ -35,8 +36,13 @@
     public static final ReadableIntPropertyKey MESSAGE_IDENTIFIER = new ReadableIntPropertyKey();
     public static final WritableObjectPropertyKey<String> PRIMARY_BUTTON_TEXT =
             new WritableObjectPropertyKey<>();
-    public static final WritableObjectPropertyKey<Runnable> ON_PRIMARY_ACTION =
-            new WritableObjectPropertyKey<>();
+    /**
+     * See the documentation of PrimaryActionClickBehavior in
+     * components/messages/android/message_enums.h for more information about the return value of
+     * the primary action callback.
+     */
+    public static final WritableObjectPropertyKey<Supplier</*@PrimaryActionClickBehavior*/ Integer>>
+            ON_PRIMARY_ACTION = new WritableObjectPropertyKey<>();
     public static final WritableObjectPropertyKey<Runnable> ON_SECONDARY_ACTION =
             new WritableObjectPropertyKey<>();
     public static final WritableObjectPropertyKey<String> TITLE = new WritableObjectPropertyKey<>();
diff --git a/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java b/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java
index 8ddbd63a..cb4ed1cf 100644
--- a/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java
+++ b/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java
@@ -167,9 +167,11 @@
         return ((BitmapDrawable) drawable).getBitmap();
     }
 
-    private void handleActionClick() {
-        if (mNativeMessageWrapper == 0) return;
-        MessageWrapperJni.get().handleActionClick(mNativeMessageWrapper);
+    private @PrimaryActionClickBehavior int handleActionClick() {
+        if (mNativeMessageWrapper != 0) {
+            MessageWrapperJni.get().handleActionClick(mNativeMessageWrapper);
+        }
+        return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
     }
 
     private void handleSecondaryActionClick() {
diff --git a/components/messages/android/java/src/org/chromium/components/messages/MessageWrapperTest.java b/components/messages/android/java/src/org/chromium/components/messages/MessageWrapperTest.java
index 9550e1f..11b64b5 100644
--- a/components/messages/android/java/src/org/chromium/components/messages/MessageWrapperTest.java
+++ b/components/messages/android/java/src/org/chromium/components/messages/MessageWrapperTest.java
@@ -96,7 +96,7 @@
         final long nativePtr = 1;
         MessageWrapper message = MessageWrapper.create(nativePtr, MessageIdentifier.TEST_MESSAGE);
         PropertyModel messageProperties = message.getMessageProperties();
-        messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
+        messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION).get();
         Mockito.verify(mNativeMock).handleActionClick(nativePtr);
         messageProperties.get(MessageBannerProperties.ON_SECONDARY_ACTION).run();
         Mockito.verify(mNativeMock).handleSecondaryActionClick(nativePtr);
@@ -116,7 +116,7 @@
         PropertyModel messageProperties = message.getMessageProperties();
 
         message.clearNativePtr();
-        messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
+        messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION).get();
         Mockito.verify(mNativeMock, never()).handleActionClick(nativePtr);
         messageProperties.get(MessageBannerProperties.ON_SECONDARY_ACTION).run();
         Mockito.verify(mNativeMock, never()).handleSecondaryActionClick(nativePtr);
diff --git a/components/messages/android/message_enums.h b/components/messages/android/message_enums.h
index 56c6bd5..d4a3509a 100644
--- a/components/messages/android/message_enums.h
+++ b/components/messages/android/message_enums.h
@@ -105,6 +105,15 @@
   COUNT
 };
 
+// The behavior the message should follow when the primary button is clicked,
+// after running the primary action callback.
+//
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.messages
+enum class PrimaryActionClickBehavior {
+  DO_NOT_DISMISS = 0,
+  DISMISS_IMMEDIATELY = 1
+};
+
 }  // namespace messages
 
 #endif  // COMPONENTS_MESSAGES_ANDROID_MESSAGE_ENUMS_H_
diff --git a/components/messages/android/message_wrapper.h b/components/messages/android/message_wrapper.h
index def74b4..14febee0 100644
--- a/components/messages/android/message_wrapper.h
+++ b/components/messages/android/message_wrapper.h
@@ -74,6 +74,10 @@
 
   void SetDuration(long customDuration);
 
+  // Note that the message will immediately be dismissed after the primary
+  // action callback is run. Making the message remain visible after the primary
+  // action button is clicked is supported in the messages API in Java, but not
+  // currently in here in the messages API in C++.
   void SetActionClick(base::OnceClosure callback);
   void SetDismissCallback(DismissCallback callback);
 
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoAdPersonalizationController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoAdPersonalizationController.java
index 8dd70e05..7196650 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoAdPersonalizationController.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoAdPersonalizationController.java
@@ -8,6 +8,7 @@
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import java.util.List;
 
@@ -16,6 +17,7 @@
  */
 public class PageInfoAdPersonalizationController extends PageInfoPreferenceSubpageController {
     public static final int ROW_ID = View.generateViewId();
+    private static List<String> sTopicsForTesting;
 
     private final PageInfoMainController mMainController;
     private final PageInfoRowView mRowView;
@@ -32,6 +34,9 @@
 
     public void setTopicsDisplay(List<String> topics) {
         mInfo = topics;
+        if (mInfo.isEmpty() && sTopicsForTesting != null) {
+            mInfo = sTopicsForTesting;
+        }
         PageInfoRowView.ViewParams rowParams = new PageInfoRowView.ViewParams();
         rowParams.visible = !mInfo.isEmpty();
         rowParams.title = getSubpageTitle();
@@ -83,4 +88,9 @@
         removeSubpageFragment();
         mSubPage = null;
     }
+
+    @VisibleForTesting
+    public static void setTopicsForTesting(List<String> topics) {
+        sTopicsForTesting = topics;
+    }
 }
diff --git a/components/password_manager/core/browser/bulk_leak_check_service.cc b/components/password_manager/core/browser/bulk_leak_check_service.cc
index 5248c2cb..8ab3e314 100644
--- a/components/password_manager/core/browser/bulk_leak_check_service.cc
+++ b/components/password_manager/core/browser/bulk_leak_check_service.cc
@@ -42,9 +42,6 @@
     base::UmaHistogramCounts1000(
         "PasswordManager.BulkCheck.CheckedCredentialsOnErrorOrCanceled",
         credential_count_);
-    base::UmaHistogramCounts100(
-        "PasswordManager.BulkCheck.LeaksFoundOnErrorOrCanceled",
-        leaked_credential_count_);
   } else {
     base::UmaHistogramMediumTimes("PasswordManager.BulkCheck.Time",
                                   timer_since_start_.Elapsed());
diff --git a/components/password_manager/core/browser/bulk_leak_check_service_unittest.cc b/components/password_manager/core/browser/bulk_leak_check_service_unittest.cc
index f44ed4a..039b24e 100644
--- a/components/password_manager/core/browser/bulk_leak_check_service_unittest.cc
+++ b/components/password_manager/core/browser/bulk_leak_check_service_unittest.cc
@@ -240,7 +240,6 @@
   expected_counts
       ["PasswordManager.BulkCheck.CheckedCredentialsOnErrorOrCanceled"] = 1;
   expected_counts["PasswordManager.BulkCheck.Error"] = 1;
-  expected_counts["PasswordManager.BulkCheck.LeaksFoundOnErrorOrCanceled"] = 1;
   EXPECT_THAT(
       histogram_tester().GetTotalCountsForPrefix("PasswordManager.BulkCheck"),
       expected_counts);
@@ -289,8 +288,6 @@
   histogram_tester().ExpectUniqueSample(
       "PasswordManager.BulkCheck.CanceledTime", kMockElapsedTime, 1);
   histogram_tester().ExpectUniqueSample(
-      "PasswordManager.BulkCheck.LeaksFoundOnErrorOrCanceled", 1, 1);
-  histogram_tester().ExpectUniqueSample(
       "PasswordManager.BulkCheck.CheckedCredentialsOnErrorOrCanceled",
       TestCredentials().size(), 1);
 
@@ -446,7 +443,6 @@
   expected_counts
       ["PasswordManager.BulkCheck.CheckedCredentialsOnErrorOrCanceled"] = 1;
   expected_counts["PasswordManager.BulkCheck.Error"] = 1;
-  expected_counts["PasswordManager.BulkCheck.LeaksFoundOnErrorOrCanceled"] = 1;
   EXPECT_THAT(
       histogram_tester().GetTotalCountsForPrefix("PasswordManager.BulkCheck"),
       expected_counts);
@@ -478,7 +474,6 @@
   expected_counts
       ["PasswordManager.BulkCheck.CheckedCredentialsOnErrorOrCanceled"] = 1;
   expected_counts["PasswordManager.BulkCheck.Error"] = 1;
-  expected_counts["PasswordManager.BulkCheck.LeaksFoundOnErrorOrCanceled"] = 1;
   EXPECT_THAT(
       histogram_tester().GetTotalCountsForPrefix("PasswordManager.BulkCheck"),
       expected_counts);
diff --git a/components/permissions/android/BUILD.gn b/components/permissions/android/BUILD.gn
index b474329ef..40b48d59 100644
--- a/components/permissions/android/BUILD.gn
+++ b/components/permissions/android/BUILD.gn
@@ -159,7 +159,7 @@
     ":java_resources",
     "//base:base_java_test_support",
     "//base:base_junit_test_support",
-    "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/theme/android:java_resources",
     "//third_party/android_deps:robolectric_all_java",
     "//third_party/junit",
     "//third_party/mockito:mockito_java",
diff --git a/components/policy/core/browser/BUILD.gn b/components/policy/core/browser/BUILD.gn
index d5c7b9d..aab14c8 100644
--- a/components/policy/core/browser/BUILD.gn
+++ b/components/policy/core/browser/BUILD.gn
@@ -52,6 +52,15 @@
     "webui/policy_status_provider.h",
   ]
 
+  if (!is_chromeos_ash) {
+    sources += [
+      "cloud/user_policy_signin_service_base.cc",
+      "cloud/user_policy_signin_service_base.h",
+      "cloud/user_policy_signin_service_util.cc",
+      "cloud/user_policy_signin_service_util.h",
+    ]
+  }
+
   configs += [ "//components/policy:component_implementation" ]
 
   public_deps = [ "//base" ]
diff --git a/components/policy/core/browser/DEPS b/components/policy/core/browser/DEPS
index a4e3f33e..18ff86b 100644
--- a/components/policy/core/browser/DEPS
+++ b/components/policy/core/browser/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+components/google/core",
+  "+components/keyed_service",
   "+components/pref_registry",
   "+components/reporting",
   "+components/signin",
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_base.cc b/components/policy/core/browser/cloud/user_policy_signin_service_base.cc
similarity index 62%
rename from chrome/browser/policy/cloud/user_policy_signin_service_base.cc
rename to components/policy/core/browser/cloud/user_policy_signin_service_base.cc
index ccbb6bc..a59e1d2 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_base.cc
+++ b/components/policy/core/browser/cloud/user_policy_signin_service_base.cc
@@ -1,8 +1,8 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// Copyright 2022 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/policy/cloud/user_policy_signin_service_base.h"
+#include "components/policy/core/browser/cloud/user_policy_signin_service_base.h"
 
 #include <utility>
 
@@ -12,47 +12,27 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/enterprise/util/managed_browser_utils.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_attributes_entry.h"
-#include "chrome/browser/profiles/profile_attributes_storage.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/signin/account_id_from_account_info.h"
-#include "chrome/common/chrome_content_client.h"
-#include "chrome/common/pref_names.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/identity_manager/account_info.h"
-#include "content/public/browser/notification_source.h"
-#include "content/public/browser/storage_partition.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace policy {
 
 UserPolicySigninServiceBase::UserPolicySigninServiceBase(
-    Profile* profile,
     PrefService* local_state,
     DeviceManagementService* device_management_service,
     UserCloudPolicyManager* policy_manager,
     signin::IdentityManager* identity_manager,
     scoped_refptr<network::SharedURLLoaderFactory> system_url_loader_factory)
-    : profile_(profile),
-      policy_manager_(policy_manager),
+    : policy_manager_(policy_manager),
       identity_manager_(identity_manager),
       local_state_(local_state),
       device_management_service_(device_management_service),
       system_url_loader_factory_(system_url_loader_factory),
-      consent_level_(signin::ConsentLevel::kSignin) {
-  // Register a listener to be called back once the current profile has finished
-  // initializing, so we can startup/shutdown the UserCloudPolicyManager.
-  registrar_.Add(this,
-                 chrome::NOTIFICATION_PROFILE_ADDED,
-                 content::Source<Profile>(profile));
-}
+      consent_level_(signin::ConsentLevel::kSignin) {}
 
 UserPolicySigninServiceBase::~UserPolicySigninServiceBase() {}
 
@@ -84,43 +64,6 @@
   manager->core()->service()->RefreshPolicy(std::move(callback));
 }
 
-void UserPolicySigninServiceBase::OnPrimaryAccountChanged(
-    const signin::PrimaryAccountChangeEvent& event) {
-  if (event.GetEventTypeFor(signin::ConsentLevel::kSignin) ==
-      signin::PrimaryAccountChangeEvent::Type::kCleared) {
-    ProfileManager* profile_manager = g_browser_process->profile_manager();
-    // Some tests do not have a profile manager.
-    if (profile_manager) {
-      ProfileAttributesEntry* entry =
-          profile_manager->GetProfileAttributesStorage()
-              .GetProfileAttributesWithPath(profile_->GetPath());
-      if (entry)
-        entry->SetUserAcceptedAccountManagement(false);
-      ShutdownUserCloudPolicyManager();
-    } else if (event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
-               signin::PrimaryAccountChangeEvent::Type::kCleared) {
-      ShutdownUserCloudPolicyManager();
-    }
-  }
-}
-
-void UserPolicySigninServiceBase::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_PROFILE_ADDED, type);
-
-  // A new profile has been loaded - if it's signed in, then initialize the
-  // UCPM, otherwise shut down the UCPM (which deletes any cached policy
-  // data). This must be done here instead of at constructor time because
-  // the Profile is not fully initialized when this object is constructed
-  // (DoFinalInit() has not yet been called, so ProfileIOData and
-  // SSLConfigServiceManager have not been created yet).
-  // TODO(atwilson): Switch to using a timer instead, to avoid contention
-  // with other services at startup (http://crbug.com/165468).
-  InitializeOnProfileReady(content::Source<Profile>(source).ptr());
-}
-
 void UserPolicySigninServiceBase::
     OnCloudPolicyServiceInitializationCompleted() {
   // This is meant to be overridden by subclasses. Starting and stopping to
@@ -157,8 +100,6 @@
 }
 
 void UserPolicySigninServiceBase::Shutdown() {
-  if (identity_manager())
-    identity_manager()->RemoveObserver(this);
   PrepareForUserCloudPolicyManagerShutdown();
 }
 
@@ -199,32 +140,6 @@
   return !BrowserPolicyConnector::IsNonEnterpriseUser(username);
 }
 
-void UserPolicySigninServiceBase::InitializeOnProfileReady(Profile* profile) {
-  DCHECK_EQ(profile, profile_);
-  // If using a TestingProfile with no IdentityManager or
-  // UserCloudPolicyManager, skip initialization.
-  if (!policy_manager() || !identity_manager()) {
-    DVLOG(1) << "Skipping initialization for tests due to missing components.";
-    return;
-  }
-
-  // Shutdown the UserCloudPolicyManager when the user signs out. We start
-  // observing the IdentityManager here because we don't want to get signout
-  // notifications until after the profile has started initializing
-  // (http://crbug.com/316229).
-  identity_manager()->AddObserver(this);
-
-  AccountId account_id = AccountIdFromAccountInfo(
-      identity_manager()->GetPrimaryAccountInfo(consent_level()));
-  if (!CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/false)) {
-    ShutdownUserCloudPolicyManager();
-  } else {
-    InitializeForSignedInUser(account_id,
-                              profile->GetDefaultStoragePartition()
-                                  ->GetURLLoaderFactoryForBrowserProcess());
-  }
-}
-
 void UserPolicySigninServiceBase::InitializeForSignedInUser(
     const AccountId& account_id,
     scoped_refptr<network::SharedURLLoaderFactory> profile_url_loader_factory) {
@@ -280,15 +195,4 @@
     manager->DisconnectAndRemovePolicy();
 }
 
-bool UserPolicySigninServiceBase::CanApplyPoliciesForSignedInUser(
-    bool check_for_refresh_token) {
-  return (check_for_refresh_token
-              ? identity_manager()->HasPrimaryAccountWithRefreshToken(
-                    signin::ConsentLevel::kSignin)
-              : identity_manager()->HasPrimaryAccount(
-                    signin::ConsentLevel::kSignin)) &&
-         (profile_can_be_managed_for_testing_ ||
-          chrome::enterprise_util::ProfileCanBeManaged(profile()));
-}
-
 }  // namespace policy
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_base.h b/components/policy/core/browser/cloud/user_policy_signin_service_base.h
similarity index 73%
rename from chrome/browser/policy/cloud/user_policy_signin_service_base.h
rename to components/policy/core/browser/cloud/user_policy_signin_service_base.h
index ae24882..bf54691af 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_base.h
+++ b/components/policy/core/browser/cloud/user_policy_signin_service_base.h
@@ -1,9 +1,9 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// Copyright 2022 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
-#define CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
 
 #include <memory>
 #include <string>
@@ -16,12 +16,9 @@
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/cloud_policy_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 
 class AccountId;
 class PrefService;
-class Profile;
 
 namespace network {
 class SharedURLLoaderFactory;
@@ -44,11 +41,10 @@
 //
 // Finally, if the user signs out, this class is responsible for shutting down
 // the policy infrastructure to ensure that any cached policy is cleared.
-class UserPolicySigninServiceBase : public KeyedService,
-                                    public CloudPolicyClient::Observer,
-                                    public CloudPolicyService::Observer,
-                                    public content::NotificationObserver,
-                                    public signin::IdentityManager::Observer {
+class POLICY_EXPORT UserPolicySigninServiceBase
+    : public KeyedService,
+      public CloudPolicyClient::Observer,
+      public CloudPolicyService::Observer {
  public:
   // The callback invoked once policy registration is complete. Passed
   // |dm_token| and |client_id| parameters are empty if policy registration
@@ -63,7 +59,6 @@
 
   // Creates a UserPolicySigninServiceBase associated with the passed |profile|.
   UserPolicySigninServiceBase(
-      Profile* profile,
       PrefService* local_state,
       DeviceManagementService* device_management_service,
       UserCloudPolicyManager* policy_manager,
@@ -86,19 +81,6 @@
       scoped_refptr<network::SharedURLLoaderFactory> profile_url_loader_factory,
       PolicyFetchCallback callback);
 
-  void set_profile_can_be_managed_for_testing(bool can_be_managed) {
-    profile_can_be_managed_for_testing_ = can_be_managed;
-  }
-
-  // signin::IdentityManager::Observer implementation:
-  void OnPrimaryAccountChanged(
-      const signin::PrimaryAccountChangeEvent& event_details) override;
-
-  // content::NotificationObserver implementation:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
   // CloudPolicyService::Observer implementation:
   void OnCloudPolicyServiceInitializationCompleted() override;
 
@@ -121,21 +103,14 @@
   // BrowserPolicyConnector::IsNonEnterpriseUser()).
   bool ShouldLoadPolicyForUser(const std::string& email_address);
 
-  // Invoked to initialize the UserPolicySigninService once its owning Profile
-  // becomes ready. If the Profile has a signed-in account associated with it
-  // at startup then this initializes the cloud policy manager by calling
-  // InitializeForSignedInUser(); otherwise it clears any stored policies.
-  void InitializeOnProfileReady(Profile* profile);
-
   // Invoked to initialize the cloud policy service for |account_id|, which is
   // the account associated with the Profile that owns this service. This is
   // invoked from InitializeOnProfileReady() if the Profile already has a
   // signed-in account at startup, or (on the desktop platforms) as soon as the
   // user signs-in and an OAuth2 login refresh token becomes available.
-  void InitializeForSignedInUser(
-      const AccountId& account_id,
-      scoped_refptr<network::SharedURLLoaderFactory>
-          profile_url_loader_factory);
+  void InitializeForSignedInUser(const AccountId& account_id,
+                                 scoped_refptr<network::SharedURLLoaderFactory>
+                                     profile_url_loader_factory);
 
   // Initializes the cloud policy manager with the passed |client|. This is
   // called from InitializeForSignedInUser() when the Profile already has a
@@ -153,36 +128,27 @@
   // out) and deletes any cached policy.
   virtual void ShutdownUserCloudPolicyManager();
 
-  bool CanApplyPoliciesForSignedInUser(bool check_for_refresh_token);
-
-  Profile* profile() { return profile_; }
   // Convenience helpers to get the associated UserCloudPolicyManager and
   // IdentityManager.
   UserCloudPolicyManager* policy_manager() { return policy_manager_; }
   signin::IdentityManager* identity_manager() { return identity_manager_; }
 
-  content::NotificationRegistrar* registrar() { return &registrar_; }
   signin::ConsentLevel consent_level() const { return consent_level_; }
 
  private:
-  // Parent profile for this service.
-  raw_ptr<Profile> profile_;
   // Weak pointer to the UserCloudPolicyManager and IdentityManager this service
   // is associated with.
   raw_ptr<UserCloudPolicyManager> policy_manager_;
   raw_ptr<signin::IdentityManager> identity_manager_;
 
-  content::NotificationRegistrar registrar_;
-
   raw_ptr<PrefService> local_state_;
   raw_ptr<DeviceManagementService> device_management_service_;
   scoped_refptr<network::SharedURLLoaderFactory> system_url_loader_factory_;
 
   signin::ConsentLevel consent_level_;
-  bool profile_can_be_managed_for_testing_ = false;
   base::WeakPtrFactory<UserPolicySigninServiceBase> weak_factory_{this};
 };
 
 }  // namespace policy
 
-#endif  // CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
+#endif  // COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
diff --git a/components/policy/core/browser/cloud/user_policy_signin_service_util.cc b/components/policy/core/browser/cloud/user_policy_signin_service_util.cc
new file mode 100644
index 0000000..b8c14a0b
--- /dev/null
+++ b/components/policy/core/browser/cloud/user_policy_signin_service_util.cc
@@ -0,0 +1,39 @@
+// Copyright 2022 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/policy/core/browser/cloud/user_policy_signin_service_util.h"
+
+#include "components/signin/public/base/consent_level.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+
+namespace policy {
+
+bool IsSignoutEvent(const signin::PrimaryAccountChangeEvent& event) {
+  return event.GetEventTypeFor(signin::ConsentLevel::kSignin) ==
+         signin::PrimaryAccountChangeEvent::Type::kCleared;
+}
+
+bool IsTurnOffSyncEvent(const signin::PrimaryAccountChangeEvent& event) {
+  return event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
+         signin::PrimaryAccountChangeEvent::Type::kCleared;
+}
+
+bool IsAnySigninEvent(const signin::PrimaryAccountChangeEvent& event) {
+  return event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
+             signin::PrimaryAccountChangeEvent::Type::kSet ||
+         event.GetEventTypeFor(signin::ConsentLevel::kSignin) ==
+             signin::PrimaryAccountChangeEvent::Type::kSet;
+}
+
+bool CanApplyPoliciesForSignedInUser(
+    bool check_for_refresh_token,
+    signin::IdentityManager* identity_manager) {
+  return (
+      check_for_refresh_token
+          ? identity_manager->HasPrimaryAccountWithRefreshToken(
+                signin::ConsentLevel::kSignin)
+          : identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin));
+}
+
+}  // namespace policy
diff --git a/components/policy/core/browser/cloud/user_policy_signin_service_util.h b/components/policy/core/browser/cloud/user_policy_signin_service_util.h
new file mode 100644
index 0000000..a3e22a9
--- /dev/null
+++ b/components/policy/core/browser/cloud/user_policy_signin_service_util.h
@@ -0,0 +1,36 @@
+// Copyright 2022 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_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
+
+#include "components/policy/policy_export.h"
+#include "components/signin/public/identity_manager/primary_account_change_event.h"
+
+namespace signin {
+class IdentityManager;
+}  // namespace signin
+
+namespace policy {
+
+// Returns true if the account was signed out.
+POLICY_EXPORT bool IsSignoutEvent(
+    const signin::PrimaryAccountChangeEvent& event);
+
+// Returns true if sync was turned off for the account.
+POLICY_EXPORT bool IsTurnOffSyncEvent(
+    const signin::PrimaryAccountChangeEvent& event);
+
+// Returns true if the event is related to sign-in.
+POLICY_EXPORT bool IsAnySigninEvent(
+    const signin::PrimaryAccountChangeEvent& event);
+
+// Returns true if policies can be applied for the signed in user.
+POLICY_EXPORT bool CanApplyPoliciesForSignedInUser(
+    bool check_for_refresh_token,
+    signin::IdentityManager* identity_manager);
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
diff --git a/components/policy/core/common/policy_map.cc b/components/policy/core/common/policy_map.cc
index 70a2af7..dc42d9b53 100644
--- a/components/policy/core/common/policy_map.cc
+++ b/components/policy/core/common/policy_map.cc
@@ -656,16 +656,18 @@
 
 void PolicyMap::UpdateStoredComputedMetapolicies() {
   cloud_policy_overrides_platform_policy_ =
-      GetValue(key::kCloudPolicyOverridesPlatformPolicy) &&
-      GetValue(key::kCloudPolicyOverridesPlatformPolicy)
-          ->GetIfBool()
-          .value_or(false);
+      GetValue(key::kCloudPolicyOverridesPlatformPolicy,
+               base::Value::Type::BOOLEAN) &&
+      GetValue(key::kCloudPolicyOverridesPlatformPolicy,
+               base::Value::Type::BOOLEAN)
+          ->GetBool();
 
   cloud_user_policy_overrides_cloud_machine_policy_ =
-      GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy) &&
-      GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy)
-          ->GetIfBool()
-          .value_or(false);
+      GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+               base::Value::Type::BOOLEAN) &&
+      GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+               base::Value::Type::BOOLEAN)
+          ->GetBool();
 }
 
 void PolicyMap::UpdateStoredUserAffiliation() {
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index ebc2524..781a1310 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -8284,7 +8284,7 @@
       'owners': ['tluk@chromium.org', 'chrome-cros@google.com'],
       'type': 'main',
       'schema' : {'type': 'boolean'},
-      'supported_on': ['chrome_os:96-'],
+      'supported_on': ['chrome_os:96-', 'chrome.*:101-'],
       'example_value': False,
       'features':{
         'dynamic_refresh': False,
@@ -8293,20 +8293,20 @@
       'items': [
         {
           'value': True,
-          'caption': 'Enable showing <ph name="GOOGLE_SEARCH_PRODUCT_NAME">Google Search</ph> results in a Browser side panel.',
+          'caption': 'Enable showing default search engine results pages in a Browser side panel.',
         },
         {
           'value': False,
-          'caption': 'Disable showing <ph name="GOOGLE_SEARCH_PRODUCT_NAME">Google Search</ph> results in a Browser side panel.',
+          'caption': 'Disable showing default search engine results pages in a Browser side panel.',
         },
       ],
       'id': 906,
       'default': True,
-      'caption': '''Allow showing the most recent <ph name="GOOGLE_SEARCH_PRODUCT_NAME">Google Search</ph> results in a Browser side panel''',
+      'caption': '''Allow showing the most recent default search engine results page in a Browser side panel''',
       'tags': [],
-      'desc': '''Setting the policy to Enabled or leaving the policy unset means that users can bring up their most recent <ph name="GOOGLE_SEARCH_PRODUCT_NAME">Google Search</ph> results in a side panel via toggling an icon in the toolbar.
+      'desc': '''Setting the policy to Enabled or leaving the policy unset means that users can bring up their most recent default search engine results page in a side panel via toggling an icon in the toolbar.
 
-      Setting the policy to Disabled removes the icon from the toolbar that opens the side panel with the <ph name="GOOGLE_SEARCH_PRODUCT_NAME">Google Search</ph> results.''',
+      Setting the policy to Disabled removes the icon from the toolbar that opens the side panel with the default search engine results page.''',
     },
     {
       'name': 'InsecurePrivateNetworkRequestsAllowed',
diff --git a/components/privacy_sandbox/privacy_sandbox_features.cc b/components/privacy_sandbox/privacy_sandbox_features.cc
index 5a274e7..68f0b09 100644
--- a/components/privacy_sandbox/privacy_sandbox_features.cc
+++ b/components/privacy_sandbox/privacy_sandbox_features.cc
@@ -18,5 +18,7 @@
 const base::FeatureParam<bool>
     kPrivacySandboxSettings3ForceShowNoticeForTesting{
         &kPrivacySandboxSettings3, "force-show-notice-for-testing", false};
+const base::FeatureParam<bool> kPrivacySandboxSettings3ShowSampleDataForTesting{
+    &kPrivacySandboxSettings3, "show-sample-data", false};
 
 }  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_features.h b/components/privacy_sandbox/privacy_sandbox_features.h
index 225acc7..a724071 100644
--- a/components/privacy_sandbox/privacy_sandbox_features.h
+++ b/components/privacy_sandbox/privacy_sandbox_features.h
@@ -24,6 +24,8 @@
     kPrivacySandboxSettings3ForceShowConsentForTesting;
 extern const base::FeatureParam<bool>
     kPrivacySandboxSettings3ForceShowNoticeForTesting;
+extern const base::FeatureParam<bool>
+    kPrivacySandboxSettings3ShowSampleDataForTesting;
 
 }  // namespace privacy_sandbox
 
diff --git a/components/resources/default_100_percent/chromium/product_logo.png b/components/resources/default_100_percent/chromium/product_logo.png
index f0c3470..7b60bd8c 100644
--- a/components/resources/default_100_percent/chromium/product_logo.png
+++ b/components/resources/default_100_percent/chromium/product_logo.png
Binary files differ
diff --git a/components/resources/default_100_percent/chromium/product_logo_white.png b/components/resources/default_100_percent/chromium/product_logo_white.png
index 454b33f..854f414e 100644
--- a/components/resources/default_100_percent/chromium/product_logo_white.png
+++ b/components/resources/default_100_percent/chromium/product_logo_white.png
Binary files differ
diff --git a/components/resources/default_200_percent/chromium/product_logo.png b/components/resources/default_200_percent/chromium/product_logo.png
index 52a636ef..876c2922 100644
--- a/components/resources/default_200_percent/chromium/product_logo.png
+++ b/components/resources/default_200_percent/chromium/product_logo.png
Binary files differ
diff --git a/components/resources/default_200_percent/chromium/product_logo_white.png b/components/resources/default_200_percent/chromium/product_logo_white.png
index 0cdf603..fffaf67 100644
--- a/components/resources/default_200_percent/chromium/product_logo_white.png
+++ b/components/resources/default_200_percent/chromium/product_logo_white.png
Binary files differ
diff --git a/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc b/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc
index d7e5acb..469e424 100644
--- a/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc
+++ b/components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc
@@ -41,7 +41,7 @@
 namespace safe_browsing {
 namespace {
 
-const int kRepeatingCheckTailoredSecurityBitDelayInMinutes = 5;
+const int kRepeatingCheckTailoredSecurityBitDelayInMinutes = 10;
 
 constexpr char kAPIScope[] =
     "https://www.googleapis.com/auth/chrome-safe-browsing";
diff --git a/components/segmentation_platform/internal/data_collection/training_data_collector.cc b/components/segmentation_platform/internal/data_collection/training_data_collector.cc
index fadeee8c..7302b516 100644
--- a/components/segmentation_platform/internal/data_collection/training_data_collector.cc
+++ b/components/segmentation_platform/internal/data_collection/training_data_collector.cc
@@ -17,6 +17,7 @@
 #include "components/segmentation_platform/internal/data_collection/dummy_training_data_collector.h"
 #include "components/segmentation_platform/internal/database/metadata_utils.h"
 #include "components/segmentation_platform/internal/database/segment_info_database.h"
+#include "components/segmentation_platform/internal/database/signal_storage_config.h"
 #include "components/segmentation_platform/internal/execution/feature_list_query_processor.h"
 #include "components/segmentation_platform/internal/proto/model_metadata.pb.h"
 #include "components/segmentation_platform/internal/proto/model_prediction.pb.h"
@@ -54,10 +55,12 @@
   TrainingDataCollectorImpl(SegmentInfoDatabase* segment_info_database,
                             FeatureListQueryProcessor* processor,
                             HistogramSignalHandler* histogram_signal_handler,
+                            SignalStorageConfig* signal_storage_config,
                             base::Clock* clock)
       : segment_info_database_(segment_info_database),
         feature_list_query_processor_(processor),
         histogram_signal_handler_(histogram_signal_handler),
+        signal_storage_config_(signal_storage_config),
         clock_(clock) {}
 
   ~TrainingDataCollectorImpl() override {
@@ -140,7 +143,7 @@
       if (hash_index_map.find(output_metric_hash) == hash_index_map.end())
         continue;
 
-      if (!segment_info.has_model_version())
+      if (!CanReportImmediateTrainingData(segment.second))
         continue;
 
       // Generate training data input.
@@ -158,6 +161,31 @@
     }
   }
 
+  bool CanReportImmediateTrainingData(const proto::SegmentInfo& segment_info) {
+    if (!segment_info.has_model_version() ||
+        !segment_info.has_model_update_time_s() ||
+        segment_info.model_update_time_s() == 0) {
+      return false;
+    }
+
+    base::TimeDelta min_signal_collection_length =
+        segment_info.model_metadata().min_signal_collection_length() *
+        metadata_utils::GetTimeUnit(segment_info.model_metadata());
+    base::Time model_update_time = base::Time::FromDeltaSinceWindowsEpoch(
+        base::Seconds(segment_info.model_update_time_s()));
+
+    // Data must be collected for enough time after a new model is downloaded.
+    // It's recommended to get the A/B testing experiment fully ramped up before
+    // deploying a new model. Or the data collected might be partially based on
+    // old behavior of Chrome.
+    if (model_update_time + min_signal_collection_length >= clock_->Now())
+      return false;
+
+    // Each input must be collected for enough time.
+    return signal_storage_config_->MeetsSignalCollectionRequirement(
+        segment_info.model_metadata());
+  }
+
   void OnGetInputTensor(float output_value,
                         int output_index,
                         OptimizationTarget segment_id,
@@ -178,6 +206,7 @@
   raw_ptr<SegmentInfoDatabase> segment_info_database_;
   raw_ptr<FeatureListQueryProcessor> feature_list_query_processor_;
   raw_ptr<HistogramSignalHandler> histogram_signal_handler_;
+  raw_ptr<SignalStorageConfig> signal_storage_config_;
   raw_ptr<base::Clock> clock_;
 
   // Hash of histograms for immediate training data collection. When any
@@ -195,11 +224,13 @@
     SegmentInfoDatabase* segment_info_database,
     FeatureListQueryProcessor* processor,
     HistogramSignalHandler* histogram_signal_handler,
+    SignalStorageConfig* signal_storage_config,
     base::Clock* clock) {
   if (base::FeatureList::IsEnabled(
           features::kSegmentationStructuredMetricsFeature)) {
     return std::make_unique<TrainingDataCollectorImpl>(
-        segment_info_database, processor, histogram_signal_handler, clock);
+        segment_info_database, processor, histogram_signal_handler,
+        signal_storage_config, clock);
   }
 
   return std::make_unique<DummyTrainingDataCollector>();
diff --git a/components/segmentation_platform/internal/data_collection/training_data_collector.h b/components/segmentation_platform/internal/data_collection/training_data_collector.h
index bfc2df6a..d25717c 100644
--- a/components/segmentation_platform/internal/data_collection/training_data_collector.h
+++ b/components/segmentation_platform/internal/data_collection/training_data_collector.h
@@ -18,6 +18,7 @@
 class FeatureListQueryProcessor;
 class HistogramSignalHandler;
 class SegmentInfoDatabase;
+class SignalStorageConfig;
 
 // Collect training data and report as Ukm message. Live on main thread.
 // TODO(xingliu): Make a new class that owns the training data collector and
@@ -28,6 +29,7 @@
       SegmentInfoDatabase* segment_info_database,
       FeatureListQueryProcessor* processor,
       HistogramSignalHandler* histogram_signal_handler,
+      SignalStorageConfig* signal_storage_config,
       base::Clock* clock);
 
   // Called when model metadata is updated. May result in training data
diff --git a/components/segmentation_platform/internal/data_collection/training_data_collector_unittest.cc b/components/segmentation_platform/internal/data_collection/training_data_collector_unittest.cc
index b0e3617..50ffce9 100644
--- a/components/segmentation_platform/internal/data_collection/training_data_collector_unittest.cc
+++ b/components/segmentation_platform/internal/data_collection/training_data_collector_unittest.cc
@@ -11,6 +11,8 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
 #include "base/test/task_environment.h"
+#include "components/segmentation_platform/internal/database/metadata_utils.h"
+#include "components/segmentation_platform/internal/database/mock_signal_storage_config.h"
 #include "components/segmentation_platform/internal/database/test_segment_info_database.h"
 #include "components/segmentation_platform/internal/execution/mock_feature_list_query_processor.h"
 #include "components/segmentation_platform/internal/proto/model_metadata.pb.h"
@@ -26,6 +28,7 @@
 using ::base::test::RunOnceCallback;
 using ::testing::_;
 using ::testing::NiceMock;
+using ::testing::Return;
 using Segmentation_ModelExecution =
     ::ukm::builders::Segmentation_ModelExecution;
 
@@ -59,11 +62,13 @@
     std::vector<float> inputs({1.f});
     ON_CALL(feature_list_processor_, ProcessFeatureList(_, _, _, _))
         .WillByDefault(RunOnceCallback<3>(true, inputs));
+    ON_CALL(signal_storage_config_, MeetsSignalCollectionRequirement(_))
+        .WillByDefault(Return(true));
 
     test_segment_info_db_ = std::make_unique<test::TestSegmentInfoDatabase>();
     collector_ = TrainingDataCollector::Create(
         test_segment_info_db_.get(), &feature_list_processor_,
-        &histogram_signal_handler_, &clock_);
+        &histogram_signal_handler_, &signal_storage_config_, &clock_);
   }
 
  protected:
@@ -72,8 +77,12 @@
     return test_segment_info_db_.get();
   }
   base::test::TaskEnvironment* task_environment() { return &task_environment_; }
+  base::SimpleTestClock* clock() { return &clock_; }
+  MockSignalStorageConfig* signal_storage_config() {
+    return &signal_storage_config_;
+  }
 
-  void CreateSegmentInfo() {
+  proto::SegmentInfo* CreateSegmentInfo() {
     test_segment_db()->AddUserActionFeature(kTestOptimizationTarget0, "action",
                                             1, 1, proto::Aggregation::COUNT);
     // Segment 0 contains 1 immediate collection uma output for for
@@ -83,6 +92,7 @@
     AddOutput(segment_info, kHistogramName0);
     proto::TrainingOutput* output1 = AddOutput(segment_info, kHistogramName1);
     output1->mutable_uma_output()->set_duration(1u);
+    return segment_info;
   }
 
   proto::SegmentInfo* CreateSegment(OptimizationTarget optimization_target) {
@@ -90,6 +100,9 @@
         test_segment_db()->FindOrCreateSegment(optimization_target);
     segment_info->mutable_model_metadata()->set_time_unit(proto::TimeUnit::DAY);
     segment_info->set_model_version(kModelVersion);
+    auto model_update_time = clock()->Now() - base::Days(365);
+    segment_info->set_model_update_time_s(
+        model_update_time.ToDeltaSinceWindowsEpoch().InSeconds());
     return segment_info;
   }
 
@@ -144,6 +157,7 @@
   ukm::TestAutoSetUkmRecorder test_recorder_;
   NiceMock<MockFeatureListQueryProcessor> feature_list_processor_;
   NiceMock<MockHistogramSignalHandler> histogram_signal_handler_;
+  NiceMock<MockSignalStorageConfig> signal_storage_config_;
   std::unique_ptr<test::TestSegmentInfoDatabase> test_segment_info_db_;
   std::unique_ptr<TrainingDataCollector> collector_;
 };
@@ -156,7 +170,7 @@
   ExpectUkmCount(0u);
 }
 
-// No segment info in database. Do nothing.
+// Histogram not in the output list will not trigger a training data report..
 TEST_F(TrainingDataCollectorTest, IrrelevantHistogramNotReported) {
   CreateSegmentInfo();
   Init();
@@ -170,6 +184,8 @@
   ExpectUkmCount(0u);
 }
 
+// Immediate training data collection for a certain histogram will be reported
+// as a UKM.
 TEST_F(TrainingDataCollectorTest, HistogramImmediatelyReported) {
   CreateSegmentInfo();
   Init();
@@ -181,6 +197,7 @@
              SegmentationUkmHelper::FloatToInt64(kSample)});
 }
 
+// A histogram interested by multiple model will trigger multiple UKM reports.
 TEST_F(TrainingDataCollectorTest, HistogramImmediatelyReported_MultipleModel) {
   CreateSegmentInfo();
   // Segment 1 contains 1 immediate collection uma output for for
@@ -192,5 +209,36 @@
   ExpectUkmCount(2u);
 }
 
+// No UKM report due to minimum data collection time not met.
+TEST_F(TrainingDataCollectorTest, SignalCollectionRequirementNotMet) {
+  EXPECT_CALL(*signal_storage_config(), MeetsSignalCollectionRequirement(_))
+      .WillOnce(Return(false));
+
+  CreateSegmentInfo();
+  Init();
+  collector()->OnHistogramSignalUpdated(kHistogramName0, kSample);
+  task_environment()->RunUntilIdle();
+  ExpectUkmCount(0u);
+}
+
+// No UKM report due to model updated recently.
+TEST_F(TrainingDataCollectorTest, ModelUpdatedRecently) {
+  auto* segment_info = CreateSegmentInfo();
+  base::TimeDelta min_signal_collection_length =
+      segment_info->model_metadata().min_signal_collection_length() *
+      metadata_utils::GetTimeUnit(segment_info->model_metadata());
+
+  // Set the model update timestamp to be closer to Now().
+  segment_info->set_model_update_time_s(
+      (clock()->Now() - min_signal_collection_length + base::Seconds(30))
+          .ToDeltaSinceWindowsEpoch()
+          .InSeconds());
+
+  Init();
+  collector()->OnHistogramSignalUpdated(kHistogramName0, kSample);
+  task_environment()->RunUntilIdle();
+  ExpectUkmCount(0u);
+}
+
 }  // namespace
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
index 700358a..b91ef47 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
@@ -246,7 +246,7 @@
 
   training_data_collector_ = TrainingDataCollector::Create(
       segment_info_database_.get(), feature_list_query_processor_.get(),
-      histogram_signal_handler_.get(), clock_);
+      histogram_signal_handler_.get(), signal_storage_config_.get(), clock_);
   training_data_collector_->OnServiceInitialized();
 
   model_execution_manager_ = CreateModelExecutionManager(
diff --git a/components/signin/core/browser/account_reconcilor.cc b/components/signin/core/browser/account_reconcilor.cc
index ca48fbc..250fc43 100644
--- a/components/signin/core/browser/account_reconcilor.cc
+++ b/components/signin/core/browser/account_reconcilor.cc
@@ -230,11 +230,12 @@
 }
 
 void AccountReconcilor::DisableReconcile(bool logout_all_accounts) {
+  const bool log_out_in_progress = log_out_in_progress_;
   AbortReconcile();
   SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_INACTIVE);
   UnregisterWithAllDependencies();
 
-  if (logout_all_accounts)
+  if (logout_all_accounts && !log_out_in_progress)
     PerformLogoutAllAccountsAction();
 }
 
@@ -596,7 +597,8 @@
   std::vector<CoreAccountId> chrome_accounts =
       LoadValidAccountsFromTokenService();
 
-  if (delegate_->ShouldAbortReconcileIfPrimaryHasError() &&
+  if (!primary_account.empty() &&
+      delegate_->ShouldAbortReconcileIfPrimaryHasError() &&
       !base::Contains(chrome_accounts, primary_account)) {
     VLOG(1) << "Primary account has error, abort.";
     DCHECK(is_reconcile_started_);
diff --git a/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc b/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
index 255d8e5..0d9458f 100644
--- a/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
+++ b/components/signin/internal/identity_manager/fake_profile_oauth2_token_service_delegate.cc
@@ -92,6 +92,12 @@
 
 void FakeProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() {
   std::vector<CoreAccountId> account_ids = GetAccounts();
+  if (account_ids.empty())
+    return;
+
+  // Use `ScopedBatchChange` so that `OnEndBatchOfRefreshTokenStateChanges()` is
+  // fired only once, like in production.
+  ScopedBatchChange batch(this);
   for (const auto& account : account_ids)
     RevokeCredentials(account);
 }
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.h b/content/browser/accessibility/browser_accessibility_cocoa.h
index 31ac270..27ef520 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.h
+++ b/content/browser/accessibility/browser_accessibility_cocoa.h
@@ -163,7 +163,6 @@
 @property(nonatomic, readonly) NSString* subrole;
 // The tabs owned by a tablist.
 @property(nonatomic, readonly) NSArray* tabs;
-@property(nonatomic, readonly) NSString* title;
 @property(nonatomic, readonly) NSString* value;
 @property(nonatomic, readonly) NSString* valueDescription;
 @property(nonatomic, readonly) NSValue* visibleCharacterRange;
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index 177866b..7a83157f 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -637,7 +637,6 @@
       {NSAccessibilitySortDirectionAttribute, @"sortDirection"},
       {NSAccessibilitySubroleAttribute, @"subrole"},
       {NSAccessibilityTabsAttribute, @"tabs"},
-      {NSAccessibilityTitleAttribute, @"title"},
       {NSAccessibilityTopLevelUIElementAttribute, @"window"},
       {NSAccessibilityValueAttribute, @"value"},
       {NSAccessibilityValueAutofillAvailableAttribute,
@@ -1525,36 +1524,6 @@
   return tabSubtree;
 }
 
-- (NSString*)AXTitle {
-  return [self title];
-}
-- (NSString*)title {
-  if (![self instanceActive])
-    return nil;
-  // Mac OS X wants static text exposed in AXValue.
-  if (ui::IsNameExposedInAXValueForRole([self internalRole]))
-    return @"";
-
-  if ([self isNameFromLabel])
-    return @"";
-
-  // If we're exposing the title in TitleUIElement, don't also redundantly
-  // expose it in AXDescription.
-  if ([self titleUIElement])
-    return @"";
-
-  ax::mojom::NameFrom nameFrom = static_cast<ax::mojom::NameFrom>(
-      _owner->GetIntAttribute(ax::mojom::IntAttribute::kNameFrom));
-
-  // On Mac OS X, cell titles are "" if it it came from content.
-  NSString* role = [self role];
-  if ([role isEqualToString:NSAccessibilityCellRole] &&
-      nameFrom == ax::mojom::NameFrom::kContents)
-    return @"";
-
-  return base::SysUTF8ToNSString(_owner->GetName());
-}
-
 - (id)AXValue {
   return [self value];
 }
@@ -2492,7 +2461,6 @@
                        NSAccessibilitySizeAttribute,
                        NSAccessibilityStartTextMarkerAttribute,
                        NSAccessibilitySubroleAttribute,
-                       NSAccessibilityTitleAttribute,
                        NSAccessibilityTopLevelUIElementAttribute,
                        NSAccessibilityValueAttribute,
                        NSAccessibilityWindowAttribute, nil];
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 125cca7..62253dc 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -1806,7 +1806,8 @@
 
 BrowserAccessibility* BrowserAccessibilityManager::AXTreeHitTest(
     const gfx::Point& blink_screen_point) const {
-  DCHECK(IsRootTree());
+  // TODO(crbug.com/1287526): assert that this gets called on a valid node. This
+  // should usually be the root node except for Paint Preview.
   DCHECK(cached_node_rtree_);
 
   std::vector<ui::AXNodeID> results;
diff --git a/content/browser/android/gesture_listener_manager.cc b/content/browser/android/gesture_listener_manager.cc
index 0770176..17167bf 100644
--- a/content/browser/android/gesture_listener_manager.cc
+++ b/content/browser/android/gesture_listener_manager.cc
@@ -11,8 +11,10 @@
 #include "content/public/android/content_jni_headers/GestureListenerManagerImpl_jni.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/use_zoom_for_dsf_policy.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "ui/events/android/gesture_event_type.h"
+#include "ui/events/blink/did_overscroll_params.h"
 #include "ui/gfx/geometry/size_f.h"
 
 using blink::WebGestureEvent;
@@ -200,6 +202,22 @@
       gesture.PositionInWidget().y() * dip_scale);
 }
 
+void GestureListenerManager::DidOverscroll(
+    const ui::DidOverscrollParams& params) {
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+  if (j_obj.is_null())
+    return;
+  float x = params.accumulated_overscroll.x();
+  float y = params.accumulated_overscroll.y();
+  if (!IsUseZoomForDSFEnabled()) {
+    float dip_scale = web_contents_->GetNativeView()->GetDipScale();
+    x *= dip_scale;
+    y *= dip_scale;
+  }
+  return Java_GestureListenerManagerImpl_didOverscroll(env, j_obj, x, y);
+}
+
 // All positions and sizes (except |top_shown_pix|) are in CSS pixels.
 // Note that viewport_width/height is a best effort based.
 void GestureListenerManager::UpdateScrollInfo(const gfx::PointF& scroll_offset,
diff --git a/content/browser/android/gesture_listener_manager.h b/content/browser/android/gesture_listener_manager.h
index d46229f3..2b4d45b 100644
--- a/content/browser/android/gesture_listener_manager.h
+++ b/content/browser/android/gesture_listener_manager.h
@@ -21,6 +21,10 @@
 class PointF;
 }  // namespace gfx
 
+namespace ui {
+struct DidOverscrollParams;
+}
+
 namespace content {
 
 class NavigationHandle;
@@ -54,6 +58,7 @@
                        blink::mojom::InputEventResultState ack_result);
   void DidStopFlinging();
   bool FilterInputEvent(const blink::WebInputEvent& event);
+  void DidOverscroll(const ui::DidOverscrollParams& params);
 
   // All sizes and offsets are in CSS pixels (except |top_show_pix|)
   // as cached by the renderer.
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
index 06b6f1a..6d190cd 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
@@ -14,16 +14,10 @@
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/browser/attribution_reporting/attribution_host_utils.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
-#include "content/browser/attribution_reporting/attribution_metrics.h"
 #include "content/browser/attribution_reporting/attribution_reporting.pb.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
 #include "content/browser/attribution_reporting/storable_source.h"
-#include "content/browser/storage_partition_impl.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/content_browser_client.h"
-#include "content/public/common/content_client.h"
-#include "net/base/schemeful_site.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/attribution_reporting/constants.h"
 #include "url/origin.h"
@@ -51,11 +45,8 @@
 }  // namespace
 
 AttributionDataHostManagerImpl::AttributionDataHostManagerImpl(
-    BrowserContext* browser_context,
     AttributionManager* attribution_manager)
-    : browser_context_(browser_context),
-      attribution_manager_(attribution_manager) {
-  DCHECK(browser_context_);
+    : attribution_manager_(attribution_manager) {
   DCHECK(attribution_manager_);
 
   // It's safe to use `base::Unretained()` as `receivers_` is owned by `this`
@@ -91,16 +82,6 @@
   base::Time source_time = base::Time::Now();
   const url::Origin& reporting_origin = data->reporting_origin;
 
-  const bool allowed =
-      GetContentClient()->browser()->IsConversionMeasurementOperationAllowed(
-          browser_context_,
-          ContentBrowserClient::ConversionMeasurementOperation::kImpression,
-          &context.context_origin, /*conversion_origin=*/nullptr,
-          &reporting_origin);
-  RecordRegisterImpressionAllowed(allowed);
-  if (!allowed)
-    return;
-
   // The API is only allowed in secure contexts.
   if (!attribution_host_utils::IsOriginTrustworthyForAttributions(
           context.context_origin) ||
@@ -148,16 +129,6 @@
   const FrozenContext& context = receivers_.current_context();
   const url::Origin& reporting_origin = data->reporting_origin;
 
-  const bool allowed =
-      GetContentClient()->browser()->IsConversionMeasurementOperationAllowed(
-          browser_context_,
-          ContentBrowserClient::ConversionMeasurementOperation::kConversion,
-          /*impression_origin=*/nullptr,
-          /*conversion_origin=*/&context.context_origin, &reporting_origin);
-  RecordRegisterConversionAllowed(allowed);
-  if (!allowed)
-    return;
-
   // The API is only allowed in secure contexts.
   if (!attribution_host_utils::IsOriginTrustworthyForAttributions(
           context.context_origin) ||
@@ -200,8 +171,8 @@
   }
 
   AttributionTrigger trigger(
-      /*conversion_destination=*/net::SchemefulSite(context.context_origin),
-      reporting_origin, std::move(*filters),
+      /*destination_origin=*/context.context_origin, reporting_origin,
+      std::move(*filters),
       data->debug_key ? absl::make_optional(data->debug_key->value)
                       : absl::nullopt,
       std::move(event_triggers));
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.h b/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
index 80aa3aa4..dd37549 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
@@ -17,7 +17,6 @@
 namespace content {
 
 class AttributionManager;
-class BrowserContext;
 
 // Manages a receiver set of all ongoing `AttributionDataHost`s and forwards
 // events to the AttributionManager which owns `this`. Because attributionsrc
@@ -28,8 +27,8 @@
     : public AttributionDataHostManager,
       public blink::mojom::AttributionDataHost {
  public:
-  AttributionDataHostManagerImpl(BrowserContext* storage_partition,
-                                 AttributionManager* attribution_manager);
+  explicit AttributionDataHostManagerImpl(
+      AttributionManager* attribution_manager);
   AttributionDataHostManagerImpl(const AttributionDataHostManager& other) =
       delete;
   AttributionDataHostManagerImpl& operator=(
@@ -68,10 +67,6 @@
 
   void OnDataHostDisconnected();
 
-  // Safe because the owning `AttributionManager` is guaranteed to outlive the
-  // browser context.
-  raw_ptr<BrowserContext> browser_context_;
-
   // Owns `this`.
   raw_ptr<AttributionManager> attribution_manager_;
 
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
index 51e88d27..a321bb0 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
@@ -13,15 +13,11 @@
 
 #include "base/containers/flat_map.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/test/metrics/histogram_tester.h"
 #include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
 #include "content/browser/attribution_reporting/attribution_source_type.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/common/content_client.h"
 #include "content/public/test/browser_task_environment.h"
-#include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_utils.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -35,19 +31,13 @@
 
 namespace {
 
-using ConversionMeasurementOperation =
-    ::content::ContentBrowserClient::ConversionMeasurementOperation;
-
 using ::testing::_;
 using ::testing::AllOf;
 using ::testing::ElementsAre;
 using ::testing::Eq;
 using ::testing::InSequence;
-using ::testing::IsNull;
 using ::testing::Mock;
 using ::testing::Optional;
-using ::testing::Pointee;
-using ::testing::Return;
 
 using Checkpoint = ::testing::MockFunction<void(int step)>;
 
@@ -57,19 +47,15 @@
  public:
   AttributionDataHostManagerImplTest()
       : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
-        browser_context_(std::make_unique<TestBrowserContext>()),
-        data_host_manager_(browser_context_.get(), &mock_manager_) {}
+        data_host_manager_(&mock_manager_) {}
 
  protected:
   BrowserTaskEnvironment task_environment_;
-  std::unique_ptr<TestBrowserContext> browser_context_;
   MockAttributionManager mock_manager_;
   AttributionDataHostManagerImpl data_host_manager_;
 };
 
 TEST_F(AttributionDataHostManagerImplTest, SourceDataHost_SourceRegistered) {
-  base::HistogramTester histograms;
-
   auto page_origin = url::Origin::Create(GURL("https://page.example"));
   auto destination_origin =
       url::Origin::Create(GURL("https://trigger.example"));
@@ -107,9 +93,6 @@
           .Build();
   data_host_remote->SourceDataAvailable(std::move(source_data));
   data_host_remote.FlushForTesting();
-
-  histograms.ExpectUniqueSample("Conversions.RegisterImpressionAllowed", true,
-                                1);
 }
 
 TEST_F(AttributionDataHostManagerImplTest,
@@ -243,43 +226,6 @@
 }
 
 TEST_F(AttributionDataHostManagerImplTest,
-       SourceDataHostEmbedderDisallow_SourceDropped) {
-  base::HistogramTester histograms;
-
-  EXPECT_CALL(mock_manager_, HandleSource).Times(0);
-
-  auto page_origin = url::Origin::Create(GURL("https://page.example"));
-  auto destination_origin =
-      url::Origin::Create(GURL("https://trigger.example"));
-  auto reporting_origin = url::Origin::Create(GURL("https://reporter.example"));
-
-  MockAttributionReportingContentBrowserClient browser_client;
-  EXPECT_CALL(browser_client,
-              IsConversionMeasurementOperationAllowed(
-                  _, ConversionMeasurementOperation::kImpression,
-                  Pointee(page_origin), IsNull(), Pointee(reporting_origin)))
-      .Times(1)
-      .WillRepeatedly(Return(false));
-  ScopedContentBrowserClientSetting setting(&browser_client);
-  mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
-  data_host_manager_.RegisterDataHost(
-      data_host_remote.BindNewPipeAndPassReceiver(), page_origin);
-
-  auto source_data = blink::mojom::AttributionSourceData::New();
-  source_data->source_event_id = 10;
-  source_data->destination = destination_origin;
-  source_data->reporting_origin = reporting_origin;
-  source_data->filter_data = blink::mojom::AttributionFilterData::New();
-  source_data->aggregatable_source =
-      blink::mojom::AttributionAggregatableSource::New();
-  data_host_remote->SourceDataAvailable(std::move(source_data));
-  data_host_remote.FlushForTesting();
-
-  histograms.ExpectUniqueSample("Conversions.RegisterImpressionAllowed", false,
-                                1);
-}
-
-TEST_F(AttributionDataHostManagerImplTest,
        SourceDataHost_ReceiverDestinationCheckPerformed) {
   Checkpoint checkpoint;
   {
@@ -387,15 +333,13 @@
 }
 
 TEST_F(AttributionDataHostManagerImplTest, TriggerDataHost_TriggerRegistered) {
-  base::HistogramTester histograms;
-
   auto destination_origin =
       url::Origin::Create(GURL("https://trigger.example"));
   auto reporting_origin = url::Origin::Create(GURL("https://reporter.example"));
   EXPECT_CALL(
       mock_manager_,
       HandleTrigger(AttributionTriggerMatches({
-          .conversion_destination = net::SchemefulSite(destination_origin),
+          .destination_origin = destination_origin,
           .reporting_origin = reporting_origin,
           .filters = *AttributionFilterData::FromTriggerFilterValues({
               {"a", {"b"}},
@@ -456,9 +400,6 @@
 
   data_host_remote->TriggerDataAvailable(std::move(trigger_data));
   data_host_remote.FlushForTesting();
-
-  histograms.ExpectUniqueSample("Conversions.RegisterConversionAllowed", true,
-                                1);
 }
 
 TEST_F(AttributionDataHostManagerImplTest,
@@ -608,41 +549,6 @@
 }
 
 TEST_F(AttributionDataHostManagerImplTest,
-       TriggerDataHostEmbedderDisallow_TriggerDropped) {
-  base::HistogramTester histograms;
-
-  EXPECT_CALL(mock_manager_, HandleTrigger).Times(0);
-
-  auto destination_origin =
-      url::Origin::Create(GURL("https://trigger.example"));
-  auto reporting_origin = url::Origin::Create(GURL("https://reporter.example"));
-
-  MockAttributionReportingContentBrowserClient browser_client;
-  EXPECT_CALL(browser_client,
-              IsConversionMeasurementOperationAllowed(
-                  _, ConversionMeasurementOperation::kConversion, IsNull(),
-                  Pointee(destination_origin), Pointee(reporting_origin)))
-      .WillRepeatedly(Return(false));
-  ScopedContentBrowserClientSetting setting(&browser_client);
-  mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
-  data_host_manager_.RegisterDataHost(
-      data_host_remote.BindNewPipeAndPassReceiver(), destination_origin);
-
-  auto trigger_data = blink::mojom::AttributionTriggerData::New();
-  trigger_data->reporting_origin = reporting_origin;
-
-  trigger_data->filters = blink::mojom::AttributionFilterData::New();
-  trigger_data->aggregatable_trigger =
-      blink::mojom::AttributionAggregatableTrigger::New();
-
-  data_host_remote->TriggerDataAvailable(std::move(trigger_data));
-  data_host_remote.FlushForTesting();
-
-  histograms.ExpectUniqueSample("Conversions.RegisterConversionAllowed", false,
-                                1);
-}
-
-TEST_F(AttributionDataHostManagerImplTest,
        TriggerDataHost_EventTriggerDataSizeCheckPerformed) {
   const struct {
     size_t size;
diff --git a/content/browser/attribution_reporting/attribution_host.cc b/content/browser/attribution_reporting/attribution_host.cc
index 5009c56..167a64e 100644
--- a/content/browser/attribution_reporting/attribution_host.cc
+++ b/content/browser/attribution_reporting/attribution_host.cc
@@ -214,22 +214,9 @@
     return;
   }
 
-  VerifyAndStoreImpression(AttributionSourceType::kNavigation,
-                           impression_origin, impression, *attribution_manager);
-}
-
-bool AttributionHost::VerifyAndStoreImpression(
-    AttributionSourceType source_type,
-    const url::Origin& impression_origin,
-    const blink::Impression& impression,
-    AttributionManager& attribution_manager) {
-  attribution_host_utils::VerifyResult result =
-      attribution_host_utils::VerifyAndStoreImpression(
-          source_type, impression_origin, impression,
-          web_contents()->GetBrowserContext(), attribution_manager,
-          base::Time::Now());
-  RecordRegisterImpressionAllowed(result.allowed);
-  return result.stored;
+  attribution_host_utils::VerifyAndStoreImpression(
+      AttributionSourceType::kNavigation, impression_origin, impression,
+      *attribution_manager, base::Time::Now());
 }
 
 void AttributionHost::RegisterConversion(
@@ -274,20 +261,8 @@
     return;
   }
 
-  const bool allowed =
-      GetContentClient()->browser()->IsConversionMeasurementOperationAllowed(
-          web_contents()->GetBrowserContext(),
-          ContentBrowserClient::ConversionMeasurementOperation::kConversion,
-          /*impression_origin=*/nullptr, &main_frame_origin,
-          &conversion->reporting_origin);
-  RecordRegisterConversionAllowed(allowed);
-  if (!allowed)
-    return;
-
-  net::SchemefulSite conversion_destination(main_frame_origin);
-
   AttributionTrigger storable_conversion(
-      conversion->conversion_data, std::move(conversion_destination),
+      conversion->conversion_data, /*destination_origin=*/main_frame_origin,
       conversion->reporting_origin, conversion->event_source_trigger_data,
       conversion->priority,
       conversion->dedup_key.is_null()
@@ -380,8 +355,9 @@
 
   // No navigation in progress and we've already committed the destination for
   // the conversion, so just store the impression.
-  VerifyAndStoreImpression(AttributionSourceType::kNavigation,
-                           impression_origin, impression, *attribution_manager);
+  attribution_host_utils::VerifyAndStoreImpression(
+      AttributionSourceType::kNavigation, impression_origin, impression,
+      *attribution_manager, base::Time::Now());
 }
 
 // static
diff --git a/content/browser/attribution_reporting/attribution_host.h b/content/browser/attribution_reporting/attribution_host.h
index 6e91d52..77821ce 100644
--- a/content/browser/attribution_reporting/attribution_host.h
+++ b/content/browser/attribution_reporting/attribution_host.h
@@ -10,7 +10,6 @@
 #include <memory>
 
 #include "base/containers/flat_map.h"
-#include "content/browser/attribution_reporting/attribution_source_type.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/render_frame_host_receiver_set.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -20,7 +19,6 @@
 
 namespace content {
 
-class AttributionManager;
 class AttributionManagerProvider;
 class AttributionPageMetrics;
 class WebContents;
@@ -91,15 +89,6 @@
   void NotifyImpressionInitiatedByPage(const url::Origin& impression_origin,
                                        const blink::Impression& impression);
 
-  // Stores the impression if conversion measurement is allowed for the
-  // impression origin and reporting origin and the impressionorigin, reporting
-  // origin, and conversion destination are potentially trustworthy. Returns
-  // whether the impression was stored.
-  bool VerifyAndStoreImpression(AttributionSourceType source_type,
-                                const url::Origin& impression_origin,
-                                const blink::Impression& impression,
-                                AttributionManager& attribution_manager);
-
   // Map which stores the top-frame origin an impression occurred on for all
   // navigations with an associated impression, keyed by navigation ID.
   // Initiator origins are stored at navigation start time to have the best
diff --git a/content/browser/attribution_reporting/attribution_host_unittest.cc b/content/browser/attribution_reporting/attribution_host_unittest.cc
index 77361ae..20961a4 100644
--- a/content/browser/attribution_reporting/attribution_host_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_host_unittest.cc
@@ -25,7 +25,6 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
-#include "net/base/schemeful_site.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/conversions/conversions.mojom.h"
@@ -59,12 +58,8 @@
     ::content::ContentBrowserClient::ConversionMeasurementOperation;
 
 using testing::_;
-using testing::AllOf;
 using testing::InSequence;
-using testing::IsNull;
 using testing::Mock;
-using testing::Pointee;
-using testing::Return;
 
 using Checkpoint = ::testing::MockFunction<void(int step)>;
 
@@ -127,8 +122,8 @@
 
 TEST_F(AttributionHostTest, ValidConversionInSubframe_NoBadMessage) {
   EXPECT_CALL(mock_manager_,
-              HandleTrigger(TriggerConversionDestinationIs(
-                  net::SchemefulSite(GURL("https://www.example.com")))));
+              HandleTrigger(TriggerDestinationOriginIs(
+                  url::Origin::Create(GURL("https://www.example.com")))));
 
   contents()->NavigateAndCommit(GURL("https://www.example.com"));
 
@@ -157,8 +152,8 @@
 TEST_F(AttributionHostTest,
        ConversionInSubframe_ConversionDestinationMatchesMainFrame) {
   EXPECT_CALL(mock_manager_,
-              HandleTrigger(TriggerConversionDestinationIs(
-                  net::SchemefulSite(GURL("https://www.example.com")))));
+              HandleTrigger(TriggerDestinationOriginIs(
+                  url::Origin::Create(GURL("https://www.example.com")))));
 
   contents()->NavigateAndCommit(GURL("https://www.example.com"));
 
@@ -214,44 +209,6 @@
       bad_message_observer.WaitForBadMessage());
 }
 
-TEST_F(AttributionHostTest, ConversionInSubframe_ChecksCorrectOrigins) {
-  // Verifies that conversions from subframes use the correct origins when
-  // checking if the operation is allowed by the embedded.
-
-  MockAttributionReportingContentBrowserClient browser_client;
-  EXPECT_CALL(
-      browser_client,
-      IsConversionMeasurementOperationAllowed(
-          _, ConversionMeasurementOperation::kConversion, IsNull(),
-          Pointee(url::Origin::Create(GURL("https://www.example.com/"))),
-          Pointee(url::Origin::Create(GURL("https://report.example/")))))
-      .WillOnce(Return(false))
-      .WillOnce(Return(true));
-  ScopedContentBrowserClientSetting setting(&browser_client);
-
-  for (bool conversion_allowed : {false, true}) {
-    EXPECT_CALL(mock_manager_, HandleTrigger).Times(conversion_allowed);
-
-    contents()->NavigateAndCommit(GURL("https://www.example.com"));
-
-    // Create a subframe and use it as a target for the conversion registration
-    // mojo.
-    content::RenderFrameHostTester* rfh_tester =
-        content::RenderFrameHostTester::For(main_rfh());
-    content::RenderFrameHost* subframe = rfh_tester->AppendChild("subframe");
-    subframe = NavigationSimulatorImpl::NavigateAndCommitFromDocument(
-        GURL("https://www.another.com"), subframe);
-    SetCurrentTargetFrameForTesting(subframe);
-
-    blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
-    conversion->reporting_origin =
-        url::Origin::Create(GURL("https://report.example"));
-    conversion_host_mojom()->RegisterConversion(std::move(conversion));
-
-    Mock::VerifyAndClear(&mock_manager_);
-  }
-}
-
 TEST_F(AttributionHostTest, ConversionOnInsecurePage_BadMessage) {
   EXPECT_CALL(mock_manager_, HandleTrigger).Times(0);
 
@@ -314,59 +271,12 @@
   EXPECT_FALSE(bad_message_observer.got_bad_message());
 }
 
-TEST_F(AttributionHostTest, ValidConversionWithEmbedderDisable_NoConversion) {
-  EXPECT_CALL(mock_manager_, HandleTrigger).Times(0);
-
-  MockAttributionReportingContentBrowserClient browser_client;
-  EXPECT_CALL(
-      browser_client,
-      IsConversionMeasurementOperationAllowed(
-          _, ConversionMeasurementOperation::kConversion, IsNull(),
-          Pointee(url::Origin::Create(GURL("https://www.example.com/"))),
-          Pointee(url::Origin::Create(GURL("https://secure.com/")))))
-      .WillOnce(Return(false));
-  ScopedContentBrowserClientSetting setting(&browser_client);
-
-  // Create a page with a secure origin.
-  contents()->NavigateAndCommit(GURL("https://www.example.com"));
-  SetCurrentTargetFrameForTesting(main_rfh());
-
-  blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
-  conversion->reporting_origin =
-      url::Origin::Create(GURL("https://secure.com"));
-  conversion_host_mojom()->RegisterConversion(std::move(conversion));
-}
-
-TEST_F(AttributionHostTest, ValidImpressionWithEmbedderDisable_NoImpression) {
-  EXPECT_CALL(mock_manager_, HandleSource).Times(0);
-
-  MockAttributionReportingContentBrowserClient browser_client;
-  // This is called twice because the real AttributionHost is still active for
-  // the test.
-  EXPECT_CALL(
-      browser_client,
-      IsConversionMeasurementOperationAllowed(
-          _, ConversionMeasurementOperation::kImpression,
-          Pointee(url::Origin::Create(GURL("https://secure_impression.com/"))),
-          IsNull(), Pointee(url::Origin::Create(GURL("https://c.com/")))))
-      .Times(2)
-      .WillRepeatedly(Return(false));
-  ScopedContentBrowserClientSetting setting(&browser_client);
-
-  contents()->NavigateAndCommit(GURL("https://secure_impression.com"));
-  auto navigation = NavigationSimulatorImpl::CreateRendererInitiated(
-      GURL(kConversionUrl), main_rfh());
-  navigation->SetInitiatorFrame(main_rfh());
-  navigation->set_impression(CreateValidImpression());
-  navigation->Commit();
-}
-
 TEST_F(AttributionHostTest, Conversion_AssociatedWithConversionSite) {
   // Verify that we use the domain of the page where the conversion occurred
   // instead of the origin.
   EXPECT_CALL(mock_manager_,
-              HandleTrigger(TriggerConversionDestinationIs(
-                  net::SchemefulSite(GURL("https://conversion.com")))));
+              HandleTrigger(TriggerDestinationOriginIs(
+                  url::Origin::Create(GURL("https://sub.conversion.com")))));
 
   // Create a page with a secure origin.
   contents()->NavigateAndCommit(GURL("https://sub.conversion.com"));
@@ -745,38 +655,6 @@
       bad_message_observer.WaitForBadMessage());
 }
 
-TEST_F(AttributionHostTest, RegisterConversion_RecordsAllowedMetric) {
-  // Create a page with a secure origin.
-  contents()->NavigateAndCommit(GURL("https://www.example.com"));
-  SetCurrentTargetFrameForTesting(main_rfh());
-
-  MockAttributionReportingContentBrowserClient browser_client;
-  EXPECT_CALL(browser_client,
-              IsConversionMeasurementOperationAllowed(
-                  _, ConversionMeasurementOperation::kConversion, IsNull(),
-                  Pointee(_), Pointee(_)))
-      .WillOnce(Return(true))
-      .WillOnce(Return(false));
-  ScopedContentBrowserClientSetting setting(&browser_client);
-
-  const struct {
-    bool want_allowed;
-  } kTestCases[] = {
-      {true},
-      {false},
-  };
-
-  for (const auto& test_case : kTestCases) {
-    base::HistogramTester histograms;
-    blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New();
-    conversion->reporting_origin =
-        url::Origin::Create(GURL("https://secure.com"));
-    conversion_host_mojom()->RegisterConversion(std::move(conversion));
-    histograms.ExpectUniqueSample("Conversions.RegisterConversionAllowed",
-                                  test_case.want_allowed, 1);
-  }
-}
-
 // In pre-loaded CCT navigations, the attribution can arrive after the
 // navigation begins but before it's committed. Currently only used on Android
 // but should work cross-platform.
diff --git a/content/browser/attribution_reporting/attribution_host_utils.cc b/content/browser/attribution_reporting/attribution_host_utils.cc
index c61a7ba..aa4004f 100644
--- a/content/browser/attribution_reporting/attribution_host_utils.cc
+++ b/content/browser/attribution_reporting/attribution_host_utils.cc
@@ -15,9 +15,6 @@
 #include "content/browser/attribution_reporting/common_source_info.h"
 #include "content/browser/attribution_reporting/storable_source.h"
 #include "content/common/url_utils.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/content_browser_client.h"
-#include "content/public/common/content_client.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "third_party/blink/public/common/navigation/impression.h"
 #include "url/gurl.h"
@@ -32,12 +29,11 @@
          network::IsOriginPotentiallyTrustworthy(origin);
 }
 
-VerifyResult VerifyAndStoreImpression(AttributionSourceType source_type,
-                                      const url::Origin& impression_origin,
-                                      const blink::Impression& impression,
-                                      BrowserContext* browser_context,
-                                      AttributionManager& attribution_manager,
-                                      base::Time impression_time) {
+void VerifyAndStoreImpression(AttributionSourceType source_type,
+                              const url::Origin& impression_origin,
+                              const blink::Impression& impression,
+                              AttributionManager& attribution_manager,
+                              base::Time impression_time) {
   // Convert |impression| into a StorableImpression that can be forwarded to
   // storage. If a reporting origin was not provided, default to the conversion
   // destination for reporting.
@@ -45,19 +41,11 @@
                                             ? impression_origin
                                             : *impression.reporting_origin;
 
-  const bool allowed =
-      GetContentClient()->browser()->IsConversionMeasurementOperationAllowed(
-          browser_context,
-          ContentBrowserClient::ConversionMeasurementOperation::kImpression,
-          &impression_origin, /*conversion_origin=*/nullptr, &reporting_origin);
-  if (!allowed)
-    return VerifyResult{.allowed = false, .stored = false};
-
   // Conversion measurement is only allowed in secure contexts.
   if (!IsOriginTrustworthyForAttributions(impression_origin) ||
       !IsOriginTrustworthyForAttributions(reporting_origin) ||
       !IsOriginTrustworthyForAttributions(impression.conversion_destination)) {
-    return VerifyResult{.allowed = true, .stored = false};
+    return;
   }
 
   StorableSource storable_impression(
@@ -74,7 +62,6 @@
   // DevTools in the event that a debug key is present but the corresponding
   // cookie is not.
   attribution_manager.HandleSource(std::move(storable_impression));
-  return VerifyResult{.allowed = true, .stored = true};
 }
 
 absl::optional<blink::Impression> ParseImpressionFromApp(
diff --git a/content/browser/attribution_reporting/attribution_host_utils.h b/content/browser/attribution_reporting/attribution_host_utils.h
index 6ed3a0f..17b891b3b 100644
--- a/content/browser/attribution_reporting/attribution_host_utils.h
+++ b/content/browser/attribution_reporting/attribution_host_utils.h
@@ -28,31 +28,21 @@
 namespace content {
 
 class AttributionManager;
-class BrowserContext;
 
 namespace attribution_host_utils {
 
-// Contains result for `VerifyAndStoreImpression()`.
-struct VerifyResult {
-  // Indicates whether the measurement operation is allowed by the
-  // ContentClient.
-  bool allowed;
-  // Indicates whether the impression is stored.
-  bool stored;
-};
-
 // Checks if the origin is trustworthy or an android app origin.
-bool IsOriginTrustworthyForAttributions(const url::Origin& origin);
+CONTENT_EXPORT bool IsOriginTrustworthyForAttributions(
+    const url::Origin& origin);
 
 // Performs required checks on an incoming impression's data (trustworthy
-// origins, etc), and if verified, generates a StorableImpression and persists
+// origins, etc), and if verified, generates a `StorableSource` and persists
 // it.
-VerifyResult VerifyAndStoreImpression(AttributionSourceType source_type,
-                                      const url::Origin& impression_origin,
-                                      const blink::Impression& impression,
-                                      BrowserContext* browser_context,
-                                      AttributionManager& attribution_manager,
-                                      base::Time impression_time);
+void VerifyAndStoreImpression(AttributionSourceType source_type,
+                              const url::Origin& impression_origin,
+                              const blink::Impression& impression,
+                              AttributionManager& attribution_manager,
+                              base::Time impression_time);
 
 CONTENT_EXPORT absl::optional<blink::Impression> ParseImpressionFromApp(
     const std::string& attribution_source_event_id,
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index 64b3874..38dd90e 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
@@ -376,6 +376,7 @@
           AttributionInfoBuilder(SourceBuilder(now).BuildStored()).Build())
           .SetReportTime(now + base::Hours(3))
           .Build(),
+      /*is_debug_report=*/false,
       SendResult(SendResult::Status::kSent,
                  /*http_response_code=*/200));
   manager_.NotifyReportSent(
@@ -384,6 +385,7 @@
           .SetReportTime(now + base::Hours(4))
           .SetPriority(-1)
           .Build(),
+      /*is_debug_report=*/false,
       SendResult(SendResult::Status::kDropped,
                  /*http_response_code=*/0));
   manager_.NotifyReportSent(
@@ -392,8 +394,19 @@
           .SetReportTime(now + base::Hours(5))
           .SetPriority(-2)
           .Build(),
+      /*is_debug_report=*/false,
       SendResult(SendResult::Status::kFailure,
                  /*http_response_code=*/0));
+  manager_.NotifyReportSent(
+      ReportBuilder(
+          AttributionInfoBuilder(SourceBuilder(now).BuildStored()).Build())
+          .SetReportTime(now + base::Hours(11))
+          .SetPriority(-8)
+          .Build(),
+      /*is_debug_report=*/true,
+      SendResult(SendResult::Status::kTransientFailure,
+                 /*http_response_code=*/0));
+
   ON_CALL(manager_, GetPendingReportsForInternalUse)
       .WillByDefault(InvokeCallback<std::vector<AttributionReport>>(
           {ReportBuilder(AttributionInfoBuilder(
@@ -483,7 +496,7 @@
     static constexpr char wait_script[] = R"(
       let table = document.querySelector("#report-table-wrapper tbody");
       let obs = new MutationObserver(() => {
-        if (table.children.length === 11 &&
+        if (table.children.length === 12 &&
             table.children[0].children[2].innerText === "https://conversion.test" &&
             table.children[0].children[3].innerText ===
               "https://report.test/.well-known/attribution-reporting/report-event-attribution" &&
@@ -503,7 +516,9 @@
             table.children[7].children[8].innerText === "Dropped due to excessive reporting origins" &&
             table.children[8].children[8].innerText === "Deduplicated" &&
             table.children[9].children[8].innerText === "No report capacity for destination site" &&
-            table.children[10].children[8].innerText === "Internal error") {
+            table.children[10].children[8].innerText === "Internal error" &&
+            table.children[11].children[3].innerText ===
+              "https://report.test/.well-known/attribution-reporting/debug/report-event-attribution") {
           document.title = $1;
         }
       });
@@ -519,27 +534,29 @@
     static constexpr char wait_script[] = R"(
       let table = document.querySelector("#report-table-wrapper tbody");
       let obs = new MutationObserver(() => {
-        if (table.children.length === 11 &&
-            table.children[10].children[2].innerText === "https://conversion.test" &&
-            table.children[10].children[3].innerText ===
+        if (table.children.length === 12 &&
+            table.children[11].children[2].innerText === "https://conversion.test" &&
+            table.children[11].children[3].innerText ===
               "https://report.test/.well-known/attribution-reporting/report-event-attribution" &&
-            table.children[10].children[6].innerText === "13" &&
-            table.children[10].children[7].innerText === "yes" &&
-            table.children[10].children[8].innerText === "Pending" &&
-            table.children[9].children[6].innerText === "12" &&
-            table.children[9].children[8].innerText === "Dropped for noise" &&
-            table.children[8].children[6].innerText === "11" &&
-            table.children[8].children[8].innerText === "Dropped due to low priority" &&
-            table.children[7].children[6].innerText === "0" &&
-            table.children[7].children[7].innerText === "no" &&
-            table.children[7].children[8].innerText === "Sent: HTTP 200" &&
-            table.children[6].children[8].innerText === "Prohibited by browser policy" &&
-            table.children[5].children[8].innerText === "Network error" &&
-            table.children[4].children[8].innerText === "Dropped due to excessive attributions" &&
-            table.children[3].children[8].innerText === "Dropped due to excessive reporting origins" &&
-            table.children[2].children[8].innerText === "Deduplicated" &&
-            table.children[1].children[8].innerText === "No report capacity for destination site" &&
-            table.children[0].children[8].innerText === "Internal error") {
+            table.children[11].children[6].innerText === "13" &&
+            table.children[11].children[7].innerText === "yes" &&
+            table.children[11].children[8].innerText === "Pending" &&
+            table.children[10].children[6].innerText === "12" &&
+            table.children[10].children[8].innerText === "Dropped for noise" &&
+            table.children[9].children[6].innerText === "11" &&
+            table.children[9].children[8].innerText === "Dropped due to low priority" &&
+            table.children[8].children[6].innerText === "0" &&
+            table.children[8].children[7].innerText === "no" &&
+            table.children[8].children[8].innerText === "Sent: HTTP 200" &&
+            table.children[7].children[8].innerText === "Prohibited by browser policy" &&
+            table.children[6].children[8].innerText === "Network error" &&
+            table.children[5].children[8].innerText === "Dropped due to excessive attributions" &&
+            table.children[4].children[8].innerText === "Dropped due to excessive reporting origins" &&
+            table.children[3].children[8].innerText === "Deduplicated" &&
+            table.children[2].children[8].innerText === "No report capacity for destination site" &&
+            table.children[1].children[8].innerText === "Internal error" &&
+            table.children[0].children[3].innerText ===
+              "https://report.test/.well-known/attribution-reporting/debug/report-event-attribution") {
           document.title = $1;
         }
       });
@@ -557,7 +574,7 @@
     static constexpr char wait_script[] = R"(
       let table = document.querySelector("#report-table-wrapper tbody");
       let obs = new MutationObserver(() => {
-        if (table.children.length === 11 &&
+        if (table.children.length === 12 &&
             table.children[0].children[2].innerText === "https://conversion.test" &&
             table.children[0].children[3].innerText ===
               "https://report.test/.well-known/attribution-reporting/report-event-attribution" &&
@@ -577,7 +594,9 @@
             table.children[7].children[8].innerText === "Dropped due to excessive reporting origins" &&
             table.children[8].children[8].innerText === "Deduplicated" &&
             table.children[9].children[8].innerText === "No report capacity for destination site" &&
-            table.children[10].children[8].innerText === "Internal error") {
+            table.children[10].children[8].innerText === "Internal error" &&
+            table.children[11].children[3].innerText ===
+              "https://report.test/.well-known/attribution-reporting/debug/report-event-attribution") {
           document.title = $1;
         }
       });
@@ -611,8 +630,10 @@
       .WillOnce(InvokeCallback<std::vector<AttributionReport>>({report}));
 
   report.set_report_time(report.report_time() + base::Hours(1));
-  manager_.NotifyReportSent(report, SendResult(SendResult::Status::kSent,
-                                               /*http_response_code=*/200));
+  manager_.NotifyReportSent(report,
+                            /*is_debug_report=*/false,
+                            SendResult(SendResult::Status::kSent,
+                                       /*http_response_code=*/200));
 
   EXPECT_CALL(manager_, ClearData)
       .WillOnce([](base::Time delete_begin, base::Time delete_end,
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
index dd0cfe2..21dad56 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
@@ -86,6 +86,7 @@
 
 mojom::WebUIAttributionReportPtr WebUIAttributionReport(
     const AttributionReport& report,
+    bool is_debug_report,
     int http_response_code,
     mojom::WebUIAttributionReport::Status status) {
   const auto* data =
@@ -95,7 +96,7 @@
   return mojom::WebUIAttributionReport::New(
       data->id,
       attribution_info.source.common_info().ConversionDestination().Serialize(),
-      report.ReportURL(),
+      report.ReportURL(is_debug_report),
       /*trigger_time=*/attribution_info.time.ToJsTime(),
       /*report_time=*/report.report_time().ToJsTime(), data->priority,
       SerializeAttributionJson(report.ReportBody(), /*pretty_print=*/true),
@@ -112,7 +113,7 @@
   web_ui_reports.reserve(pending_reports.size());
   for (const AttributionReport& report : pending_reports) {
     web_ui_reports.push_back(WebUIAttributionReport(
-        report, /*http_response_code=*/0,
+        report, /*is_debug_report=*/false, /*http_response_code=*/0,
         mojom::WebUIAttributionReport::Status::kPending));
   }
 
@@ -265,6 +266,7 @@
 
 void AttributionInternalsHandlerImpl::OnReportSent(
     const AttributionReport& report,
+    bool is_debug_report,
     const SendResult& info) {
   // TODO(crbug.com/1285317): Show aggregatable reports in internal page.
   if (!absl::holds_alternative<AttributionReport::EventLevelData>(
@@ -282,16 +284,16 @@
           mojom::WebUIAttributionReport::Status::kProhibitedByBrowserPolicy;
       break;
     case SendResult::Status::kFailure:
+    case SendResult::Status::kTransientFailure:
       status = mojom::WebUIAttributionReport::Status::kNetworkError;
       break;
-    case SendResult::Status::kTransientFailure:
     case SendResult::Status::kFailedToAssemble:
       NOTREACHED();
       return;
   }
 
-  auto web_report =
-      WebUIAttributionReport(report, info.http_response_code, status);
+  auto web_report = WebUIAttributionReport(report, is_debug_report,
+                                           info.http_response_code, status);
 
   for (auto& observer : observers_) {
     observer->OnReportSent(web_report.Clone());
@@ -344,6 +346,7 @@
 
   DCHECK_EQ(result.dropped_reports().size(), 1u);
   auto report = WebUIAttributionReport(result.dropped_reports().front(),
+                                       /*is_debug_report=*/false,
                                        /*http_response_code=*/0, status);
 
   for (auto& observer : observers_) {
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.h b/content/browser/attribution_reporting/attribution_internals_handler_impl.h
index 38a4128b..de81c2ff 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.h
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.h
@@ -74,6 +74,7 @@
   void OnSourceHandled(const StorableSource& source,
                        StorableSource::Result result) override;
   void OnReportSent(const AttributionReport& report,
+                    bool is_debug_report,
                     const SendResult& info) override;
   void OnTriggerHandled(const CreateReportResult& result) override;
 
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index 6b1cfb9..1eec6fc 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -24,6 +24,7 @@
 #include "content/browser/attribution_reporting/attribution_cookie_checker_impl.h"
 #include "content/browser/attribution_reporting/attribution_data_host_manager_impl.h"
 #include "content/browser/attribution_reporting/attribution_info.h"
+#include "content/browser/attribution_reporting/attribution_metrics.h"
 #include "content/browser/attribution_reporting/attribution_observer.h"
 #include "content/browser/attribution_reporting/attribution_observer_types.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
@@ -187,6 +188,18 @@
       AttributionNoiseMode::kDefault, AttributionDelayMode::kDefault);
 }
 
+bool IsOperationAllowed(
+    StoragePartitionImpl* storage_partition,
+    ContentBrowserClient::ConversionMeasurementOperation operation,
+    const url::Origin* source_origin,
+    const url::Origin* destination_origin,
+    const url::Origin* reporting_origin) {
+  DCHECK(storage_partition);
+  return GetContentClient()->browser()->IsConversionMeasurementOperationAllowed(
+      storage_partition->browser_context(), operation, source_origin,
+      destination_origin, reporting_origin);
+}
+
 }  // namespace
 
 absl::optional<base::TimeDelta> GetFailedReportDelay(int failed_send_attempts) {
@@ -207,11 +220,12 @@
   AttributionStorageSql::RunInMemoryForTesting();
 }
 
-bool AttributionManagerImpl::IsReportAllowed(const AttributionReport& report) {
+bool AttributionManagerImpl::IsReportAllowed(
+    const AttributionReport& report) const {
   const CommonSourceInfo& common_info =
       report.attribution_info().source.common_info();
-  return GetContentClient()->browser()->IsConversionMeasurementOperationAllowed(
-      storage_partition_->browser_context(),
+  return IsOperationAllowed(
+      storage_partition_.get(),
       ContentBrowserClient::ConversionMeasurementOperation::kReport,
       &common_info.impression_origin(), &common_info.conversion_origin(),
       &common_info.reporting_origin());
@@ -244,9 +258,7 @@
           MakeStorageDelegate(),
           std::make_unique<AttributionCookieCheckerImpl>(storage_partition),
           std::make_unique<AttributionReportNetworkSender>(storage_partition),
-          std::make_unique<AttributionDataHostManagerImpl>(
-              storage_partition->browser_context(),
-              this)) {}
+          std::make_unique<AttributionDataHostManagerImpl>(this)) {}
 
 AttributionManagerImpl::AttributionManagerImpl(
     StoragePartitionImpl* storage_partition,
@@ -425,13 +437,33 @@
     bool is_debug_cookie_set;
 
     void operator()(StorableSource source) {
+      CommonSourceInfo& common_info = source.common_info();
+
+      bool allowed = IsOperationAllowed(
+          manager->storage_partition_.get(),
+          ContentBrowserClient::ConversionMeasurementOperation::kImpression,
+          &common_info.impression_origin(),
+          /*destination_origin=*/nullptr, &common_info.reporting_origin());
+      RecordRegisterImpressionAllowed(allowed);
+      if (!allowed)
+        return;
+
       if (!is_debug_cookie_set)
-        source.common_info().ClearDebugKey();
+        common_info.ClearDebugKey();
 
       manager->StoreSource(std::move(source));
     }
 
     void operator()(AttributionTrigger trigger) {
+      bool allowed = IsOperationAllowed(
+          manager->storage_partition_.get(),
+          ContentBrowserClient::ConversionMeasurementOperation::kConversion,
+          /*source_origin=*/nullptr, &trigger.destination_origin(),
+          &trigger.reporting_origin());
+      RecordRegisterConversionAllowed(allowed);
+      if (!allowed)
+        return;
+
       if (!is_debug_cookie_set)
         trigger.ClearDebugKey();
 
@@ -481,9 +513,11 @@
   DCHECK(absl::holds_alternative<AttributionReport::EventLevelData>(
       report.data()));
 
-  // We don't fire observer callbacks or delete from storage for debug reports.
+  // We don't delete from storage for debug reports.
   PrepareToSendReport(std::move(report), /*is_debug_report=*/true,
-                      base::DoNothing());
+                      base::BindOnce(&AttributionManagerImpl::NotifyReportSent,
+                                     weak_factory_.GetWeakPtr(),
+                                     /*is_debug_report=*/true));
 }
 
 void AttributionManagerImpl::GetActiveSourcesForWebUI(
@@ -710,8 +744,14 @@
     return;
   }
 
+  NotifyReportSent(/*is_debug_report=*/false, std::move(report), info);
+}
+
+void AttributionManagerImpl::NotifyReportSent(bool is_debug_report,
+                                              AttributionReport report,
+                                              SendResult info) {
   for (auto& observer : observers_)
-    observer.OnReportSent(report, info);
+    observer.OnReportSent(report, /*is_debug_report=*/is_debug_report, info);
 }
 
 void AttributionManagerImpl::AssembleAggregatableReport(
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h
index 3164ffb3..7ea13df 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -161,8 +161,9 @@
   void NotifySourcesChanged();
   void NotifyReportsChanged();
   void NotifySourceDeactivated(const DeactivatedSource& source);
+  void NotifyReportSent(bool is_debug_report, AttributionReport, SendResult);
 
-  bool IsReportAllowed(const AttributionReport&);
+  bool IsReportAllowed(const AttributionReport&) const;
 
   // Friend to expose the AttributionStorage for certain tests.
   friend std::vector<AttributionReport> GetAttributionReportsForTesting(
diff --git a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
index 284742b..27a73236 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
@@ -46,7 +46,6 @@
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_utils.h"
-#include "net/base/schemeful_site.h"
 #include "services/network/test/test_network_connection_tracker.h"
 #include "storage/browser/test/mock_special_storage_policy.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -61,12 +60,14 @@
 
 using ::testing::_;
 using ::testing::AllOf;
+using ::testing::AnyOf;
 using ::testing::ElementsAre;
 using ::testing::Expectation;
 using ::testing::Field;
 using ::testing::Ge;
 using ::testing::InSequence;
 using ::testing::IsEmpty;
+using ::testing::IsNull;
 using ::testing::Le;
 using ::testing::Pointee;
 using ::testing::Return;
@@ -99,7 +100,9 @@
 
   MOCK_METHOD(void,
               OnReportSent,
-              (const AttributionReport& report, const SendResult& info),
+              (const AttributionReport& report,
+               bool is_debug_report,
+               const SendResult& info),
               (override));
 
   MOCK_METHOD(void,
@@ -406,6 +409,8 @@
 }
 
 TEST_F(AttributionManagerImplTest, ImpressionConverted_ReportSent) {
+  base::HistogramTester histograms;
+
   attribution_manager_->HandleSource(
       SourceBuilder().SetExpiry(kImpressionExpiry).Build());
   attribution_manager_->HandleTrigger(DefaultTrigger());
@@ -417,6 +422,11 @@
 
   task_environment_.FastForwardBy(base::Microseconds(1));
   EXPECT_THAT(report_sender_->calls(), SizeIs(1));
+
+  histograms.ExpectUniqueSample("Conversions.RegisterImpressionAllowed", true,
+                                1);
+  histograms.ExpectUniqueSample("Conversions.RegisterConversionAllowed", true,
+                                1);
 }
 
 TEST_F(AttributionManagerImplTest,
@@ -700,9 +710,12 @@
       &observer);
   observation.Observe(attribution_manager_.get());
 
-  EXPECT_CALL(observer, OnReportSent(ReportSourceIs(SourceEventIdIs(1u)), _));
-  EXPECT_CALL(observer, OnReportSent(ReportSourceIs(SourceEventIdIs(2u)), _));
-  EXPECT_CALL(observer, OnReportSent(ReportSourceIs(SourceEventIdIs(3u)), _));
+  EXPECT_CALL(observer, OnReportSent(ReportSourceIs(SourceEventIdIs(1u)),
+                                     /*is_debug_report=*/false, _));
+  EXPECT_CALL(observer, OnReportSent(ReportSourceIs(SourceEventIdIs(2u)),
+                                     /*is_debug_report=*/false, _));
+  EXPECT_CALL(observer, OnReportSent(ReportSourceIs(SourceEventIdIs(3u)),
+                                     /*is_debug_report=*/false, _));
 
   attribution_manager_->HandleSource(
       SourceBuilder().SetSourceEventId(1).SetExpiry(kImpressionExpiry).Build());
@@ -1189,11 +1202,75 @@
   run_loop.Run();
 }
 
+TEST_F(AttributionManagerImplTest,
+       EmbedderDisallowsImpressions_SourceNotStored) {
+  base::HistogramTester histograms;
+
+  MockAttributionReportingContentBrowserClient browser_client;
+  EXPECT_CALL(
+      browser_client,
+      IsConversionMeasurementOperationAllowed(
+          _, ContentBrowserClient::ConversionMeasurementOperation::kImpression,
+          Pointee(url::Origin::Create(GURL("https://impression.test/"))),
+          IsNull(), Pointee(url::Origin::Create(GURL("https://report.test/")))))
+      .WillOnce(Return(false));
+  ScopedContentBrowserClientSetting setting(&browser_client);
+
+  attribution_manager_->HandleSource(
+      SourceBuilder().SetExpiry(kImpressionExpiry).Build());
+  EXPECT_THAT(StoredSources(), IsEmpty());
+
+  histograms.ExpectUniqueSample("Conversions.RegisterImpressionAllowed", false,
+                                1);
+}
+
+TEST_F(AttributionManagerImplTest,
+       EmbedderDisallowsConversions_ReportNotStored) {
+  base::HistogramTester histograms;
+
+  MockAttributionReportingContentBrowserClient browser_client;
+  EXPECT_CALL(
+      browser_client,
+      IsConversionMeasurementOperationAllowed(
+          _, ContentBrowserClient::ConversionMeasurementOperation::kImpression,
+          _, _, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(
+      browser_client,
+      IsConversionMeasurementOperationAllowed(
+          _, ContentBrowserClient::ConversionMeasurementOperation::kConversion,
+          IsNull(),
+          Pointee(url::Origin::Create(GURL("https://sub.conversion.test/"))),
+          Pointee(url::Origin::Create(GURL("https://report.test/")))))
+      .WillOnce(Return(false));
+  ScopedContentBrowserClientSetting setting(&browser_client);
+
+  attribution_manager_->HandleSource(
+      SourceBuilder().SetExpiry(kImpressionExpiry).Build());
+  EXPECT_THAT(StoredSources(), SizeIs(1));
+
+  attribution_manager_->HandleTrigger(DefaultTrigger());
+  EXPECT_THAT(StoredReports(), IsEmpty());
+
+  histograms.ExpectUniqueSample("Conversions.RegisterConversionAllowed", false,
+                                1);
+}
+
 TEST_F(AttributionManagerImplTest, EmbedderDisallowsReporting_ReportNotSent) {
   MockAttributionReportingContentBrowserClient browser_client;
   EXPECT_CALL(
       browser_client,
       IsConversionMeasurementOperationAllowed(
+          _,
+          AnyOf(
+              ContentBrowserClient::ConversionMeasurementOperation::kImpression,
+              ContentBrowserClient::ConversionMeasurementOperation::
+                  kConversion),
+          _, _, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(
+      browser_client,
+      IsConversionMeasurementOperationAllowed(
           _, ContentBrowserClient::ConversionMeasurementOperation::kReport,
           Pointee(url::Origin::Create(GURL("https://impression.test/"))),
           Pointee(url::Origin::Create(GURL("https://sub.conversion.test/"))),
@@ -1213,8 +1290,9 @@
       &observer);
   observation.Observe(attribution_manager_.get());
 
-  EXPECT_CALL(observer, OnReportSent(_, Field(&SendResult::status,
-                                              SendResult::Status::kDropped)));
+  EXPECT_CALL(observer, OnReportSent(_, /*is_debug_report=*/false,
+                                     Field(&SendResult::status,
+                                           SendResult::Status::kDropped)));
 
   task_environment_.FastForwardBy(kFirstReportingWindow);
 
@@ -1237,6 +1315,16 @@
   EXPECT_CALL(
       browser_client,
       IsConversionMeasurementOperationAllowed(
+          _,
+          AnyOf(
+              ContentBrowserClient::ConversionMeasurementOperation::kImpression,
+              ContentBrowserClient::ConversionMeasurementOperation::
+                  kConversion),
+          _, _, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(
+      browser_client,
+      IsConversionMeasurementOperationAllowed(
           _, ContentBrowserClient::ConversionMeasurementOperation::kReport,
           Pointee(source_origin), Pointee(destination_origin),
           Pointee(reporting_origin)))
@@ -1254,7 +1342,7 @@
 
   attribution_manager_->HandleTrigger(
       TriggerBuilder()
-          .SetConversionDestination(net::SchemefulSite(destination_origin))
+          .SetDestinationOrigin(destination_origin)
           .SetReportingOrigin(reporting_origin)
           .SetDebugKey(456)
           .Build());
@@ -1563,6 +1651,13 @@
   };
 
   for (const auto& test_case : kTestCases) {
+    MockAttributionObserver observer;
+    base::ScopedObservation<AttributionManager, AttributionObserver>
+        observation(&observer);
+    observation.Observe(attribution_manager_.get());
+    EXPECT_CALL(observer, OnReportSent(_, /*is_debug_report=*/true, _))
+        .Times(test_case.send_expected);
+
     attribution_manager_->HandleSource(
         SourceBuilder()
             .SetReportingOrigin(reporting_origin)
@@ -1588,12 +1683,17 @@
               ReportSourceIs(SourceDebugKeyIs(test_case.source_debug_key)),
               TriggerDebugKeyIs(test_case.trigger_debug_key))))
           << test_case.name;
+
+      report_sender_->RunCallbacksAndReset(
+          {SendResult::Status::kTransientFailure});
     } else {
       EXPECT_THAT(report_sender_->debug_calls(), IsEmpty());
     }
 
     attribution_manager_->ClearData(base::Time::Min(), base::Time::Max(),
                                     base::NullCallback(), base::DoNothing());
+
+    ::testing::Mock::VerifyAndClear(&observer);
   }
 }
 
diff --git a/content/browser/attribution_reporting/attribution_observer.h b/content/browser/attribution_reporting/attribution_observer.h
index 65134c3..7247f49 100644
--- a/content/browser/attribution_reporting/attribution_observer.h
+++ b/content/browser/attribution_reporting/attribution_observer.h
@@ -39,6 +39,7 @@
   // Called when a report is sent, regardless of success, but not for attempts
   // that will be retried.
   virtual void OnReportSent(const AttributionReport& report,
+                            bool is_debug_report,
                             const SendResult& info) {}
 
   // Called when a trigger is registered, regardless of success.
diff --git a/content/browser/attribution_reporting/attribution_reporter_android.cc b/content/browser/attribution_reporting/attribution_reporter_android.cc
index 3485f29..df7759e 100644
--- a/content/browser/attribution_reporting/attribution_reporter_android.cc
+++ b/content/browser/attribution_reporting/attribution_reporter_android.cc
@@ -52,7 +52,7 @@
       OriginFromAndroidPackageName(source_package_name);
 
   attribution_host_utils::VerifyAndStoreImpression(
-      AttributionSourceType::kEvent, impression_origin, *impression, context,
+      AttributionSourceType::kEvent, impression_origin, *impression,
       attribution_manager, impression_time);
 }
 
diff --git a/content/browser/attribution_reporting/attribution_reporter_android_unittest.cc b/content/browser/attribution_reporting/attribution_reporter_android_unittest.cc
index 805a83a..2f8d3870 100644
--- a/content/browser/attribution_reporting/attribution_reporter_android_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_reporter_android_unittest.cc
@@ -21,11 +21,7 @@
 
 namespace {
 
-using testing::_;
 using testing::AllOf;
-using testing::IsNull;
-using testing::Pointee;
-using testing::Return;
 
 const char kPackageName[] = "org.chromium.chrome.test";
 const char kConversionUrl[] = "https://b.com";
@@ -75,23 +71,6 @@
       base::Time::Now());
 }
 
-TEST_F(AttributionReporterTest, ValidImpression_Disallowed) {
-  MockAttributionReportingContentBrowserClient browser_client;
-  EXPECT_CALL(
-      browser_client,
-      IsConversionMeasurementOperationAllowed(
-          _, ContentBrowserClient::ConversionMeasurementOperation::kImpression,
-          Pointee(_), IsNull(), Pointee(_)))
-      .WillOnce(Return(false));
-  ScopedContentBrowserClientSetting setting(&browser_client);
-
-  EXPECT_CALL(mock_manager_, HandleSource).Times(0);
-
-  attribution_reporter_android::ReportAppImpression(
-      mock_manager_, nullptr, kPackageName, kEventId, kConversionUrl,
-      kReportToUrl, 56789, base::Time::Now());
-}
-
 TEST_F(AttributionReporterTest, InvalidImpression) {
   EXPECT_CALL(mock_manager_, HandleSource).Times(0);
 
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index ea5155c..4e0a66b 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -41,6 +41,7 @@
 #include "content/browser/attribution_reporting/sql_utils.h"
 #include "content/browser/attribution_reporting/storable_source.h"
 #include "content/browser/attribution_reporting/stored_source.h"
+#include "net/base/schemeful_site.h"
 #include "sql/database.h"
 #include "sql/recovery.h"
 #include "sql/statement.h"
@@ -727,8 +728,7 @@
 
   DCHECK(report.has_value());
 
-  switch (
-      CapacityForStoringReport(trigger.conversion_destination().Serialize())) {
+  switch (CapacityForStoringReport(trigger)) {
     case ConversionCapacityStatus::kHasCapacity:
       break;
     case ConversionCapacityStatus::kNoCapacity:
@@ -849,9 +849,8 @@
     const AttributionTrigger& trigger,
     absl::optional<StoredSource::Id>& source_id_to_attribute,
     std::vector<StoredSource::Id>& source_ids_to_delete) {
-  const net::SchemefulSite& conversion_destination =
-      trigger.conversion_destination();
-  DCHECK(!conversion_destination.opaque());
+  const url::Origin& destination_origin = trigger.destination_origin();
+  DCHECK(!destination_origin.opaque());
 
   const url::Origin& reporting_origin = trigger.reporting_origin();
   DCHECK(!reporting_origin.opaque());
@@ -871,7 +870,7 @@
 
   sql::Statement statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kGetMatchingSourcesSql));
-  statement.BindString(0, conversion_destination.Serialize());
+  statement.BindString(0, net::SchemefulSite(destination_origin).Serialize());
   statement.BindString(1, SerializeOrigin(reporting_origin));
   statement.BindTime(2, base::Time::Now());
 
@@ -1676,7 +1675,7 @@
 
 AttributionStorageSql::ConversionCapacityStatus
 AttributionStorageSql::CapacityForStoringReport(
-    const std::string& serialized_origin) {
+    const AttributionTrigger& trigger) {
   // This query should be reasonably optimized via
   // `kConversionDestinationIndexSql`. The conversion origin is the second
   // column in a multi-column index where the first column is just a boolean.
@@ -1695,7 +1694,8 @@
       "(aggregatable_active BETWEEN 0 AND 1)";
   sql::Statement statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kCountReportsSql));
-  statement.BindString(0, serialized_origin);
+  statement.BindString(
+      0, net::SchemefulSite(trigger.destination_origin()).Serialize());
   if (!statement.Step())
     return ConversionCapacityStatus::kError;
   int64_t count = statement.ColumnInt64(0);
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.h b/content/browser/attribution_reporting/attribution_storage_sql.h
index ae6bb62..0fa21b3 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.h
+++ b/content/browser/attribution_reporting/attribution_storage_sql.h
@@ -167,8 +167,7 @@
     kError,
   };
 
-  ConversionCapacityStatus CapacityForStoringReport(
-      const std::string& serialized_origin)
+  ConversionCapacityStatus CapacityForStoringReport(const AttributionTrigger&)
       VALID_CONTEXT_REQUIRED(sequence_checker_);
 
   enum class MaybeReplaceLowerPriorityEventLevelReportResult {
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
index 5d00ff6..bfd81f01 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
@@ -599,13 +599,12 @@
                              .Build());
 
   task_environment_.FastForwardBy(base::Days(1));
-  EXPECT_EQ(
-      AttributionTrigger::EventLevelResult::kSuccess,
-      MaybeCreateAndStoreEventLevelReport(
-          TriggerBuilder()
-              .SetConversionDestination(net::SchemefulSite(conversion_origin))
-              .SetReportingOrigin(reporting_origin)
-              .Build()));
+  EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
+            MaybeCreateAndStoreEventLevelReport(
+                TriggerBuilder()
+                    .SetDestinationOrigin(conversion_origin)
+                    .SetReportingOrigin(reporting_origin)
+                    .Build()));
   EXPECT_THAT(storage()->GetActiveSources(), SizeIs(1));
 
   task_environment_.FastForwardBy(base::Days(1));
@@ -645,13 +644,12 @@
                              .Build());
 
   task_environment_.FastForwardBy(base::Days(1));
-  EXPECT_EQ(
-      AttributionTrigger::EventLevelResult::kSuccess,
-      MaybeCreateAndStoreEventLevelReport(
-          TriggerBuilder()
-              .SetConversionDestination(net::SchemefulSite(conversion_origin))
-              .SetReportingOrigin(reporting_origin)
-              .Build()));
+  EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
+            MaybeCreateAndStoreEventLevelReport(
+                TriggerBuilder()
+                    .SetDestinationOrigin(conversion_origin)
+                    .SetReportingOrigin(reporting_origin)
+                    .Build()));
   EXPECT_THAT(storage()->GetActiveSources(), SizeIs(1));
 
   task_environment_.FastForwardBy(base::Days(1));
@@ -734,8 +732,8 @@
       MaybeCreateAndStoreEventLevelReport(
           TriggerBuilder()
               .SetTriggerData(kMaxUint64)
-              .SetConversionDestination(
-                  impression.common_info().ConversionDestination())
+              .SetDestinationOrigin(
+                  impression.common_info().conversion_origin())
               .SetReportingOrigin(impression.common_info().reporting_origin())
               .Build()));
 
diff --git a/content/browser/attribution_reporting/attribution_storage_unittest.cc b/content/browser/attribution_reporting/attribution_storage_unittest.cc
index 2b5810c4..da33886c 100644
--- a/content/browser/attribution_reporting/attribution_storage_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_unittest.cc
@@ -222,8 +222,7 @@
       AttributionTrigger::EventLevelResult::kSuccess,
       MaybeCreateAndStoreEventLevelReport(
           TriggerBuilder()
-              .SetConversionDestination(
-                  net::SchemefulSite(GURL("https://a.test")))
+              .SetDestinationOrigin(url::Origin::Create(GURL("https://a.test")))
               .SetReportingOrigin(impression.common_info().reporting_origin())
               .Build()));
 }
@@ -659,23 +658,23 @@
   for (int i = 0; i < 5; i++) {
     auto origin =
         url::Origin::Create(GURL(base::StringPrintf("https://%d.com/", i)));
-    EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
-              MaybeCreateAndStoreEventLevelReport(
-                  TriggerBuilder()
-                      .SetConversionDestination(net::SchemefulSite(origin))
-                      .SetReportingOrigin(origin)
-                      .Build()));
+    EXPECT_EQ(
+        AttributionTrigger::EventLevelResult::kSuccess,
+        MaybeCreateAndStoreEventLevelReport(TriggerBuilder()
+                                                .SetDestinationOrigin(origin)
+                                                .SetReportingOrigin(origin)
+                                                .Build()));
   }
   task_environment_.FastForwardBy(base::Days(1));
   for (int i = 5; i < 10; i++) {
     auto origin =
         url::Origin::Create(GURL(base::StringPrintf("https://%d.com/", i)));
-    EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
-              MaybeCreateAndStoreEventLevelReport(
-                  TriggerBuilder()
-                      .SetConversionDestination(net::SchemefulSite(origin))
-                      .SetReportingOrigin(origin)
-                      .Build()));
+    EXPECT_EQ(
+        AttributionTrigger::EventLevelResult::kSuccess,
+        MaybeCreateAndStoreEventLevelReport(TriggerBuilder()
+                                                .SetDestinationOrigin(origin)
+                                                .SetReportingOrigin(origin)
+                                                .Build()));
   }
 
   auto null_filter = base::RepeatingCallback<bool(const url::Origin&)>();
@@ -1312,8 +1311,8 @@
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(
                 TriggerBuilder()
-                    .SetConversionDestination(net::SchemefulSite(
-                        url::Origin::Create(GURL("https://a.example"))))
+                    .SetDestinationOrigin(
+                        url::Origin::Create(GURL("https://a.example")))
                     .SetDedupKey(11)
                     .SetTriggerData(71)
                     .Build()));
@@ -1323,8 +1322,8 @@
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(
                 TriggerBuilder()
-                    .SetConversionDestination(net::SchemefulSite(
-                        url::Origin::Create(GURL("https://a.example"))))
+                    .SetDestinationOrigin(
+                        url::Origin::Create(GURL("https://a.example")))
                     .SetDedupKey(12)
                     .SetTriggerData(72)
                     .Build()));
@@ -1334,8 +1333,8 @@
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(
                 TriggerBuilder()
-                    .SetConversionDestination(net::SchemefulSite(
-                        url::Origin::Create(GURL("https://b.example"))))
+                    .SetDestinationOrigin(
+                        url::Origin::Create(GURL("https://b.example")))
                     .SetDedupKey(12)
                     .SetTriggerData(73)
                     .Build()));
@@ -1343,8 +1342,7 @@
   // Shouldn't be stored because conversion destination and dedup key match.
   auto result = storage()->MaybeCreateAndStoreReport(
       TriggerBuilder()
-          .SetConversionDestination(net::SchemefulSite(
-              url::Origin::Create(GURL("https://a.example"))))
+          .SetDestinationOrigin(url::Origin::Create(GURL("https://a.example")))
           .SetDedupKey(11)
           .SetTriggerData(74)
           .Build());
@@ -1357,8 +1355,8 @@
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kDeduplicated,
             MaybeCreateAndStoreEventLevelReport(
                 TriggerBuilder()
-                    .SetConversionDestination(net::SchemefulSite(
-                        url::Origin::Create(GURL("https://b.example"))))
+                    .SetDestinationOrigin(
+                        url::Origin::Create(GURL("https://b.example")))
                     .SetDedupKey(12)
                     .SetTriggerData(75)
                     .Build()));
@@ -1387,8 +1385,8 @@
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(
                 TriggerBuilder()
-                    .SetConversionDestination(net::SchemefulSite(
-                        url::Origin::Create(GURL("https://a.example"))))
+                    .SetDestinationOrigin(
+                        url::Origin::Create(GURL("https://a.example")))
                     .SetDedupKey(2)
                     .SetTriggerData(3)
                     .Build()));
@@ -1409,8 +1407,8 @@
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kDeduplicated,
             MaybeCreateAndStoreEventLevelReport(
                 TriggerBuilder()
-                    .SetConversionDestination(net::SchemefulSite(
-                        url::Origin::Create(GURL("https://a.example"))))
+                    .SetDestinationOrigin(
+                        url::Origin::Create(GURL("https://a.example")))
                     .SetDedupKey(2)
                     .SetTriggerData(5)
                     .Build()));
@@ -2197,7 +2195,7 @@
 
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(AttributionTrigger(
-                net::SchemefulSite(origin), origin,
+                origin, origin,
                 /*filters=*/AttributionFilterData(),
                 /*debug_key=*/absl::nullopt,
                 {AttributionTrigger::EventTriggerData(
@@ -2287,7 +2285,7 @@
 
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
             MaybeCreateAndStoreEventLevelReport(AttributionTrigger(
-                net::SchemefulSite(origin), origin,
+                origin, origin,
                 /*filters=*/AttributionFilterData(),
                 /*debug_key=*/absl::nullopt, event_triggers)));
 
@@ -2310,7 +2308,7 @@
               {{"abc", {"123"}}}))
           .Build());
 
-  AttributionTrigger trigger1(net::SchemefulSite(origin), origin,
+  AttributionTrigger trigger1(origin, origin,
                               /*filters=*/
                               *AttributionFilterData::FromTriggerFilterValues({
                                   {"abc", {"456"}},
@@ -2318,7 +2316,7 @@
                               /*debug_key=*/absl::nullopt,
                               /*event_triggers=*/{});
 
-  AttributionTrigger trigger2(net::SchemefulSite(origin), origin,
+  AttributionTrigger trigger2(origin, origin,
                               /*filters=*/
                               *AttributionFilterData::FromTriggerFilterValues({
                                   {"abc", {"123"}},
diff --git a/content/browser/attribution_reporting/attribution_test_utils.cc b/content/browser/attribution_reporting/attribution_test_utils.cc
index f4679f92..d26275e 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.cc
+++ b/content/browser/attribution_reporting/attribution_test_utils.cc
@@ -34,7 +34,6 @@
 
 const char kDefaultImpressionOrigin[] = "https://impression.test/";
 const char kDefaultTriggerOrigin[] = "https://sub.conversion.test/";
-const char kDefaultTriggerDestination[] = "https://conversion.test/";
 const char kDefaultReportOrigin[] = "https://report.test/";
 
 // Default expiry time for impressions for testing.
@@ -359,9 +358,10 @@
 }
 
 void MockAttributionManager::NotifyReportSent(const AttributionReport& report,
+                                              bool is_debug_report,
                                               const SendResult& info) {
   for (auto& observer : observers_)
-    observer.OnReportSent(report, info);
+    observer.OnReportSent(report, is_debug_report, info);
 }
 
 void MockAttributionManager::NotifyTriggerHandled(
@@ -497,8 +497,7 @@
 }
 
 TriggerBuilder::TriggerBuilder()
-    : conversion_destination_(
-          net::SchemefulSite(GURL(kDefaultTriggerDestination))),
+    : destination_origin_(url::Origin::Create(GURL(kDefaultTriggerOrigin))),
       reporting_origin_(url::Origin::Create(GURL(kDefaultReportOrigin))) {}
 
 TriggerBuilder::~TriggerBuilder() = default;
@@ -514,9 +513,9 @@
   return *this;
 }
 
-TriggerBuilder& TriggerBuilder::SetConversionDestination(
-    net::SchemefulSite conversion_destination) {
-  conversion_destination_ = std::move(conversion_destination);
+TriggerBuilder& TriggerBuilder::SetDestinationOrigin(
+    url::Origin destination_origin) {
+  destination_origin_ = std::move(destination_origin);
   return *this;
 }
 
@@ -544,7 +543,7 @@
 }
 
 AttributionTrigger TriggerBuilder::Build() const {
-  return AttributionTrigger(trigger_data_, conversion_destination_,
+  return AttributionTrigger(trigger_data_, destination_origin_,
                             reporting_origin_, event_source_trigger_data_,
                             priority_, dedup_key_, debug_key_);
 }
@@ -697,7 +696,7 @@
 
 bool operator==(const AttributionTrigger& a, const AttributionTrigger& b) {
   const auto tie = [](const AttributionTrigger& t) {
-    return std::make_tuple(t.conversion_destination(), t.reporting_origin(),
+    return std::make_tuple(t.destination_origin(), t.reporting_origin(),
                            t.debug_key(), t.event_triggers());
   };
   return tie(a) == tie(b);
@@ -930,8 +929,7 @@
 
 std::ostream& operator<<(std::ostream& out,
                          const AttributionTrigger& conversion) {
-  out << "{conversion_destination="
-      << conversion.conversion_destination().Serialize()
+  out << "{destination_origin=" << conversion.destination_origin()
       << ",reporting_origin=" << conversion.reporting_origin() << ",debug_key="
       << (conversion.debug_key() ? base::NumberToString(*conversion.debug_key())
                                  : "null")
@@ -1209,9 +1207,8 @@
 ::testing::Matcher<AttributionTrigger> AttributionTriggerMatches(
     const AttributionTriggerMatcherConfig& cfg) {
   return AllOf(
-      Property("conversion_destination",
-               &AttributionTrigger::conversion_destination,
-               cfg.conversion_destination),
+      Property("destination_origin", &AttributionTrigger::destination_origin,
+               cfg.destination_origin),
       Property("reporting_origin", &AttributionTrigger::reporting_origin,
                cfg.reporting_origin),
       Property("filters", &AttributionTrigger::filters, cfg.filters),
diff --git a/content/browser/attribution_reporting/attribution_test_utils.h b/content/browser/attribution_reporting/attribution_test_utils.h
index 2911cf1..63add89 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.h
+++ b/content/browser/attribution_reporting/attribution_test_utils.h
@@ -41,7 +41,6 @@
 #include "content/browser/attribution_reporting/stored_source.h"
 #include "content/public/browser/attribution_reporting.h"
 #include "content/test/test_content_browser_client.h"
-#include "net/base/schemeful_site.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/attribution_reporting/constants.h"
@@ -308,6 +307,7 @@
   void NotifySourceHandled(const StorableSource& source,
                            StorableSource::Result result);
   void NotifyReportSent(const AttributionReport& report,
+                        bool is_debug_report,
                         const SendResult& info);
   void NotifyTriggerHandled(const CreateReportResult& result);
 
@@ -408,8 +408,7 @@
 
   TriggerBuilder& SetEventSourceTriggerData(uint64_t event_source_trigger_data);
 
-  TriggerBuilder& SetConversionDestination(
-      net::SchemefulSite conversion_destination);
+  TriggerBuilder& SetDestinationOrigin(url::Origin destination_origin);
 
   TriggerBuilder& SetReportingOrigin(url::Origin reporting_origin);
 
@@ -424,7 +423,7 @@
  private:
   uint64_t trigger_data_ = 111;
   uint64_t event_source_trigger_data_ = 0;
-  net::SchemefulSite conversion_destination_;
+  url::Origin destination_origin_;
   url::Origin reporting_origin_;
   int64_t priority_ = 0;
   absl::optional<uint64_t> dedup_key_;
@@ -707,9 +706,8 @@
 
 // Trigger matchers.
 
-MATCHER_P(TriggerConversionDestinationIs, matcher, "") {
-  return ExplainMatchResult(matcher, arg.conversion_destination(),
-                            result_listener);
+MATCHER_P(TriggerDestinationOriginIs, matcher, "") {
+  return ExplainMatchResult(matcher, arg.destination_origin(), result_listener);
 }
 
 // Report matchers
@@ -790,8 +788,7 @@
 EventTriggerDataMatches(const EventTriggerDataMatcherConfig&);
 
 struct AttributionTriggerMatcherConfig {
-  ::testing::Matcher<const net::SchemefulSite&> conversion_destination =
-      ::testing::_;
+  ::testing::Matcher<const url::Origin&> destination_origin = ::testing::_;
   ::testing::Matcher<const url::Origin&> reporting_origin = ::testing::_;
   ::testing::Matcher<const AttributionFilterData&> filters = ::testing::_;
   ::testing::Matcher<absl::optional<uint64_t>> debug_key = ::testing::_;
diff --git a/content/browser/attribution_reporting/attribution_trigger.cc b/content/browser/attribution_reporting/attribution_trigger.cc
index afdf927..dc0884e 100644
--- a/content/browser/attribution_reporting/attribution_trigger.cc
+++ b/content/browser/attribution_reporting/attribution_trigger.cc
@@ -24,30 +24,29 @@
       not_filters(std::move(not_filters)) {}
 
 AttributionTrigger::AttributionTrigger(
-    net::SchemefulSite conversion_destination,
+    url::Origin destination_origin,
     url::Origin reporting_origin,
     AttributionFilterData filters,
     absl::optional<uint64_t> debug_key,
     std::vector<EventTriggerData> event_triggers)
-    : conversion_destination_(std::move(conversion_destination)),
+    : destination_origin_(std::move(destination_origin)),
       reporting_origin_(std::move(reporting_origin)),
       filters_(std::move(filters)),
       debug_key_(debug_key),
       event_triggers_(std::move(event_triggers)) {
   DCHECK(!reporting_origin_.opaque());
-  DCHECK(!conversion_destination_.opaque());
+  DCHECK(!destination_origin_.opaque());
 }
 
-AttributionTrigger::AttributionTrigger(
-    uint64_t trigger_data,
-    net::SchemefulSite conversion_destination,
-    url::Origin reporting_origin,
-    uint64_t event_source_trigger_data,
-    int64_t priority,
-    absl::optional<uint64_t> dedup_key,
-    absl::optional<uint64_t> debug_key)
+AttributionTrigger::AttributionTrigger(uint64_t trigger_data,
+                                       url::Origin destination_origin,
+                                       url::Origin reporting_origin,
+                                       uint64_t event_source_trigger_data,
+                                       int64_t priority,
+                                       absl::optional<uint64_t> dedup_key,
+                                       absl::optional<uint64_t> debug_key)
     : AttributionTrigger(
-          std::move(conversion_destination),
+          std::move(destination_origin),
           std::move(reporting_origin),
           /*filters=*/AttributionFilterData(),
           debug_key,
diff --git a/content/browser/attribution_reporting/attribution_trigger.h b/content/browser/attribution_reporting/attribution_trigger.h
index 83e86ca..41aa0aed 100644
--- a/content/browser/attribution_reporting/attribution_trigger.h
+++ b/content/browser/attribution_reporting/attribution_trigger.h
@@ -11,7 +11,6 @@
 
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
 #include "content/common/content_export.h"
-#include "net/base/schemeful_site.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/origin.h"
 
@@ -77,7 +76,7 @@
   // Should only be created with values that the browser process has already
   // validated. |conversion_destination| should be filled by a navigation origin
   // known by the browser process.
-  AttributionTrigger(net::SchemefulSite conversion_destination,
+  AttributionTrigger(url::Origin destination_origin,
                      url::Origin reporting_origin,
                      AttributionFilterData filters,
                      absl::optional<uint64_t> debug_key,
@@ -92,7 +91,7 @@
   // TODO(apaseltiner): Remove this constructor once the old
   // trigger-registration API surface is removed.
   AttributionTrigger(uint64_t trigger_data,
-                     net::SchemefulSite conversion_destination,
+                     url::Origin destination_origin,
                      url::Origin reporting_origin,
                      uint64_t event_source_trigger_data,
                      int64_t priority,
@@ -105,9 +104,7 @@
   AttributionTrigger& operator=(AttributionTrigger&& other);
   ~AttributionTrigger();
 
-  const net::SchemefulSite& conversion_destination() const {
-    return conversion_destination_;
-  }
+  const url::Origin& destination_origin() const { return destination_origin_; }
 
   const url::Origin& reporting_origin() const { return reporting_origin_; }
 
@@ -122,8 +119,8 @@
   }
 
  private:
-  // Schemeful site that this conversion event occurred on.
-  net::SchemefulSite conversion_destination_;
+  // Origin that this conversion event occurred on.
+  url::Origin destination_origin_;
 
   // Origin of the conversion redirect url, and the origin that will receive any
   // reports.
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 3a156c59..21756b9 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -295,6 +295,7 @@
   RFH_CREATE_FENCED_FRAME_IN_SANDBOXED_FRAME = 268,
   RFH_UNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME = 269,
   RFH_BEFOREUNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME = 270,
+  MSDH_GET_OPEN_DEVICE_USE_WITHOUT_FEATURE = 271,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/devtools/service_worker_devtools_agent_host.cc b/content/browser/devtools/service_worker_devtools_agent_host.cc
index 2d4ef85..c1895858 100644
--- a/content/browser/devtools/service_worker_devtools_agent_host.cc
+++ b/content/browser/devtools/service_worker_devtools_agent_host.cc
@@ -294,6 +294,8 @@
 void ServiceWorkerDevToolsAgentHost::WorkerStopped() {
   DCHECK_NE(WORKER_TERMINATED, state_);
   state_ = WORKER_TERMINATED;
+  worker_process_id_ = content::ChildProcessHost::kInvalidUniqueID;
+  worker_route_id_ = MSG_ROUTING_NONE;
   for (auto* inspector : protocol::InspectorHandler::ForAgentHost(this))
     inspector->TargetCrashed();
   GetRendererChannel()->SetRenderer(mojo::NullRemote(), mojo::NullReceiver(),
@@ -331,6 +333,10 @@
 
 void ServiceWorkerDevToolsAgentHost::UpdateLoaderFactories(
     base::OnceClosure callback) {
+  if (state_ == WORKER_TERMINATED) {
+    std::move(callback).Run();
+    return;
+  }
   RenderProcessHost* rph = RenderProcessHost::FromID(worker_process_id_);
   if (!rph) {
     std::move(callback).Run();
@@ -352,6 +358,12 @@
         coep_reporter_for_subresource_loader.InitWithNewPipeAndPassReceiver());
   }
 
+  auto* version = context_wrapper_->GetLiveVersion(version_id_);
+  if (!version) {
+    std::move(callback).Run();
+    return;
+  }
+
   auto script_bundle = EmbeddedWorkerInstance::CreateFactoryBundle(
       rph, worker_route_id_, origin, client_security_state_.Clone(),
       std::move(coep_reporter_for_script_loader),
@@ -363,9 +375,6 @@
       ContentBrowserClient::URLLoaderFactoryType::kServiceWorkerSubResource,
       GetId());
 
-  auto* version = context_wrapper_->GetLiveVersion(version_id_);
-  if (!version)
-    return;
   version->embedded_worker()->UpdateLoaderFactories(
       std::move(script_bundle), std::move(subresource_bundle));
 
diff --git a/content/browser/interest_group/auction_runner.h b/content/browser/interest_group/auction_runner.h
index 564a10c..5c52841 100644
--- a/content/browser/interest_group/auction_runner.h
+++ b/content/browser/interest_group/auction_runner.h
@@ -40,10 +40,10 @@
   // appropriate.
   struct InterestGroupKey {
     InterestGroupKey(url::Origin o, std::string n) : owner(o), name(n) {}
-    constexpr bool operator<(const InterestGroupKey& other) const {
+    inline bool operator<(const InterestGroupKey& other) const {
       return owner != other.owner ? owner < other.owner : name < other.name;
     }
-    constexpr bool operator==(const InterestGroupKey& other) const {
+    inline bool operator==(const InterestGroupKey& other) const {
       return owner == other.owner && name == other.name;
     }
     url::Origin owner;
diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host.cc b/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
index 3eca3db..9285c94c 100644
--- a/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
+++ b/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
@@ -565,6 +565,32 @@
 }
 #endif
 
+void MediaStreamDispatcherHost::GetOpenDevice(
+    const base::UnguessableToken& session_id,
+    GetOpenDeviceCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  if (!base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer)) {
+    ReceivedBadMessage(render_process_id_,
+                       bad_message::MSDH_GET_OPEN_DEVICE_USE_WITHOUT_FEATURE);
+
+    std::move(callback).Run(
+        blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
+    return;
+  }
+  // TODO(https://crbug.com/1288839): Implement GetOpenDevice in
+  // MediaStreamManager and call that.
+
+  // TODO(https://crbug.com/1288839): Decide whether we need to have another
+  // mojo method, called by the first renderer to say "I'm going to be
+  // transferring this track, allow the receiving renderer to call GetOpenDevice
+  // on it", and whether we can/need to specific the destination renderer/frame
+  // in this case.
+
+  std::move(callback).Run(blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED,
+                          nullptr);
+}
+
 void MediaStreamDispatcherHost::ReceivedBadMessage(
     int render_process_id,
     bad_message::BadMessageReason reason) {
diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host.h b/content/browser/renderer_host/media/media_stream_dispatcher_host.h
index 9407d43..e127fa8 100644
--- a/content/browser/renderer_host/media/media_stream_dispatcher_host.h
+++ b/content/browser/renderer_host/media/media_stream_dispatcher_host.h
@@ -107,6 +107,8 @@
                                 CropCallback callback,
                                 bool crop_id_passed_validation);
 #endif
+  void GetOpenDevice(const base::UnguessableToken& session_id,
+                     GetOpenDeviceCallback callback) override;
 
   void DoGenerateStream(
       int32_t request_id,
diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc b/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
index c4785b5..65e7433 100644
--- a/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
+++ b/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
@@ -469,6 +469,12 @@
     return true;
   }
 
+  void GetOpenDevice(
+      const base::UnguessableToken& session_id,
+      MediaStreamDispatcherHost::GetOpenDeviceCallback callback) {
+    host_->GetOpenDevice(session_id, std::move(callback));
+  }
+
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<MockMediaStreamDispatcherHost> host_;
   std::unique_ptr<MediaStreamManager> media_stream_manager_;
@@ -1070,4 +1076,22 @@
   EXPECT_EQ(group_id2, host_->opened_device_.group_id);
 }
 
+TEST_F(MediaStreamDispatcherHostTest, GetOpenDeviceWithoutFeatureFails) {
+  EXPECT_CALL(
+      *this,
+      MockOnBadMessage(kProcessId,
+                       bad_message::MSDH_GET_OPEN_DEVICE_USE_WITHOUT_FEATURE));
+
+  base::RunLoop loop;
+  GetOpenDevice(base::UnguessableToken(),
+                base::BindOnce([](blink::mojom::MediaStreamRequestResult result,
+                                  blink::mojom::GetOpenDeviceResponsePtr ptr) {
+                  EXPECT_EQ(
+                      blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED,
+                      result);
+                  EXPECT_FALSE(ptr);
+                }).Then(loop.QuitClosure()));
+  loop.Run();
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h
index 0c76683..72a09dce490 100644
--- a/content/browser/renderer_host/media/media_stream_manager.h
+++ b/content/browser/renderer_host/media/media_stream_manager.h
@@ -122,6 +122,10 @@
       base::RepeatingCallback<void(const std::string& label,
                                    const blink::MediaStreamDevice& device)>;
 
+  using GetOpenDeviceCallback = base::OnceCallback<void(
+      blink::mojom::MediaStreamRequestResult result,
+      absl::optional<blink::mojom::GetOpenDeviceResponse> response)>;
+
   // Callback for testing.
   using GenerateStreamTestCallback =
       base::OnceCallback<bool(const blink::StreamControls&)>;
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 0ca1820..61e4041 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -1523,8 +1523,6 @@
 }
 
 RenderFrameHostImpl::~RenderFrameHostImpl() {
-  CHECK_EQ(check_if_deleted_request_count_, 0);
-
   // The lifetime of this object has ended, so remove it from the id map before
   // calling any delegates/observers, so that any calls to |FromID| no longer
   // return |this|.
@@ -13346,22 +13344,4 @@
   return o << LifecycleStateImplToString(s);
 }
 
-std::unique_ptr<RenderFrameHostImpl::CheckOnDeleteRef>
-RenderFrameHostImpl::EnableCheckIfDeleted() {
-  // Uses WrapUnique() as constructor is private.
-  return base::WrapUnique(new CheckOnDeleteRef(this));
-}
-
-RenderFrameHostImpl::CheckOnDeleteRef::~CheckOnDeleteRef() {
-  --(host_->check_if_deleted_request_count_);
-  CHECK_GE(host_->check_if_deleted_request_count_, 0);
-}
-
-RenderFrameHostImpl::CheckOnDeleteRef::CheckOnDeleteRef(
-    RenderFrameHostImpl* host)
-    : host_(host) {
-  ++(host_->check_if_deleted_request_count_);
-  CHECK_GT(host_->check_if_deleted_request_count_, 0);
-}
-
 }  // namespace content
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index e25974b..5b9eca4 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -2429,23 +2429,6 @@
 
   void DidChangeReferrerPolicy(network::mojom::ReferrerPolicy referrer_policy);
 
-  class CheckOnDeleteRef {
-   public:
-    CheckOnDeleteRef(const CheckOnDeleteRef&) = delete;
-    CheckOnDeleteRef& operator=(const CheckOnDeleteRef&) = delete;
-    ~CheckOnDeleteRef();
-
-   private:
-    friend class RenderFrameHostImpl;
-
-    explicit CheckOnDeleteRef(RenderFrameHostImpl* host);
-
-    RenderFrameHostImpl* host_;
-  };
-
-  // TODO(https://crbug.com/1262098): used to track down crash.
-  std::unique_ptr<CheckOnDeleteRef> EnableCheckIfDeleted();
-
   // TODO: While FencedFrame shadow DOM implementation exists and is dependent
   // on the effective frame policy in BrowsingContextState, fenced frame status
   // is dependent on FrameTreeNode being initialized and associated with a
@@ -4149,8 +4132,6 @@
   BackForwardCacheDisablingFeaturesCallback
       back_forward_cache_disabling_features_callback_for_testing_;
 
-  int check_if_deleted_request_count_ = 0;
-
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   // Manages the snapshot processing by Screen AI, if enabled.
   std::unique_ptr<AXScreenAIAnnotator> ax_screen_ai_annotator_;
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index 07f5f81..a47eae3 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -3458,9 +3458,6 @@
     }
   }
 
-  // TODO(https://crbug.com/1262098): used to track down crash.
-  auto check_on_delete_ref = render_frame_host_->EnableCheckIfDeleted();
-
   // For all main frames, the RenderWidgetHost will not be destroyed when the
   // local frame is detached. https://crbug.com/419087
   //
@@ -3506,9 +3503,7 @@
   // also exist. For a local root frame, they share lifetimes exactly. For
   // another child frame, the RenderWidgetHostView comes from a parent, but if
   // this renderer frame is live its ancestors must be as well.
-  // TODO(https://crbug.com/1262098): used to track down crash, converted
-  // from DCHECK to CHECK.
-  CHECK(new_view);
+  DCHECK(new_view);
 
   if (focus_render_view) {
     if (is_main_frame) {
@@ -3556,8 +3551,6 @@
   delegate_->NotifySwappedFromRenderManager(old_render_frame_host.get(),
                                             render_frame_host_.get());
 
-  check_on_delete_ref.reset();
-
   // Make the new view show the contents of old view until it has something
   // useful to show.
   if (is_main_frame && old_view && old_view != new_view)
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 5f006f5..e620fc4 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -2098,6 +2098,9 @@
   if (!view_.parent() || !is_showing_)
     return;
 
+  if (gesture_listener_manager_)
+    gesture_listener_manager_->DidOverscroll(params);
+
   if (overscroll_controller_)
     overscroll_controller_->OnOverscrolled(params);
 }
diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_unittest.cc
index 6e5b85d..bf456ae4 100644
--- a/content/browser/storage_partition_impl_unittest.cc
+++ b/content/browser/storage_partition_impl_unittest.cc
@@ -63,7 +63,6 @@
 #include "content/public/test/test_utils.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "net/base/network_isolation_key.h"
-#include "net/base/schemeful_site.h"
 #include "net/base/test_completion_callback.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_access_result.h"
@@ -2063,11 +2062,10 @@
                                           .SetConversionOrigin(conv)
                                           .SetExpiry(base::Days(2))
                                           .Build());
-    attribution_manager->HandleTrigger(
-        TriggerBuilder()
-            .SetConversionDestination(net::SchemefulSite(conv))
-            .SetReportingOrigin(reporter)
-            .Build());
+    attribution_manager->HandleTrigger(TriggerBuilder()
+                                           .SetDestinationOrigin(conv)
+                                           .SetReportingOrigin(reporter)
+                                           .Build());
   }
 
   EXPECT_EQ(5u, GetAttributionReportsForTesting(attribution_manager,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 9082686..f2caa45 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -6622,9 +6622,6 @@
   if (old_frame && !new_frame->GetParent()) {
     RenderWidgetHostImpl* old_widget = old_frame->GetRenderWidgetHost();
     RenderWidgetHostImpl* new_widget = new_frame->GetRenderWidgetHost();
-    // TODO(https://crbug.com/1262098): CHECKs are to help track down crash.
-    CHECK(new_widget);
-    CHECK(old_widget);
     new_widget->SetImportance(old_widget->importance());
   }
 #endif
@@ -7951,9 +7948,6 @@
   DCHECK_NE(new_frame->lifecycle_state(),
             RenderFrameHostImpl::LifecycleStateImpl::kSpeculative);
 
-  // TODO(https://crbug.com/1262098): CHECKs are to help track down crash.
-  CHECK(new_frame);
-
   // Only fire RenderViewHostChanged if it is related to our FrameTree, as
   // observers can not deal with events coming from non-primary FrameTree.
   // TODO(https://crbug.com/1168562): Update observers to deal with the events,
diff --git a/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java b/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
index e76f6b4b..f38707b 100644
--- a/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
@@ -309,6 +309,14 @@
         return false;
     }
 
+    @CalledByNative
+    private void didOverscroll(float accumulatedOverscrollX, float accumulatedOverscrollY) {
+        for (mIterator.rewind(); mIterator.hasNext();) {
+            GestureStateListener listener = mIterator.next();
+            listener.didOverscroll(accumulatedOverscrollX, accumulatedOverscrollY);
+        }
+    }
+
     @SuppressWarnings("unused")
     @CalledByNative
     private void updateScrollInfo(float scrollOffsetX, float scrollOffsetY, float pageScaleFactor,
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/GestureStateListener.java b/content/public/android/java/src/org/chromium/content_public/browser/GestureStateListener.java
index 56a2c13c..96a3f732 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/GestureStateListener.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/GestureStateListener.java
@@ -81,6 +81,14 @@
     public void onLongPress() {}
 
     /**
+     * Called on overscroll. This happens when user tries to scroll beyond scroll bounds, or when
+     * a fling animation hits scroll bounds.
+     * @param accumulatedOverscrollX see `ui::DidOverscrollParams::accumulated_overscroll`.
+     * @param accumulatedOverscrollY see `ui::DidOverscrollParams::accumulated_overscroll`.
+     */
+    public void didOverscroll(float accumulatedOverscrollX, float accumulatedOverscrollY) {}
+
+    /**
      * Called when the gesture source is being destroyed.
      */
     public void onDestroyed() {}
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index f4aeda8..fe52508 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -543,6 +543,10 @@
 const base::Feature kMediaLicenseBackend{"MediaLicenseBackend",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Allow cross-context transfer of MediaStreamTracks.
+const base::Feature kMediaStreamTrackTransfer{
+    "MediaStreamTrackTransfer", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // If enabled Mojo uses a dedicated background thread to listen for incoming
 // IPCs. Otherwise it's configured to use Content's IO thread for that purpose.
 const base::Feature kMojoDedicatedThread{"MojoDedicatedThread",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index a07338b..45f123b 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -140,6 +140,7 @@
 CONTENT_EXPORT extern const base::FeatureParam<MBIMode> kMBIModeParam;
 CONTENT_EXPORT extern const base::Feature kMediaDevicesSystemMonitorCache;
 CONTENT_EXPORT extern const base::Feature kMediaLicenseBackend;
+CONTENT_EXPORT extern const base::Feature kMediaStreamTrackTransfer;
 CONTENT_EXPORT extern const base::Feature kMojoDedicatedThread;
 CONTENT_EXPORT extern const base::Feature kMojoVideoCapture;
 CONTENT_EXPORT extern const base::Feature kMojoVideoCaptureSecondary;
diff --git a/content/test/attribution_simulator_input_parser.cc b/content/test/attribution_simulator_input_parser.cc
index 40566c8..db66f58c 100644
--- a/content/test/attribution_simulator_input_parser.cc
+++ b/content/test/attribution_simulator_input_parser.cc
@@ -21,11 +21,11 @@
 #include "base/values.h"
 #include "content/browser/attribution_reporting/attribution_aggregatable_source.h"
 #include "content/browser/attribution_reporting/attribution_filter_data.h"
+#include "content/browser/attribution_reporting/attribution_host_utils.h"
 #include "content/browser/attribution_reporting/attribution_source_type.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
 #include "content/browser/attribution_reporting/storable_source.h"
-#include "net/base/schemeful_site.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "url/gurl.h"
@@ -220,7 +220,7 @@
 
     base::Time trigger_time = ParseTime(trigger, "trigger_time");
     url::Origin reporting_origin = ParseOrigin(trigger, "reporting_origin");
-    net::SchemefulSite destination(ParseOrigin(trigger, "destination"));
+    url::Origin destination_origin = ParseOrigin(trigger, "destination");
 
     absl::optional<uint64_t> debug_key;
     AttributionFilterData filters;
@@ -245,7 +245,7 @@
     events_.emplace_back(
         AttributionTriggerAndTime{
             .trigger = AttributionTrigger(
-                std::move(destination), std::move(reporting_origin),
+                std::move(destination_origin), std::move(reporting_origin),
                 std::move(filters), debug_key, std::move(event_triggers)),
             .time = trigger_time,
         },
@@ -302,8 +302,8 @@
     if (const std::string* v = dict.FindStringKey(key))
       origin = url::Origin::Create(GURL(*v));
 
-    if (origin.opaque())
-      *Error() << "must be a valid origin";
+    if (!attribution_host_utils::IsOriginTrustworthyForAttributions(origin))
+      *Error() << "must be a valid, secure origin";
 
     return origin;
   }
diff --git a/content/test/attribution_simulator_input_parser_unittest.cc b/content/test/attribution_simulator_input_parser_unittest.cc
index c1a7c62..b110dfab 100644
--- a/content/test/attribution_simulator_input_parser_unittest.cc
+++ b/content/test/attribution_simulator_input_parser_unittest.cc
@@ -16,7 +16,6 @@
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
 #include "content/browser/attribution_reporting/storable_source.h"
-#include "net/base/schemeful_site.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -253,8 +252,8 @@
           Pair(
               AttributionTriggerAndTime{
                   .trigger = AttributionTrigger(
-                      /*conversion_destination=*/net::SchemefulSite(
-                          url::Origin::Create(GURL("https://a.d1.test"))),
+                      /*destination_origin=*/
+                      url::Origin::Create(GURL("https://a.d1.test")),
                       /*reporting_origin=*/
                       url::Origin::Create(GURL("https://a.r.test")),
                       *AttributionFilterData::FromTriggerFilterValues({
@@ -288,8 +287,8 @@
           Pair(
               AttributionTriggerAndTime{
                   .trigger = AttributionTrigger(
-                      /*conversion_destination=*/net::SchemefulSite(
-                          url::Origin::Create(GURL("https://a.d2.test"))),
+                      /*destination_origin=*/
+                      url::Origin::Create(GURL("https://a.d2.test")),
                       /*reporting_origin=*/
                       url::Origin::Create(GURL("https://b.r.test")),
                       AttributionFilterData(),
@@ -379,7 +378,7 @@
         }]})json",
     },
     {
-        R"(["sources"][0]["reporting_origin"]: must be a valid origin)",
+        R"(["sources"][0]["reporting_origin"]: must be a valid, secure origin)",
         R"json({"sources": [{
           "source_type": "navigation",
           "source_time": 1643235574,
@@ -391,7 +390,20 @@
         }]})json",
     },
     {
-        R"(["sources"][0]["source_origin"]: must be a valid origin)",
+        R"(["sources"][0]["reporting_origin"]: must be a valid, secure origin)",
+        R"json({"sources": [{
+          "source_type": "navigation",
+          "source_time": 1643235574,
+          "source_origin": "https://a.s.test",
+          "reporting_origin": "http://r.test",
+          "registration_config": {
+            "source_event_id": "123",
+            "destination": "https://a.d.test"
+          }
+        }]})json",
+    },
+    {
+        R"(["sources"][0]["source_origin"]: must be a valid, secure origin)",
         R"json({"sources": [{
           "source_type": "navigation",
           "source_time": 1643235574,
@@ -434,7 +446,7 @@
         }]})json",
     },
     {
-        R"(["sources"][0]["registration_config"]["destination"]: must be a valid origin)",
+        R"(["sources"][0]["registration_config"]["destination"]: must be a valid, secure origin)",
         R"json({"sources": [{
           "source_type": "navigation",
           "source_time": 1643235574,
@@ -575,7 +587,7 @@
         }]})json",
     },
     {
-        R"(["triggers"][0]["destination"]: must be a valid origin)",
+        R"(["triggers"][0]["destination"]: must be a valid, secure origin)",
         R"json({"triggers": [{
           "trigger_time": 1643235576,
           "reporting_origin": "https://a.r.test",
@@ -583,7 +595,7 @@
         }]})json",
     },
     {
-        R"(["triggers"][0]["reporting_origin"]: must be a valid origin)",
+        R"(["triggers"][0]["reporting_origin"]: must be a valid, secure origin)",
         R"json({"triggers": [{
           "trigger_time": 1643235576,
           "destination": " https://a.d1.test",
diff --git a/extensions/browser/api/messaging/message_service.cc b/extensions/browser/api/messaging/message_service.cc
index 631024c8..33f084b6 100644
--- a/extensions/browser/api/messaging/message_service.cc
+++ b/extensions/browser/api/messaging/message_service.cc
@@ -514,6 +514,7 @@
                                       const PortId& source_port_id,
                                       int tab_id,
                                       int frame_id,
+                                      const std::string& document_id,
                                       const std::string& extension_id,
                                       const std::string& channel_name) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -545,9 +546,9 @@
 
   const PortId receiver_port_id = source_port_id.GetOppositePortId();
   std::unique_ptr<MessagePort> receiver =
-      messaging_delegate_->CreateReceiverForTab(weak_factory_.GetWeakPtr(),
-                                                extension_id, receiver_port_id,
-                                                receiver_contents, frame_id);
+      messaging_delegate_->CreateReceiverForTab(
+          weak_factory_.GetWeakPtr(), extension_id, receiver_port_id,
+          receiver_contents, frame_id, document_id);
   if (!receiver.get()) {
     opener_port->DispatchOnDisconnect(kReceivingEndDoesntExistError);
     return;
diff --git a/extensions/browser/api/messaging/message_service.h b/extensions/browser/api/messaging/message_service.h
index dad94ba2..3f2001c1 100644
--- a/extensions/browser/api/messaging/message_service.h
+++ b/extensions/browser/api/messaging/message_service.h
@@ -101,6 +101,7 @@
                         const PortId& source_port_id,
                         int tab_id,
                         int frame_id,
+                        const std::string& document_id,
                         const std::string& extension_id,
                         const std::string& channel_name);
 
diff --git a/extensions/browser/api/messaging/messaging_api_message_filter.cc b/extensions/browser/api/messaging/messaging_api_message_filter.cc
index 619c6d64..abd3f6b 100644
--- a/extensions/browser/api/messaging/messaging_api_message_filter.cc
+++ b/extensions/browser/api/messaging/messaging_api_message_filter.cc
@@ -324,7 +324,7 @@
                                   source_context);
   MessageService::Get(browser_context_)
       ->OpenChannelToTab(source_endpoint, port_id, info.tab_id, info.frame_id,
-                         extension_id, channel_name);
+                         info.document_id, extension_id, channel_name);
 }
 
 void MessagingAPIMessageFilter::OnOpenMessagePort(const PortContext& source,
diff --git a/extensions/browser/api/messaging/messaging_delegate.cc b/extensions/browser/api/messaging/messaging_delegate.cc
index 9553d4363..5715531 100644
--- a/extensions/browser/api/messaging/messaging_delegate.cc
+++ b/extensions/browser/api/messaging/messaging_delegate.cc
@@ -35,7 +35,8 @@
     const std::string& extension_id,
     const PortId& receiver_port_id,
     content::WebContents* receiver_contents,
-    int receiver_frame_id) {
+    int receiver_frame_id,
+    const std::string& receiver_document_id) {
   NOTIMPLEMENTED();
   return nullptr;
 }
diff --git a/extensions/browser/api/messaging/messaging_delegate.h b/extensions/browser/api/messaging/messaging_delegate.h
index 538a8ba9..351a70a 100644
--- a/extensions/browser/api/messaging/messaging_delegate.h
+++ b/extensions/browser/api/messaging/messaging_delegate.h
@@ -63,7 +63,8 @@
       const std::string& extension_id,
       const PortId& receiver_port_id,
       content::WebContents* receiver_contents,
-      int receiver_frame_id);
+      int receiver_frame_id,
+      const std::string& receiver_document_id);
 
   // Creates a MessagePort for a native app. If the port cannot be created,
   // returns nullptr and may populate |error_out|.
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index 3767ea7e..73683a9b 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -159,6 +159,9 @@
   // Frame ID of the destination. -1 for all frames, 0 for main frame and
   // positive if the destination is a specific child frame.
   IPC_STRUCT_MEMBER(int, frame_id)
+
+  // The unique ID of the document of the target frame.
+  IPC_STRUCT_MEMBER(std::string, document_id)
 IPC_STRUCT_END()
 
 IPC_STRUCT_TRAITS_BEGIN(extensions::MessagingEndpoint)
diff --git a/extensions/renderer/ipc_message_sender.cc b/extensions/renderer/ipc_message_sender.cc
index 5e25676..5d2ac72 100644
--- a/extensions/renderer/ipc_message_sender.cc
+++ b/extensions/renderer/ipc_message_sender.cc
@@ -169,6 +169,8 @@
         ExtensionMsg_TabTargetConnectionInfo info;
         info.tab_id = *target.tab_id;
         info.frame_id = *target.frame_id;
+        if (target.document_id)
+          info.document_id = *target.document_id;
         render_frame->Send(new ExtensionHostMsg_OpenChannelToTab(
             frame_context, info, extension->id(), channel_name, port_id));
         break;
diff --git a/extensions/renderer/message_target.cc b/extensions/renderer/message_target.cc
index 892d161..18505808 100644
--- a/extensions/renderer/message_target.cc
+++ b/extensions/renderer/message_target.cc
@@ -13,6 +13,17 @@
   return target;
 }
 
+MessageTarget MessageTarget::ForTab(int tab_id,
+                                    int frame_id,
+                                    const std::string& document_id) {
+  MessageTarget target(TAB);
+  target.tab_id = tab_id;
+  target.frame_id = frame_id;
+  if (!document_id.empty())
+    target.document_id = document_id;
+  return target;
+}
+
 MessageTarget MessageTarget::ForExtension(const ExtensionId& extension_id) {
   MessageTarget target(EXTENSION);
   target.extension_id = extension_id;
diff --git a/extensions/renderer/message_target.h b/extensions/renderer/message_target.h
index 1cafd6ca..170d511 100644
--- a/extensions/renderer/message_target.h
+++ b/extensions/renderer/message_target.h
@@ -32,6 +32,9 @@
   };
 
   static MessageTarget ForTab(int tab_id, int frame_id);
+  static MessageTarget ForTab(int tab_id,
+                              int frame_id,
+                              const std::string& document_id);
   static MessageTarget ForExtension(const ExtensionId& extension_id);
   static MessageTarget ForNativeApp(const std::string& native_app_name);
 
@@ -47,6 +50,7 @@
   // Only valid for Type::TAB.
   absl::optional<int> tab_id;
   absl::optional<int> frame_id;
+  absl::optional<std::string> document_id;
 
   bool operator==(const MessageTarget& other) const;
 
diff --git a/extensions/renderer/messaging_util.cc b/extensions/renderer/messaging_util.cc
index d85957d..0a5edbe 100644
--- a/extensions/renderer/messaging_util.cc
+++ b/extensions/renderer/messaging_util.cc
@@ -219,6 +219,15 @@
       // backwards compatibility, we do the same here.
       options.frame_id = frame_id < 0 ? -1 : frame_id;
     }
+
+    v8::Local<v8::Value> v8_document_id;
+    success = options_dict.Get("documentId", &v8_document_id);
+    DCHECK(success);
+
+    if (!v8_document_id->IsUndefined()) {
+      DCHECK(v8_document_id->IsString());
+      options.document_id = gin::V8ToString(isolate, v8_document_id);
+    }
   }
 
   // Note: the options object may also include an includeTlsChannelId property.
diff --git a/extensions/renderer/messaging_util.h b/extensions/renderer/messaging_util.h
index 007b3a8..60ef81c 100644
--- a/extensions/renderer/messaging_util.h
+++ b/extensions/renderer/messaging_util.h
@@ -69,6 +69,7 @@
 struct MessageOptions {
   std::string channel_name;
   int frame_id = kNoFrameId;
+  std::string document_id;
 };
 
 // Parses and returns the options parameter for sendMessage or connect.
diff --git a/gpu/ipc/service/gpu_watchdog_thread.cc b/gpu/ipc/service/gpu_watchdog_thread.cc
index 155f7c05..8d701a6 100644
--- a/gpu/ipc/service/gpu_watchdog_thread.cc
+++ b/gpu/ipc/service/gpu_watchdog_thread.cc
@@ -622,8 +622,6 @@
   base::debug::Alias(&less_than_full_thread_time_after_capped_);
 #endif
 
-  GpuWatchdogHistogram(GpuWatchdogThreadEvent::kGpuWatchdogKill);
-
   crash_keys::gpu_watchdog_crashed_in_gpu_init.Set(
       in_gpu_initialization_ ? "1" : "0");
 
@@ -645,12 +643,18 @@
   // Create a crash dump first
   base::debug::DumpWithoutCrashing();
 
+  // A kKill event is triggered and DumpWithoutCrashing() is called in the
+  // watchdog timeout routine OnWatchdogTimeout(). If it turns out
+  // gpu does not hang after the crash dump, another histogram
+  // kNoKillForGpuProgressDuringCrashDumping will be recorded later.
+  GpuWatchdogTimeoutHistogram(GpuWatchdogTimeoutEvent::kKill);
+
   // Final check after the crash dump. If the watched thread makes a progress
   // (disarmed) during generating crash dump, no need to crash the GPU process.
   bool gpu_hang = IsArmed();
   if (gpu_hang) {
-    // Still armed without any progress. GPU possibly hangs.
-    GpuWatchdogTimeoutHistogram(GpuWatchdogTimeoutEvent::kKill);
+    // Still armed without any progress. The GPU process is now killed.
+    GpuWatchdogHistogram(GpuWatchdogThreadEvent::kGpuWatchdogKill);
 #if BUILDFLAG(IS_WIN)
     if (less_than_full_thread_time_after_capped_)
       GpuWatchdogTimeoutHistogram(
diff --git a/gpu/ipc/service/gpu_watchdog_thread_unittest.cc b/gpu/ipc/service/gpu_watchdog_thread_unittest.cc
index 9a1f7b9..fc1e946 100644
--- a/gpu/ipc/service/gpu_watchdog_thread_unittest.cc
+++ b/gpu/ipc/service/gpu_watchdog_thread_unittest.cc
@@ -40,6 +40,13 @@
 [[maybe_unused]] constexpr auto kExtraGPUJobTimeForTestingSlow =
     base::Milliseconds(1000);
 
+// For Fuchsia in which GpuWatchdogTest.GpuInitializationAndRunningTasks test
+// is flaky.
+[[maybe_unused]] constexpr auto kGpuWatchdogTimeoutForTestingSlowest =
+    base::Milliseconds(1000);
+[[maybe_unused]] constexpr auto kExtraGPUJobTimeForTestingSlowest =
+    base::Milliseconds(4000);
+
 // On Windows, the gpu watchdog check if the main thread has used the full
 // thread time. We want to detect the case in which the main thread is swapped
 // out by the OS scheduler. The task on windows is simiulated by reading
@@ -105,12 +112,19 @@
 void GpuWatchdogTest::SetUp() {
   ASSERT_TRUE(base::ThreadTaskRunnerHandle::IsSet());
   ASSERT_TRUE(base::CurrentThread::IsSet());
-  bool use_slow_timeout = false;
+
+  enum TimeOutType {
+    kNormal,
+    kSlow,
+    kSlowest,
+  };
+
+  TimeOutType timeout_type = kNormal;
 
 #if BUILDFLAG(IS_WIN)
   // Win7
   if (base::win::GetVersion() < base::win::Version::WIN10) {
-    use_slow_timeout = true;
+    timeout_type = kSlow;
   }
 
 #elif BUILDFLAG(IS_MAC)
@@ -119,8 +133,10 @@
   int os_version = base::mac::internal::MacOSVersion();
 
   if (os_version <= 1100) {
-    use_slow_timeout = true;
+    // Check MacOS version.
+    timeout_type = kSlow;
   } else {
+    // Check Mac machine model version.
     std::string model_str = base::SysInfo::HardwareModelName();
     size_t found_position = model_str.find("MacBookPro");
     constexpr size_t model_version_pos = 10;
@@ -132,7 +148,7 @@
       int major_model_ver = std::atoi(model_ver_str.c_str());
       // For version < 14,1
       if (major_model_ver < 14) {
-        use_slow_timeout = true;
+        timeout_type = kSlow;
       }
     }
   }
@@ -146,16 +162,19 @@
 
   // For Android version < Android Pie (Version 9)
   if (major_version < 9) {
-    use_slow_timeout = true;
+    timeout_type = kSlow;
   }
 
 #elif BUILDFLAG(IS_FUCHSIA)
-  use_slow_timeout = true;
+  timeout_type = kSlowest;
 #endif
 
-  if (use_slow_timeout) {
+  if (timeout_type == kSlow) {
     timeout_ = kGpuWatchdogTimeoutForTestingSlow;
     extra_gpu_job_time_ = kExtraGPUJobTimeForTestingSlow;
+  } else if (timeout_type == kSlowest) {
+    timeout_ = kGpuWatchdogTimeoutForTestingSlowest;
+    extra_gpu_job_time_ = kExtraGPUJobTimeForTestingSlowest;
   }
 
 #if BUILDFLAG(IS_WIN)
diff --git a/infra/config/generated/builders/ci/android-rust-arm-dbg/properties.json b/infra/config/generated/builders/ci/android-rust-arm-dbg/properties.json
new file mode 100644
index 0000000..1e3e28e
--- /dev/null
+++ b/infra/config/generated/builders/ci/android-rust-arm-dbg/properties.json
@@ -0,0 +1,16 @@
+{
+  "$build/reclient": {
+    "instance": "rbe-chromium-trusted",
+    "jobs": 250,
+    "metrics_project": "chromium-reclient-metrics"
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.rust",
+  "recipe": "chromium"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/linux-code-coverage/properties.json b/infra/config/generated/builders/ci/linux-code-coverage/properties.json
index 573155d..0c3959d 100644
--- a/infra/config/generated/builders/ci/linux-code-coverage/properties.json
+++ b/infra/config/generated/builders/ci/linux-code-coverage/properties.json
@@ -4,6 +4,7 @@
       "overall",
       "unit"
     ],
+    "export_coverage_to_zoss": true,
     "use_clang_coverage": true
   },
   "$build/goma": {
diff --git a/infra/config/generated/builders/ci/linux-rust-x64-dbg/properties.json b/infra/config/generated/builders/ci/linux-rust-x64-dbg/properties.json
new file mode 100644
index 0000000..486923e
--- /dev/null
+++ b/infra/config/generated/builders/ci/linux-rust-x64-dbg/properties.json
@@ -0,0 +1,17 @@
+{
+  "$build/goma": {
+    "enable_ats": true,
+    "rpc_extra_params": "?prod",
+    "server_host": "goma.chromium.org",
+    "use_luci_auth": true
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.rust",
+  "recipe": "chromium"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/android-rust-arm-dbg/properties.json b/infra/config/generated/builders/try/android-rust-arm-dbg/properties.json
new file mode 100644
index 0000000..318a8a3
--- /dev/null
+++ b/infra/config/generated/builders/try/android-rust-arm-dbg/properties.json
@@ -0,0 +1,17 @@
+{
+  "$build/goma": {
+    "enable_ats": true,
+    "rpc_extra_params": "?prod",
+    "server_host": "goma.chromium.org",
+    "use_luci_auth": true
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "tryserver.chromium.rust",
+  "recipe": "chromium_trybot"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/linux-rust-x64-dbg/properties.json b/infra/config/generated/builders/try/linux-rust-x64-dbg/properties.json
new file mode 100644
index 0000000..318a8a3
--- /dev/null
+++ b/infra/config/generated/builders/try/linux-rust-x64-dbg/properties.json
@@ -0,0 +1,17 @@
+{
+  "$build/goma": {
+    "enable_ats": true,
+    "rpc_extra_params": "?prod",
+    "server_host": "goma.chromium.org",
+    "use_luci_auth": true
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "tryserver.chromium.rust",
+  "recipe": "chromium_trybot"
+}
\ No newline at end of file
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index bd17777..d855b59c 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -91,6 +91,17 @@
   Path regular expressions:
   * [`//.+/3pp/.+`](https://cs.chromium.org/search?q=+file:.+/3pp/)
 
+* [android-cronet-arm-dbg](https://ci.chromium.org/p/chromium/builders/try/android-cronet-arm-dbg) ([definition](https://cs.chromium.org/search?q=+file:/try.star$+""android-cronet-arm-dbg"")) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+""android-cronet-arm-dbg""))
+
+  Path regular expressions:
+  * [`//components/cronet/.+`](https://cs.chromium.org/chromium/src/components/cronet/)
+  * [`//components/grpc_support/.+`](https://cs.chromium.org/chromium/src/components/grpc_support/)
+  * [`//build/android/.+`](https://cs.chromium.org/chromium/src/build/android/)
+  * [`//build/config/android/.+`](https://cs.chromium.org/chromium/src/build/config/android/)
+
+  Path exclude regular expressions:
+  * [`//components/cronet/ios/.+`](https://cs.chromium.org/chromium/src/components/cronet/ios/)
+
 * [android-cronet-x86-dbg-10-tests](https://ci.chromium.org/p/chromium/builders/try/android-cronet-x86-dbg-10-tests) ([definition](https://cs.chromium.org/search?q=+file:/try.star$+""android-cronet-x86-dbg-10-tests"")) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+""android-cronet-x86-dbg-10-tests""))
 
   Path regular expressions:
diff --git a/infra/config/generated/cq-usage/full.cfg b/infra/config/generated/cq-usage/full.cfg
index f4a112e..d6f97ee 100644
--- a/infra/config/generated/cq-usage/full.cfg
+++ b/infra/config/generated/cq-usage/full.cfg
@@ -28,6 +28,16 @@
         location_regexp_exclude: ".+/[+]/infra/config/.+"
       }
       builders {
+        name: "chromium/try/android-cronet-arm-dbg"
+        location_regexp: ".+/[+]/components/cronet/.+"
+        location_regexp: ".+/[+]/components/grpc_support/.+"
+        location_regexp: ".+/[+]/build/android/.+"
+        location_regexp: ".+/[+]/build/config/android/.+"
+        location_regexp_exclude: ".+/[+]/docs/.+"
+        location_regexp_exclude: ".+/[+]/infra/config/.+"
+        location_regexp_exclude: ".+/[+]/components/cronet/ios/.+"
+      }
+      builders {
         name: "chromium/try/android-cronet-x86-dbg-10-tests"
         location_regexp: ".+/[+]/components/cronet/.+"
         location_regexp: ".+/[+]/components/grpc_support/.+"
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 6b8414b2..8cbe074 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -327,7 +327,13 @@
       }
       builders {
         name: "chromium/try/android-cronet-arm-dbg"
-        includable_only: true
+        location_regexp: ".+/[+]/components/cronet/.+"
+        location_regexp: ".+/[+]/components/grpc_support/.+"
+        location_regexp: ".+/[+]/build/android/.+"
+        location_regexp: ".+/[+]/build/config/android/.+"
+        location_regexp_exclude: ".+/[+]/docs/.+"
+        location_regexp_exclude: ".+/[+]/infra/config/.+"
+        location_regexp_exclude: ".+/[+]/components/cronet/ios/.+"
       }
       builders {
         name: "chromium/try/android-cronet-arm64-dbg"
@@ -476,6 +482,10 @@
         includable_only: true
       }
       builders {
+        name: "chromium/try/android-rust-arm-dbg"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/android-rust-arm-rel"
         includable_only: true
       }
@@ -1395,6 +1405,10 @@
         includable_only: true
       }
       builders {
+        name: "chromium/try/linux-rust-x64-dbg"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/linux-rust-x64-rel"
         includable_only: true
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 6e2a4aa..c969134 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -26952,6 +26952,84 @@
       }
     }
     builders {
+      name: "android-rust-arm-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:android-rust-arm-dbg"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/android-rust-arm-dbg/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.rust",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "android-rust-arm-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:android-rust-arm-rel"
@@ -35912,6 +35990,84 @@
       }
     }
     builders {
+      name: "linux-rust-x64-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:linux-rust-x64-dbg"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/linux-rust-x64-dbg/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.rust",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "linux-rust-x64-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:linux-rust-x64-rel"
@@ -51825,14 +51981,102 @@
       }
     }
     builders {
-      name: "android-rust-arm-rel"
+      name: "android-rust-arm-dbg"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
+      dimensions: "builder:android-rust-arm-dbg"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
-      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/android-rust-arm-dbg/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "tryserver.chromium.rust",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium_trybot"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
+      name: "android-rust-arm-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:android-rust-arm-rel"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
         cipd_version: "latest"
@@ -68125,12 +68369,11 @@
     builders {
       name: "linux-rust-intree-x64-rel"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
+      dimensions: "builder:linux-rust-intree-x64-rel"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
-      dimensions: "ssd:0"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
         cipd_version: "latest"
@@ -68213,14 +68456,102 @@
       }
     }
     builders {
-      name: "linux-rust-x64-rel"
+      name: "linux-rust-x64-dbg"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
+      dimensions: "builder:linux-rust-x64-dbg"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
+      dimensions: "os:Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
-      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/linux-rust-x64-dbg/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "tryserver.chromium.rust",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium_trybot"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
+      name: "linux-rust-x64-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:linux-rust-x64-rel"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
         cipd_version: "latest"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index c9d1bf67..ac8918a 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -2463,6 +2463,9 @@
     name: "buildbucket/luci.chromium.try/android-binary-size"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android-cronet-arm-dbg"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-cronet-x86-dbg-10-tests"
   }
   builders {
@@ -11226,14 +11229,24 @@
   refs: "regexp:refs/heads/main"
   manifest_name: "REVISION"
   builders {
-    name: "buildbucket/luci.chromium.ci/linux-rust-x64-rel"
-    category: "linux|rel"
-    short_name: "64"
+    name: "buildbucket/luci.chromium.ci/android-rust-arm-dbg"
+    category: "Android"
+    short_name: "dbg"
   }
   builders {
     name: "buildbucket/luci.chromium.ci/android-rust-arm-rel"
-    category: "android|rel"
-    short_name: "32"
+    category: "Android"
+    short_name: "rel"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.ci/linux-rust-x64-dbg"
+    category: "Linux"
+    short_name: "dbg"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.ci/linux-rust-x64-rel"
+    category: "Linux"
+    short_name: "rel"
   }
   header {
     oncalls {
@@ -14653,6 +14666,9 @@
     name: "buildbucket/luci.chromium.try/android-pie-x86-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android-rust-arm-dbg"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-rust-arm-rel"
   }
   builders {
@@ -15208,6 +15224,9 @@
     name: "buildbucket/luci.chromium.try/linux-rust-intree-x64-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux-rust-x64-dbg"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux-rust-x64-rel"
   }
   builders {
@@ -16527,12 +16546,18 @@
   id: "tryserver.chromium.rust"
   name: "tryserver.chromium.rust"
   builders {
+    name: "buildbucket/luci.chromium.try/android-rust-arm-dbg"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-rust-arm-rel"
   }
   builders {
     name: "buildbucket/luci.chromium.try/linux-rust-intree-x64-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux-rust-x64-dbg"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux-rust-x64-rel"
   }
   builder_view_only: true
diff --git a/infra/config/generated/luci/luci-notify.cfg b/infra/config/generated/luci/luci-notify.cfg
index 9891b85..0d47986 100644
--- a/infra/config/generated/luci/luci-notify.cfg
+++ b/infra/config/generated/luci/luci-notify.cfg
@@ -2558,7 +2558,20 @@
   notifications {
     on_change: true
     email {
-      recipients: "chrome-memory-safety+bots@google.com"
+      recipients: "chrome-rust-experiments+bots@google.com"
+    }
+  }
+  builders {
+    bucket: "ci"
+    name: "android-rust-arm-dbg"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  }
+}
+notifiers {
+  notifications {
+    on_change: true
+    email {
+      recipients: "chrome-rust-experiments+bots@google.com"
     }
   }
   builders {
@@ -3441,7 +3454,20 @@
   notifications {
     on_change: true
     email {
-      recipients: "chrome-memory-safety+bots@google.com"
+      recipients: "chrome-rust-experiments+bots@google.com"
+    }
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-rust-x64-dbg"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  }
+}
+notifiers {
+  notifications {
+    on_change: true
+    email {
+      recipients: "chrome-rust-experiments+bots@google.com"
     }
   }
   builders {
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 83fe9b0..9b0cf6007 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -4652,6 +4652,16 @@
   }
 }
 job {
+  id: "android-rust-arm-dbg"
+  realm: "ci"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "android-rust-arm-dbg"
+  }
+}
+job {
   id: "android-rust-arm-rel"
   realm: "ci"
   acl_sets: "ci"
@@ -5929,6 +5939,16 @@
   }
 }
 job {
+  id: "linux-rust-x64-dbg"
+  realm: "ci"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "linux-rust-x64-dbg"
+  }
+}
+job {
   id: "linux-rust-x64-rel"
   realm: "ci"
   acl_sets: "ci"
@@ -7362,6 +7382,7 @@
   triggers: "android-pie-arm64-rel"
   triggers: "android-pie-arm64-wpt-rel-non-cq"
   triggers: "android-pie-x86-rel"
+  triggers: "android-rust-arm-dbg"
   triggers: "android-rust-arm-rel"
   triggers: "android-weblayer-pie-x86-wpt-fyi-rel"
   triggers: "android-weblayer-pie-x86-wpt-smoketest"
@@ -7448,6 +7469,7 @@
   triggers: "linux-lacros-version-skew-fyi"
   triggers: "linux-official"
   triggers: "linux-perfetto-rel"
+  triggers: "linux-rust-x64-dbg"
   triggers: "linux-rust-x64-rel"
   triggers: "linux-swangle-chromium-x64"
   triggers: "linux-swangle-tot-angle-x64"
diff --git a/infra/config/lib/args.star b/infra/config/lib/args.star
index 11fbd40f..2f3bbb42a 100644
--- a/infra/config/lib/args.star
+++ b/infra/config/lib/args.star
@@ -145,6 +145,9 @@
     wrap_in_ignore_default = False
     l = []
     for a in args:
+        should_ignore_default, a = _should_ignore_default(a)
+        if should_ignore_default:
+            wrap_in_ignore_default = True
         if type(a) != type([]):
             a = [a]
         for e in a:
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index 14bc1c8..2465c36b 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -244,7 +244,8 @@
         use_java_coverage,
         use_javascript_coverage,
         coverage_exclude_sources,
-        coverage_test_types):
+        coverage_test_types,
+        export_coverage_to_zoss):
     code_coverage = {}
 
     use_clang_coverage = defaults.get_value(
@@ -276,6 +277,13 @@
     if coverage_test_types:
         code_coverage["coverage_test_types"] = coverage_test_types
 
+    export_coverage_to_zoss = defaults.get_value(
+        "export_coverage_to_zoss",
+        export_coverage_to_zoss,
+    )
+    if export_coverage_to_zoss:
+        code_coverage["export_coverage_to_zoss"] = export_coverage_to_zoss
+
     return code_coverage or None
 
 def _reclient_property(*, instance, service, jobs, rewrapper_env, profiler_service, publish_trace, cache_silo, ensure_verified):
@@ -341,6 +349,7 @@
     use_javascript_coverage = False,
     coverage_exclude_sources = None,
     coverage_test_types = None,
+    export_coverage_to_zoss = False,
     resultdb_bigquery_exports = [],
     resultdb_index_by_timestamp = False,
     reclient_instance = None,
@@ -397,6 +406,7 @@
         use_javascript_coverage = args.DEFAULT,
         coverage_exclude_sources = args.DEFAULT,
         coverage_test_types = args.DEFAULT,
+        export_coverage_to_zoss = args.DEFAULT,
         resultdb_bigquery_exports = args.DEFAULT,
         resultdb_index_by_timestamp = args.DEFAULT,
         reclient_instance = args.DEFAULT,
@@ -543,6 +553,10 @@
         coverage_test_types: a list of string as test types to process data for
             in code_coverage recipe module. Will be copied to
             '$build/code_coverage' property. By default, considered None.
+        export_coverage_to_zoss: a boolean indicating if the raw coverage data
+            be exported zoss(and eventually in code search) in code_coverage
+            recipe module. Will be copied to '$build/code_coverage' property
+            if set. Be default, considered False.
         resultdb_bigquery_exports: a list of resultdb.export_test_results(...)
             specifying parameters for exporting test results to BigQuery. By
             default, do not export.
@@ -699,6 +713,7 @@
         use_javascript_coverage = use_javascript_coverage,
         coverage_exclude_sources = coverage_exclude_sources,
         coverage_test_types = coverage_test_types,
+        export_coverage_to_zoss = export_coverage_to_zoss,
     )
     if code_coverage != None:
         properties["$build/code_coverage"] = code_coverage
diff --git a/infra/config/notifiers.star b/infra/config/notifiers.star
index c34b30d..a602bce 100644
--- a/infra/config/notifiers.star
+++ b/infra/config/notifiers.star
@@ -29,6 +29,14 @@
 )
 
 luci.notifier(
+    name = "chrome-rust-experiments",
+    on_status_change = True,
+    notify_emails = [
+        "chrome-rust-experiments+bots@google.com",
+    ],
+)
+
+luci.notifier(
     name = "chrome-memory-sheriffs",
     on_status_change = True,
     notify_emails = [
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 6e79aee466..177d4e79 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -1394,6 +1394,7 @@
     os = os.LINUX_BIONIC_SWITCH_TO_DEFAULT,
     use_clang_coverage = True,
     coverage_test_types = ["overall", "unit"],
+    export_coverage_to_zoss = True,
     triggered_by = [],
 )
 
diff --git a/infra/config/subprojects/chromium/ci/chromium.rust.star b/infra/config/subprojects/chromium/ci/chromium.rust.star
index 89c651d6..c36cc0a 100644
--- a/infra/config/subprojects/chromium/ci/chromium.rust.star
+++ b/infra/config/subprojects/chromium/ci/chromium.rust.star
@@ -9,45 +9,55 @@
 
 ci.defaults.set(
     builder_group = "chromium.rust",
+    builderless = False,
     cores = 8,
     executable = ci.DEFAULT_EXECUTABLE,
     execution_timeout = ci.DEFAULT_EXECUTION_TIMEOUT,
     goma_backend = goma.backend.RBE_PROD,
     pool = ci.DEFAULT_POOL,
     service_account = ci.DEFAULT_SERVICE_ACCOUNT,
+    os = os.LINUX_BIONIC_SWITCH_TO_DEFAULT,
+    notifies = ["chrome-rust-experiments"],
 )
 
 consoles.console_view(
     name = "chromium.rust",
-    ordering = {
-        None: [
-            "linux",
-            "android",
-        ],
-    },
 )
 
 ci.builder(
-    name = "android-rust-arm-rel",
-    builderless = False,
+    name = "android-rust-arm-dbg",
     console_view_entry = consoles.console_view_entry(
-        category = "android|rel",
-        short_name = "32",
+        category = "Android",
+        short_name = "dbg",
     ),
-    notifies = ["chrome-memory-safety"],
-    os = os.LINUX_BIONIC_SWITCH_TO_DEFAULT,
     goma_backend = None,
     reclient_jobs = rbe_jobs.DEFAULT,
     reclient_instance = rbe_instance.DEFAULT,
 )
 
 ci.builder(
-    name = "linux-rust-x64-rel",
-    builderless = False,
+    name = "android-rust-arm-rel",
     console_view_entry = consoles.console_view_entry(
-        category = "linux|rel",
-        short_name = "64",
+        category = "Android",
+        short_name = "rel",
     ),
-    notifies = ["chrome-memory-safety"],
-    os = os.LINUX_BIONIC_SWITCH_TO_DEFAULT,
+    goma_backend = None,
+    reclient_jobs = rbe_jobs.DEFAULT,
+    reclient_instance = rbe_instance.DEFAULT,
+)
+
+ci.builder(
+    name = "linux-rust-x64-dbg",
+    console_view_entry = consoles.console_view_entry(
+        category = "Linux",
+        short_name = "dbg",
+    ),
+)
+
+ci.builder(
+    name = "linux-rust-x64-rel",
+    console_view_entry = consoles.console_view_entry(
+        category = "Linux",
+        short_name = "rel",
+    ),
 )
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
index 5a83cc8b..829395c 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
@@ -121,6 +121,19 @@
 
 try_.builder(
     name = "android-cronet-arm-dbg",
+    branch_selector = branches.STANDARD_MILESTONE,
+    main_list_view = "try",
+    tryjob = try_.job(
+        location_regexp = [
+            ".+/[+]/components/cronet/.+",
+            ".+/[+]/components/grpc_support/.+",
+            ".+/[+]/build/android/.+",
+            ".+/[+]/build/config/android/.+",
+        ],
+        location_regexp_exclude = [
+            ".+/[+]/components/cronet/ios/.+",
+        ],
+    ),
 )
 
 try_.builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.rust.star b/infra/config/subprojects/chromium/try/tryserver.chromium.rust.star
index 6c1afc0..06159fb 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.rust.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.rust.star
@@ -9,11 +9,12 @@
 
 try_.defaults.set(
     builder_group = "tryserver.chromium.rust",
+    builderless = False,
     cores = 8,
     executable = try_.DEFAULT_EXECUTABLE,
     execution_timeout = try_.DEFAULT_EXECUTION_TIMEOUT,
     goma_backend = goma.backend.RBE_PROD,
-    os = os.LINUX_DEFAULT,
+    os = os.LINUX_BIONIC_SWITCH_TO_DEFAULT,
     pool = try_.DEFAULT_POOL,
     service_account = try_.DEFAULT_SERVICE_ACCOUNT,
 )
@@ -23,13 +24,21 @@
 )
 
 try_.builder(
-    name = "linux-rust-x64-rel",
-)
-
-try_.builder(
-    name = "linux-rust-intree-x64-rel",
+    name = "android-rust-arm-dbg",
 )
 
 try_.builder(
     name = "android-rust-arm-rel",
 )
+
+try_.builder(
+    name = "linux-rust-x64-rel",
+)
+
+try_.builder(
+    name = "linux-rust-x64-dbg",
+)
+
+try_.builder(
+    name = "linux-rust-intree-x64-rel",
+)
diff --git a/ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.mm b/ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.mm
index 2fc260a..4fd944a 100644
--- a/ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.mm
+++ b/ios/chrome/browser/overlays/public/common/infobars/infobar_overlay_request_config.mm
@@ -21,8 +21,7 @@
     bool is_high_priority)
     : infobar_(infobar->GetWeakPtr()),
       infobar_type_(infobar->infobar_type()),
-      has_badge_(BadgeTypeForInfobarType(infobar_type_) !=
-                 BadgeType::kBadgeTypeNone),
+      has_badge_(BadgeTypeForInfobarType(infobar_type_) != kBadgeTypeNone),
       is_high_priority_(is_high_priority),
       overlay_type_(overlay_type) {}
 
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
index 62d585d..483e1c4a 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
@@ -549,6 +549,11 @@
 
 // Tests password generation on manual fallback.
 - (void)testPasswordGenerationOnManualFallback {
+  // Disable the test on iOS 15.3 due to build failure.
+  // TODO(crbug.com/1306530): enable the test with fix.
+  if (@available(iOS 15.3, *)) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 15.3.");
+  }
   [SigninEarlGreyUI signinWithFakeIdentity:[FakeChromeIdentity fakeIdentity1]];
   [ChromeEarlGrey waitForSyncInitialized:YES syncTimeout:10.0];
 
diff --git a/ios/chrome/browser/ui/badges/BUILD.gn b/ios/chrome/browser/ui/badges/BUILD.gn
index 3b66f6a8..44fbc679 100644
--- a/ios/chrome/browser/ui/badges/BUILD.gn
+++ b/ios/chrome/browser/ui/badges/BUILD.gn
@@ -18,8 +18,8 @@
 source_set("util") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "badge_type_util.cc",
     "badge_type_util.h",
+    "badge_type_util.mm",
   ]
   deps = [
     ":public",
@@ -54,7 +54,7 @@
     "resources:wrench_badge",
     "//base",
     "//components/password_manager/core/common",
-    "//ios/chrome/app/strings:ios_strings_grit",
+    "//ios/chrome/app/strings",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/infobars",
@@ -103,6 +103,7 @@
     ":public",
     "//base",
     "//components/password_manager/core/common",
+    "//components/strings:components_strings_grit",
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser/infobars",
     "//ios/chrome/browser/infobars:public",
diff --git a/ios/chrome/browser/ui/badges/OWNERS b/ios/chrome/browser/ui/badges/OWNERS
index 9cf8c07297..8d0b1e93 100644
--- a/ios/chrome/browser/ui/badges/OWNERS
+++ b/ios/chrome/browser/ui/badges/OWNERS
@@ -1,2 +1,3 @@
+ginnyhuang@chromium.org
 thegreenfrog@chromium.org
 sczs@chromium.org
diff --git a/ios/chrome/browser/ui/badges/badge_button_factory.mm b/ios/chrome/browser/ui/badges/badge_button_factory.mm
index 96991fb..b80492f 100644
--- a/ios/chrome/browser/ui/badges/badge_button_factory.mm
+++ b/ios/chrome/browser/ui/badges/badge_button_factory.mm
@@ -27,27 +27,27 @@
 
 - (BadgeButton*)badgeButtonForBadgeType:(BadgeType)badgeType {
   switch (badgeType) {
-    case BadgeType::kBadgeTypePasswordSave:
+    case kBadgeTypePasswordSave:
       return [self passwordsSaveBadgeButton];
-    case BadgeType::kBadgeTypePasswordUpdate:
+    case kBadgeTypePasswordUpdate:
       return [self passwordsUpdateBadgeButton];
-    case BadgeType::kBadgeTypeSaveCard:
+    case kBadgeTypeSaveCard:
       return [self saveCardBadgeButton];
-    case BadgeType::kBadgeTypeTranslate:
+    case kBadgeTypeTranslate:
       return [self translateBadgeButton];
-    case BadgeType::kBadgeTypeIncognito:
+    case kBadgeTypeIncognito:
       return [self incognitoBadgeButton];
-    case BadgeType::kBadgeTypeOverflow:
+    case kBadgeTypeOverflow:
       return [self overflowBadgeButton];
-    case BadgeType::kBadgeTypeSaveAddressProfile:
+    case kBadgeTypeSaveAddressProfile:
       return [self saveAddressProfileBadgeButton];
-    case BadgeType::kBadgeTypeAddToReadingList:
+    case kBadgeTypeAddToReadingList:
       return [self readingListBadgeButton];
-    case BadgeType::kBadgeTypePermissionsCamera:
+    case kBadgeTypePermissionsCamera:
       return [self permissionsCameraBadgeButton];
-    case BadgeType::kBadgeTypePermissionsMicrophone:
+    case kBadgeTypePermissionsMicrophone:
       return [self permissionsMicrophoneBadgeButton];
-    case BadgeType::kBadgeTypeNone:
+    case kBadgeTypeNone:
       NOTREACHED() << "A badge should not have kBadgeTypeNone";
       return nil;
   }
diff --git a/ios/chrome/browser/ui/badges/badge_mediator_unittest.mm b/ios/chrome/browser/ui/badges/badge_mediator_unittest.mm
index 8787560..ec186059 100644
--- a/ios/chrome/browser/ui/badges/badge_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/badges/badge_mediator_unittest.mm
@@ -62,14 +62,14 @@
                 fullScreenBadge:(id<BadgeItem>)fullscreenBadgeItem {
   self.hasFullscreenOffTheRecordBadge =
       fullscreenBadgeItem != nil &&
-      fullscreenBadgeItem.badgeType == BadgeType::kBadgeTypeIncognito;
+      fullscreenBadgeItem.badgeType == kBadgeTypeIncognito;
   self.displayedBadge = displayedBadgeItem;
 }
 - (void)updateDisplayedBadge:(id<BadgeItem>)displayedBadgeItem
              fullScreenBadge:(id<BadgeItem>)fullscreenBadgeItem {
   self.hasFullscreenOffTheRecordBadge =
       fullscreenBadgeItem != nil &&
-      fullscreenBadgeItem.badgeType == BadgeType::kBadgeTypeIncognito;
+      fullscreenBadgeItem.badgeType == kBadgeTypeIncognito;
   self.displayedBadge = displayedBadgeItem;
 }
 - (void)markDisplayedBadgeAsRead:(BOOL)read {
@@ -171,8 +171,7 @@
   InsertActivatedWebState(/*index=*/0);
   AddInfobar(kFirstInfobarType, kFirstInfobarMessageText);
   ASSERT_TRUE(badge_consumer_.displayedBadge);
-  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType,
-            BadgeType::kBadgeTypePasswordSave);
+  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType, kBadgeTypePasswordSave);
 }
 
 // Test that the BadgeMediator handled the removal of the correct badge when two
@@ -182,12 +181,10 @@
   AddInfobar(kFirstInfobarType, kFirstInfobarMessageText);
   InfoBarIOS* second_infobar =
       AddInfobar(kSecondInfobarType, kSecondInfobarMessageText);
-  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType,
-            BadgeType::kBadgeTypeOverflow);
+  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType, kBadgeTypeOverflow);
   RemoveInfobar(second_infobar);
   ASSERT_TRUE(badge_consumer_.displayedBadge);
-  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType,
-            BadgeType::kBadgeTypePasswordSave);
+  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType, kBadgeTypePasswordSave);
 }
 
 TEST_P(BadgeMediatorTest, BadgeMediatorTestMarkAsRead) {
@@ -196,8 +193,7 @@
   // Since there is only one badge, it should be marked as read.
   EXPECT_FALSE(badge_consumer_.hasUnreadBadge);
   AddInfobar(kSecondInfobarType, kSecondInfobarMessageText);
-  ASSERT_EQ(BadgeType::kBadgeTypeOverflow,
-            badge_consumer_.displayedBadge.badgeType);
+  ASSERT_EQ(kBadgeTypeOverflow, badge_consumer_.displayedBadge.badgeType);
   // Second badge should be unread since the overflow badge is being shown as
   // the displayed badge.
   EXPECT_TRUE(badge_consumer_.hasUnreadBadge);
@@ -212,8 +208,7 @@
   InsertActivatedWebState(/*index=*/0);
   AddInfobar(kFirstInfobarType, kFirstInfobarMessageText);
   ASSERT_TRUE(badge_consumer_.displayedBadge);
-  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType,
-            BadgeType::kBadgeTypePasswordSave);
+  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType, kBadgeTypePasswordSave);
   InsertActivatedWebState(/*index=*/1);
   EXPECT_FALSE(badge_consumer_.displayedBadge);
 }
@@ -225,8 +220,7 @@
   InsertActivatedWebState(/*index=*/0);
   AddInfobar(kFirstInfobarType, kFirstInfobarMessageText);
   ASSERT_TRUE(badge_consumer_.displayedBadge);
-  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType,
-            BadgeType::kBadgeTypePasswordSave);
+  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType, kBadgeTypePasswordSave);
   InsertActivatedWebState(/*index=*/1);
   std::unique_ptr<InfoBarIOS> added_infobar = std::make_unique<FakeInfobarIOS>(
       kSecondInfobarType, kSecondInfobarMessageText);
@@ -276,8 +270,7 @@
   badge_mediator_ = [[BadgeMediator alloc] initWithBrowser:browser()];
   badge_mediator_.consumer = badge_consumer_;
   ASSERT_TRUE(badge_consumer_.displayedBadge);
-  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType,
-            BadgeType::kBadgeTypePasswordSave);
+  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType, kBadgeTypePasswordSave);
 }
 
 // Test that the BadgeMediator clears its badges when the last WebState is
@@ -287,8 +280,7 @@
   InsertActivatedWebState(/*index=*/0);
   AddInfobar(kFirstInfobarType, kFirstInfobarMessageText);
   ASSERT_TRUE(badge_consumer_.displayedBadge);
-  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType,
-            BadgeType::kBadgeTypePasswordSave);
+  EXPECT_EQ(badge_consumer_.displayedBadge.badgeType, kBadgeTypePasswordSave);
   web_state_list()->DetachWebStateAt(0);
   InsertActivatedWebState(/*index=*/0);
   ASSERT_FALSE(badge_consumer_.displayedBadge);
diff --git a/ios/chrome/browser/ui/badges/badge_popup_menu_coordinator.mm b/ios/chrome/browser/ui/badges/badge_popup_menu_coordinator.mm
index 6fd8885..7c89e3c 100644
--- a/ios/chrome/browser/ui/badges/badge_popup_menu_coordinator.mm
+++ b/ios/chrome/browser/ui/badges/badge_popup_menu_coordinator.mm
@@ -115,8 +115,9 @@
       break;
     }
     case PopupMenuActionShowSaveAddressProfileOptions: {
-      // TODO(crbug.com/1167062): Record this event.
-
+      UMA_HISTOGRAM_ENUMERATION(
+          kInfobarOverflowMenuTappedHistogram,
+          MobileMessagesInfobarType::AutofillSaveAddressProfile);
       [self addModalRequestForInfobarType:
                 InfobarType::kInfobarTypeSaveAutofillAddressProfile];
       break;
diff --git a/ios/chrome/browser/ui/badges/badge_popup_menu_item.mm b/ios/chrome/browser/ui/badges/badge_popup_menu_item.mm
index 3e3a0cf..12ee552 100644
--- a/ios/chrome/browser/ui/badges/badge_popup_menu_item.mm
+++ b/ios/chrome/browser/ui/badges/badge_popup_menu_item.mm
@@ -8,6 +8,7 @@
 
 #import "base/notreached.h"
 #include "components/password_manager/core/common/password_manager_features.h"
+#include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/list_model/list_model.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
@@ -52,47 +53,48 @@
     self.cellClass = [BadgePopupMenuCell class];
     _badgeType = badgeType;
     switch (badgeType) {
-      case BadgeType::kBadgeTypePasswordSave:
+      case kBadgeTypePasswordSave:
         _actionIdentifier = PopupMenuActionShowSavePasswordOptions;
         _title = l10n_util::GetNSString(
             IDS_IOS_PASSWORD_MANAGER_SAVE_PASSWORD_TITLE);
         break;
-      case BadgeType::kBadgeTypePasswordUpdate:
+      case kBadgeTypePasswordUpdate:
         _actionIdentifier = PopupMenuActionShowUpdatePasswordOptions;
         _title = l10n_util::GetNSString(
             IDS_IOS_PASSWORD_MANAGER_UPDATE_PASSWORD_TITLE);
         break;
-      case BadgeType::kBadgeTypeSaveAddressProfile:
+      case kBadgeTypeSaveAddressProfile:
         _actionIdentifier = PopupMenuActionShowSaveAddressProfileOptions;
-        _title = @"Save Address";
+        _title =
+            l10n_util::GetNSString(IDS_IOS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE);
         break;
-      case BadgeType::kBadgeTypeSaveCard:
+      case kBadgeTypeSaveCard:
         _actionIdentifier = PopupMenuActionShowSaveCardOptions;
         _title = l10n_util::GetNSString(IDS_IOS_AUTOFILL_SAVE_CARD);
         break;
-      case BadgeType::kBadgeTypeTranslate:
+      case kBadgeTypeTranslate:
         _actionIdentifier = PopupMenuActionShowTranslateOptions;
         _title = l10n_util::GetNSString(IDS_IOS_TRANSLATE_INFOBAR_MODAL_TITLE);
         break;
-      case BadgeType::kBadgeTypeAddToReadingList:
+      case kBadgeTypeAddToReadingList:
         _actionIdentifier = PopupMenuActionAddToReadingListOptions;
         _title =
             l10n_util::GetNSString(IDS_IOS_READING_LIST_MESSAGES_MODAL_TITLE);
         break;
-      case BadgeType::kBadgeTypePermissionsCamera:
+      case kBadgeTypePermissionsCamera:
         // Falls through.
-      case BadgeType::kBadgeTypePermissionsMicrophone:
+      case kBadgeTypePermissionsMicrophone:
         _actionIdentifier = PopupMenuActionShowPermissionsOptions;
         _title = l10n_util::GetNSString(
             IDS_IOS_PERMISSIONS_INFOBAR_OVERFLOW_POPUP_TITLE);
         break;
-      case BadgeType::kBadgeTypeIncognito:
+      case kBadgeTypeIncognito:
         NOTREACHED() << "A BadgePopupMenuItem should not be an Incognito badge";
         break;
-      case BadgeType::kBadgeTypeOverflow:
+      case kBadgeTypeOverflow:
         NOTREACHED() << "A BadgePopupMenuItem should not be an overflow badge";
         break;
-      case BadgeType::kBadgeTypeNone:
+      case kBadgeTypeNone:
         NOTREACHED() << "A badge should not have kBadgeTypeNone";
         break;
     }
diff --git a/ios/chrome/browser/ui/badges/badge_static_item.mm b/ios/chrome/browser/ui/badges/badge_static_item.mm
index c44ef4a..53beb16 100644
--- a/ios/chrome/browser/ui/badges/badge_static_item.mm
+++ b/ios/chrome/browser/ui/badges/badge_static_item.mm
@@ -31,7 +31,7 @@
     _badgeType = badgeType;
     _tappable = NO;
     _badgeState = BadgeStateNone;
-    _fullScreen = badgeType == BadgeType::kBadgeTypeIncognito;
+    _fullScreen = badgeType == kBadgeTypeIncognito;
   }
   return self;
 }
diff --git a/ios/chrome/browser/ui/badges/badge_type.h b/ios/chrome/browser/ui/badges/badge_type.h
index 29f0b67..d435842 100644
--- a/ios/chrome/browser/ui/badges/badge_type.h
+++ b/ios/chrome/browser/ui/badges/badge_type.h
@@ -5,8 +5,10 @@
 #ifndef IOS_CHROME_BROWSER_UI_BADGES_BADGE_TYPE_H_
 #define IOS_CHROME_BROWSER_UI_BADGES_BADGE_TYPE_H_
 
+#import <Foundation/Foundation.h>
+
 // Badge types.
-enum class BadgeType {
+typedef NS_ENUM(NSUInteger, BadgeType) {
   // Badge type for no badge. This is to allow other features to distinguish
   // when a badge is necessary or not. Setting a BadgeModel type to
   // kBadgeTypeNone might result in a crash.
diff --git a/ios/chrome/browser/ui/badges/badge_type_util.cc b/ios/chrome/browser/ui/badges/badge_type_util.mm
similarity index 66%
rename from ios/chrome/browser/ui/badges/badge_type_util.cc
rename to ios/chrome/browser/ui/badges/badge_type_util.mm
index 24388c3..219c973 100644
--- a/ios/chrome/browser/ui/badges/badge_type_util.cc
+++ b/ios/chrome/browser/ui/badges/badge_type_util.mm
@@ -5,49 +5,53 @@
 #include "ios/chrome/browser/ui/badges/badge_type_util.h"
 
 #include <ostream>
-
 #include "base/notreached.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 BadgeType BadgeTypeForInfobarType(InfobarType infobar_type) {
   switch (infobar_type) {
     case InfobarType::kInfobarTypePasswordSave:
-      return BadgeType::kBadgeTypePasswordSave;
+      return kBadgeTypePasswordSave;
     case InfobarType::kInfobarTypePasswordUpdate:
-      return BadgeType::kBadgeTypePasswordUpdate;
+      return kBadgeTypePasswordUpdate;
     case InfobarType::kInfobarTypeSaveAutofillAddressProfile:
-      return BadgeType::kBadgeTypeSaveAddressProfile;
+      return kBadgeTypeSaveAddressProfile;
     case InfobarType::kInfobarTypeSaveCard:
-      return BadgeType::kBadgeTypeSaveCard;
+      return kBadgeTypeSaveCard;
     case InfobarType::kInfobarTypeTranslate:
-      return BadgeType::kBadgeTypeTranslate;
+      return kBadgeTypeTranslate;
     case InfobarType::kInfobarTypeAddToReadingList:
-      return BadgeType::kBadgeTypeAddToReadingList;
+      return kBadgeTypeAddToReadingList;
     case InfobarType::kInfobarTypePermissions:
       // Default value; actual value would depend on the value of
       // GetStatesForAllPermissions() of the currently active WebState, and be
       // overridden when used.
-      return BadgeType::kBadgeTypePermissionsCamera;
+      return kBadgeTypePermissionsCamera;
     default:
-      return BadgeType::kBadgeTypeNone;
+      return kBadgeTypeNone;
   }
 }
 
 InfobarType InfobarTypeForBadgeType(BadgeType badge_type) {
   switch (badge_type) {
-    case BadgeType::kBadgeTypePasswordSave:
+    case kBadgeTypePasswordSave:
       return InfobarType::kInfobarTypePasswordSave;
-    case BadgeType::kBadgeTypePasswordUpdate:
+    case kBadgeTypePasswordUpdate:
       return InfobarType::kInfobarTypePasswordUpdate;
-    case BadgeType::kBadgeTypeSaveAddressProfile:
+    case kBadgeTypeSaveAddressProfile:
       return InfobarType::kInfobarTypeSaveAutofillAddressProfile;
-    case BadgeType::kBadgeTypeSaveCard:
+    case kBadgeTypeSaveCard:
       return InfobarType::kInfobarTypeSaveCard;
-    case BadgeType::kBadgeTypeTranslate:
+    case kBadgeTypeTranslate:
       return InfobarType::kInfobarTypeTranslate;
-    case BadgeType::kBadgeTypeAddToReadingList:
+    case kBadgeTypeAddToReadingList:
       return InfobarType::kInfobarTypeAddToReadingList;
-    case BadgeType::kBadgeTypePermissionsCamera:
-    case BadgeType::kBadgeTypePermissionsMicrophone:
+    case kBadgeTypePermissionsCamera:
+      // Falls through.
+    case kBadgeTypePermissionsMicrophone:
       return InfobarType::kInfobarTypePermissions;
     default:
       NOTREACHED() << "Unsupported badge type.";
diff --git a/ios/chrome/browser/ui/badges/badges_histograms.h b/ios/chrome/browser/ui/badges/badges_histograms.h
index 279806d..7bdb3c6 100644
--- a/ios/chrome/browser/ui/badges/badges_histograms.h
+++ b/ios/chrome/browser/ui/badges/badges_histograms.h
@@ -10,6 +10,8 @@
 
 // Values for the Mobile.Messages.OverflowRow.Tapped histogram. Entries should
 // not be renumbered and numeric values should never be reused.
+// Please also update MobileMessagesInfobarType enum in
+// tools/metrics/histograms/enums.xml
 enum class MobileMessagesInfobarType {
   Confirm = 0,
   SavePassword = 1,
@@ -17,8 +19,9 @@
   SaveCard = 3,
   Translate = 4,
   Permissions = 5,
+  AutofillSaveAddressProfile = 6,
   // Highest enumerator. Recommended by Histogram metrics best practices.
-  kMaxValue = Permissions,
+  kMaxValue = AutofillSaveAddressProfile,
 };
 
 #endif  // IOS_CHROME_BROWSER_UI_BADGES_BADGES_HISTOGRAMS_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index 909fa07..6caa27ad 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -564,6 +564,11 @@
 
 // Tests that tapping the fake omnibox moves the collection.
 - (void)testTapFakeOmniboxScroll {
+  // Disable the test on iOS 15.3 due to build failure.
+  // TODO(crbug.com/1306515): enable the test with fix.
+  if (@available(iOS 15.3, *)) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 15.3.");
+  }
   // Get the collection and its layout.
   UICollectionView* collectionView = [NewTabPageAppInterface collectionView];
 
@@ -598,6 +603,11 @@
 // Tests that tapping the fake omnibox then unfocusing it moves the collection
 // back to where it was.
 - (void)testTapFakeOmniboxScrollScrolled {
+  // Disable the test on iOS 15.3 due to build failure.
+  // TODO(crbug.com/1306515): enable the test with fix.
+  if (@available(iOS 15.3, *)) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 15.3.");
+  }
   // Get the collection and its layout.
   UICollectionView* collectionView = [NewTabPageAppInterface collectionView];
 
diff --git a/ios/chrome/browser/ui/popup_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/BUILD.gn
index 916ea98..9a92a6a4 100644
--- a/ios/chrome/browser/ui/popup_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/BUILD.gn
@@ -104,6 +104,7 @@
     "//ios/chrome/common/ui/colors",
     "//ios/components/webui:url_constants",
     "//ios/public/provider/chrome/browser",
+    "//ios/public/provider/chrome/browser/follow",
     "//ios/public/provider/chrome/browser/text_zoom:text_zoom_api",
     "//ios/public/provider/chrome/browser/user_feedback",
     "//ios/web",
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
index d842543c..f389ac4d 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
@@ -42,6 +42,8 @@
 #import "ios/chrome/browser/web/web_navigation_browser_agent.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
+#import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#import "ios/public/provider/chrome/browser/follow/follow_provider.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -287,12 +289,17 @@
           overlayPresenter;
       self.overflowMenuMediator.browserPolicyConnector =
           GetApplicationContext()->GetBrowserPolicyConnector();
-      self.overflowMenuMediator.followActionState =
-          IsWebChannelsEnabled()
-              ? GetFollowActionState(
-                    self.browser->GetWebStateList()->GetActiveWebState(),
-                    self.browser->GetBrowserState())
-              : FollowActionStateHidden;
+
+      if (IsWebChannelsEnabled()) {
+        self.overflowMenuMediator.followActionState = GetFollowActionState(
+            self.browser->GetWebStateList()->GetActiveWebState(),
+            self.browser->GetBrowserState());
+        ios::GetChromeBrowserProvider()
+            .GetFollowProvider()
+            ->SetFollowEventDelegate(self.browser);
+      } else {
+        self.overflowMenuMediator.followActionState = FollowActionStateHidden;
+      }
 
       self.contentBlockerMediator.consumer = self.overflowMenuMediator;
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
index e2cc26b..9235bcdc 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
@@ -240,7 +240,8 @@
       @selector(testOpenTabsHeaderVisibleInSearchModeWhenSearchBarIsNotEmpty),
       @selector(testSuggestedActionsVisibleInSearchModeWhenSearchBarIsNotEmpty),
       @selector(testSearchSuggestedActionsDisplaysCorrectHistoryMatchesCount),
-      @selector(testSearchSuggestedActionsSectionContentInRegularGrid)};
+      @selector(testSearchSuggestedActionsSectionContentInRegularGrid),
+      @selector(testSuggestedActionsNotAvailableInIncognitoPageSearchMode)};
   for (SEL test : searchTests) {
     if ([self isRunningTest:test]) {
       config.features_enabled.push_back(kTabsSearch);
@@ -1598,6 +1599,33 @@
       assertWithMatcher:grey_nil()];
 }
 
+// Tests that suggested actions section does not appear in search mode for
+// incognito page.
+- (void)testSuggestedActionsNotAvailableInIncognitoPageSearchMode {
+  [self loadTestURLsInNewIncognitoTabs];
+  [ChromeEarlGrey showTabSwitcher];
+
+  // Enter search mode.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
+      performAction:grey_tap()];
+
+  // Upon entry, the search bar is empty. Verify that the suggested actions
+  // section doesn't exist.
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_nil()];
+
+  // Searching with a query should not show suggested actions section.
+  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
+      performAction:grey_typeText(@"page 2\n")];
+  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
+      assertWithMatcher:grey_nil()];
+  [[self scrollDownViewMatcher:chrome_test_util::IncognitoTabGrid()
+               toSelectMatcher:SearchSuggestedActionsSection()]
+      assertWithMatcher:grey_nil()];
+}
+
 // Tests that the search suggested actions section has the right rows in the
 // regular grid.
 - (void)testSearchSuggestedActionsSectionContentInRegularGrid {
@@ -1697,7 +1725,6 @@
 // Tests that selecting an open tab search result in incognito mode will
 // correctly open the expected tab.
 - (void)testSearchIncognitoOpenTabsSelectResult {
-  [ChromeEarlGrey openNewIncognitoTab];
   [self loadTestURLsInNewIncognitoTabs];
   [ChromeEarlGrey showTabSwitcher];
 
@@ -1840,6 +1867,7 @@
 }
 
 - (void)loadTestURLsInNewIncognitoTabs {
+  [ChromeEarlGrey openNewIncognitoTab];
   [ChromeEarlGrey loadURL:_URL1];
   [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
 
diff --git a/ios/public/provider/chrome/browser/follow/follow_provider.h b/ios/public/provider/chrome/browser/follow/follow_provider.h
index 37cfbd45..7cb0658 100644
--- a/ios/public/provider/chrome/browser/follow/follow_provider.h
+++ b/ios/public/provider/chrome/browser/follow/follow_provider.h
@@ -9,6 +9,7 @@
 
 @class FollowSiteInfo;
 @class FollowedWebChannel;
+class Browser;
 
 // FollowProvider provides and updates the following status of websites and
 // provides information related to these.
@@ -32,6 +33,10 @@
 
   // Updates the following status of |site| to |state|.
   virtual void UpdateFollowStatus(FollowSiteInfo* site, bool state);
+
+  // Sets the follow event delegate to discover feed with |browser|.
+  // This method must be called before any follow action needs to be handled.
+  virtual void SetFollowEventDelegate(Browser* browser);
 };
 
 #endif  // IOS_PUBLIC_PROVIDER_CHROME_BROWSER_FOLLOW_FOLLOW_PROVIDER_H_
diff --git a/ios/public/provider/chrome/browser/follow/follow_provider.mm b/ios/public/provider/chrome/browser/follow/follow_provider.mm
index f7a81ac9..dd042a1ad 100644
--- a/ios/public/provider/chrome/browser/follow/follow_provider.mm
+++ b/ios/public/provider/chrome/browser/follow/follow_provider.mm
@@ -21,3 +21,5 @@
 }
 
 void FollowProvider::UpdateFollowStatus(FollowSiteInfo* site, bool state) {}
+
+void FollowProvider::SetFollowEventDelegate(Browser* browser) {}
diff --git a/ios/showcase/badges/OWNERS b/ios/showcase/badges/OWNERS
new file mode 100644
index 0000000..e4cb6a98
--- /dev/null
+++ b/ios/showcase/badges/OWNERS
@@ -0,0 +1 @@
+ginnyhuang@chromium.org
diff --git a/ios/showcase/badges/sc_badge_coordinator.mm b/ios/showcase/badges/sc_badge_coordinator.mm
index 5ce9369..ffd7a22 100644
--- a/ios/showcase/badges/sc_badge_coordinator.mm
+++ b/ios/showcase/badges/sc_badge_coordinator.mm
@@ -79,30 +79,30 @@
 
   self.title = @"Badges";
   AddNamedGuidesToView(@[ kBadgeOverflowMenuGuide ], self.view);
-  BadgeStaticItem* incognitoItem = [[BadgeStaticItem alloc]
-      initWithBadgeType:BadgeType::kBadgeTypeIncognito];
-  BadgeTappableItem* passwordBadgeItem = [[BadgeTappableItem alloc]
-      initWithBadgeType:BadgeType::kBadgeTypePasswordSave];
+  BadgeStaticItem* incognitoItem =
+      [[BadgeStaticItem alloc] initWithBadgeType:kBadgeTypeIncognito];
+  BadgeTappableItem* passwordBadgeItem =
+      [[BadgeTappableItem alloc] initWithBadgeType:kBadgeTypePasswordSave];
   passwordBadgeItem.badgeState = BadgeStateRead;
   [self.consumer setupWithDisplayedBadge:passwordBadgeItem
                          fullScreenBadge:incognitoItem];
 }
 
 - (void)showAcceptedDisplayedBadge {
-  BadgeStaticItem* incognitoItem = [[BadgeStaticItem alloc]
-      initWithBadgeType:BadgeType::kBadgeTypeIncognito];
-  BadgeTappableItem* passwordBadgeItem = [[BadgeTappableItem alloc]
-      initWithBadgeType:BadgeType::kBadgeTypePasswordSave];
+  BadgeStaticItem* incognitoItem =
+      [[BadgeStaticItem alloc] initWithBadgeType:kBadgeTypeIncognito];
+  BadgeTappableItem* passwordBadgeItem =
+      [[BadgeTappableItem alloc] initWithBadgeType:kBadgeTypePasswordSave];
   passwordBadgeItem.badgeState = BadgeStateRead | BadgeStateAccepted;
   [self.consumer setupWithDisplayedBadge:passwordBadgeItem
                          fullScreenBadge:incognitoItem];
 }
 
 - (void)addSecondBadge:(id)sender {
-  BadgeStaticItem* incognitoItem = [[BadgeStaticItem alloc]
-      initWithBadgeType:BadgeType::kBadgeTypeIncognito];
-  BadgeTappableItem* displayedBadge = [[BadgeTappableItem alloc]
-      initWithBadgeType:BadgeType::kBadgeTypeOverflow];
+  BadgeStaticItem* incognitoItem =
+      [[BadgeStaticItem alloc] initWithBadgeType:kBadgeTypeIncognito];
+  BadgeTappableItem* displayedBadge =
+      [[BadgeTappableItem alloc] initWithBadgeType:kBadgeTypeOverflow];
   [self.consumer setupWithDisplayedBadge:displayedBadge
                          fullScreenBadge:incognitoItem];
   [self.consumer markDisplayedBadgeAsRead:NO];
@@ -158,8 +158,8 @@
   self.badgePopupMenuCoordinator = [[BadgePopupMenuCoordinator alloc]
       initWithBaseViewController:self.containerViewController
                          browser:nil];
-  NSArray* badgeItems = @[ [[BadgeTappableItem alloc]
-      initWithBadgeType:BadgeType::kBadgeTypePasswordSave] ];
+  NSArray* badgeItems =
+      @[ [[BadgeTappableItem alloc] initWithBadgeType:kBadgeTypePasswordSave] ];
   [self.badgePopupMenuCoordinator setBadgeItemsToShow:badgeItems];
   [self.badgePopupMenuCoordinator start];
   [self.consumer markDisplayedBadgeAsRead:YES];
diff --git a/media/gpu/mac/vt_video_decode_accelerator_mac.cc b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
index 13a393d..eba1691 100644
--- a/media/gpu/mac/vt_video_decode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
@@ -1345,6 +1345,11 @@
           return;
         }
         seen_pps_[pps_id].assign(nalu.data, nalu.data + nalu.size);
+        // Pass PPS as data to the platform decoder, it helps in cases
+        // when there are more than one PPS, Video Toolbox is smart enough
+        // to find and recognize them there.
+        nalus.push_back(nalu);
+        data_size += kNALUHeaderLength + nalu.size;
         break;
       }
 
diff --git a/media/video/gpu_memory_buffer_video_frame_pool.cc b/media/video/gpu_memory_buffer_video_frame_pool.cc
index 9b28dd3..564c753 100644
--- a/media/video/gpu_memory_buffer_video_frame_pool.cc
+++ b/media/video/gpu_memory_buffer_video_frame_pool.cc
@@ -508,25 +508,25 @@
 }
 
 void CopyRowsToNV12Buffer(int first_row,
-                          int y_rows,
-                          int uv_rows,
-                          int y_bytes_per_row,
-                          int uv_bytes_per_row,
+                          int rows_y,
+                          int rows_uv,
+                          int bytes_per_row_y,
+                          int bytes_per_row_uv,
                           const VideoFrame* source_frame,
                           uint8_t* dest_y,
                           int dest_stride_y,
                           uint8_t* dest_uv,
                           int dest_stride_uv) {
   TRACE_EVENT2("media", "CopyRowsToNV12Buffer", "bytes_per_row",
-               y_bytes_per_row, "rows", y_rows);
+               bytes_per_row_y, "rows", rows_y);
 
   if (!dest_y || !dest_uv)
     return;
 
   DCHECK_NE(dest_stride_y, 0);
   DCHECK_NE(dest_stride_uv, 0);
-  DCHECK_LE(y_bytes_per_row, std::abs(dest_stride_y));
-  DCHECK_LE(uv_bytes_per_row, std::abs(dest_stride_uv));
+  DCHECK_LE(bytes_per_row_y, std::abs(dest_stride_y));
+  DCHECK_LE(bytes_per_row_uv, std::abs(dest_stride_uv));
   DCHECK_EQ(0, first_row % 2);
   DCHECK(source_frame->format() == PIXEL_FORMAT_I420 ||
          source_frame->format() == PIXEL_FORMAT_YV12 ||
@@ -536,13 +536,13 @@
                           first_row * source_frame->stride(VideoFrame::kYPlane),
                       source_frame->stride(VideoFrame::kYPlane),
                       dest_y + first_row * dest_stride_y, dest_stride_y,
-                      y_bytes_per_row, y_rows);
+                      bytes_per_row_y, rows_y);
     libyuv::CopyPlane(
         source_frame->visible_data(VideoFrame::kUVPlane) +
             first_row / 2 * source_frame->stride(VideoFrame::kUVPlane),
         source_frame->stride(VideoFrame::kUVPlane),
         dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv,
-        uv_bytes_per_row, uv_rows);
+        bytes_per_row_uv, rows_uv);
 
     return;
   }
@@ -558,8 +558,8 @@
           first_row / 2 * source_frame->stride(VideoFrame::kVPlane),
       source_frame->stride(VideoFrame::kVPlane),
       dest_y + first_row * dest_stride_y, dest_stride_y,
-      dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv, y_bytes_per_row,
-      y_rows);
+      dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv, bytes_per_row_y,
+      rows_y);
 }
 
 void CopyRowsToRGB10Buffer(bool is_argb,
@@ -1015,36 +1015,36 @@
       break;
 
     case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB: {
-      const size_t y_rows_to_copy = VideoFrame::Rows(
+      const size_t rows_to_copy_y = VideoFrame::Rows(
           VideoFrame::kYPlane, VideoFormat(output_format), rows_to_copy);
-      const size_t uv_rows_to_copy = VideoFrame::Rows(
+      const size_t rows_to_copy_uv = VideoFrame::Rows(
           VideoFrame::kUVPlane, VideoFormat(output_format), rows_to_copy);
-      const size_t y_bytes_per_row = VideoFrame::RowBytes(
+      const size_t bytes_per_row_y = VideoFrame::RowBytes(
           VideoFrame::kYPlane, VideoFormat(output_format), coded_size.width());
-      const size_t uv_bytes_per_row = VideoFrame::RowBytes(
+      const size_t bytes_per_row_uv = VideoFrame::RowBytes(
           VideoFrame::kUVPlane, VideoFormat(output_format), coded_size.width());
       CopyRowsToNV12Buffer(
-          row, y_rows_to_copy, uv_rows_to_copy, y_bytes_per_row,
-          uv_bytes_per_row, video_frame,
+          row, rows_to_copy_y, rows_to_copy_uv, bytes_per_row_y,
+          bytes_per_row_uv, video_frame,
           static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0),
           static_cast<uint8_t*>(buffer->memory(1)), buffer->stride(1));
       break;
     }
 
     case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB: {
-      const size_t y_rows_to_copy = VideoFrame::Rows(
+      const size_t rows_to_copy_y = VideoFrame::Rows(
           VideoFrame::kYPlane, VideoFormat(output_format), rows_to_copy);
-      const size_t uv_rows_to_copy = VideoFrame::Rows(
+      const size_t rows_to_copy_uv = VideoFrame::Rows(
           VideoFrame::kUVPlane, VideoFormat(output_format), rows_to_copy);
-      const size_t y_bytes_per_row = VideoFrame::RowBytes(
+      const size_t bytes_per_row_y = VideoFrame::RowBytes(
           VideoFrame::kYPlane, VideoFormat(output_format), coded_size.width());
-      const size_t uv_bytes_per_row = VideoFrame::RowBytes(
+      const size_t bytes_per_row_uv = VideoFrame::RowBytes(
           VideoFrame::kUVPlane, VideoFormat(output_format), coded_size.width());
       gfx::GpuMemoryBuffer* buffer2 =
           frame_resources->plane_resources[1].gpu_memory_buffer.get();
       CopyRowsToNV12Buffer(
-          row, y_rows_to_copy, uv_rows_to_copy, y_bytes_per_row,
-          uv_bytes_per_row, video_frame,
+          row, rows_to_copy_y, rows_to_copy_uv, bytes_per_row_y,
+          bytes_per_row_uv, video_frame,
           static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0),
           static_cast<uint8_t*>(buffer2->memory(0)), buffer2->stride(0));
       break;
diff --git a/net/BUILD.gn b/net/BUILD.gn
index b329352d..5b4ce09 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -672,6 +672,7 @@
     "log/net_log_capture_mode.h",
     "log/net_log_entry.cc",
     "log/net_log_entry.h",
+    "log/net_log_event_type.cc",
     "log/net_log_event_type.h",
     "log/net_log_event_type_list.h",
     "log/net_log_source.cc",
diff --git a/net/log/net_log.cc b/net/log/net_log.cc
index f81d83d..e053527 100644
--- a/net/log/net_log.cc
+++ b/net/log/net_log.cc
@@ -181,24 +181,10 @@
 }
 
 // static
-const char* NetLog::EventTypeToString(NetLogEventType event) {
-  switch (event) {
-#define EVENT_TYPE(label)      \
-  case NetLogEventType::label: \
-    return #label;
-#include "net/log/net_log_event_type_list.h"
-#undef EVENT_TYPE
-    default:
-      NOTREACHED();
-      return nullptr;
-  }
-}
-
-// static
 base::Value NetLog::GetEventTypesAsValue() {
   base::Value dict(base::Value::Type::DICTIONARY);
   for (int i = 0; i < static_cast<int>(NetLogEventType::COUNT); ++i) {
-    dict.SetIntKey(EventTypeToString(static_cast<NetLogEventType>(i)), i);
+    dict.SetIntKey(NetLogEventTypeToString(static_cast<NetLogEventType>(i)), i);
   }
   return dict;
 }
diff --git a/net/log/net_log.h b/net/log/net_log.h
index 427bf17..9a6fc19 100644
--- a/net/log/net_log.h
+++ b/net/log/net_log.h
@@ -334,9 +334,6 @@
   // timestamps are desired, but is suitable for e.g. expiration times.
   static std::string TimeToString(const base::Time& time);
 
-  // Returns a C-String symbolic name for |event_type|.
-  static const char* EventTypeToString(NetLogEventType event_type);
-
   // Returns a dictionary that maps event type symbolic names to their enum
   // values.
   static base::Value GetEventTypesAsValue();
diff --git a/net/log/net_log_event_type.cc b/net/log/net_log_event_type.cc
new file mode 100644
index 0000000..069b3dca
--- /dev/null
+++ b/net/log/net_log_event_type.cc
@@ -0,0 +1,28 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/log/net_log_event_type.h"
+
+#include "base/notreached.h"
+
+namespace net {
+
+const char* NetLogEventTypeToString(NetLogEventType type) {
+  switch (type) {
+#define EVENT_TYPE(label)      \
+  case NetLogEventType::label: \
+    return #label;
+#include "net/log/net_log_event_type_list.h"
+#undef EVENT_TYPE
+    default:
+      NOTREACHED();
+      return nullptr;
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, NetLogEventType type) {
+  return os << NetLogEventTypeToString(type);
+}
+
+}  // namespace net
diff --git a/net/log/net_log_event_type.h b/net/log/net_log_event_type.h
index d9d4bf6..1a58d67 100644
--- a/net/log/net_log_event_type.h
+++ b/net/log/net_log_event_type.h
@@ -5,6 +5,10 @@
 #ifndef NET_LOG_NET_LOG_EVENT_TYPE_H_
 #define NET_LOG_NET_LOG_EVENT_TYPE_H_
 
+#include <ostream>
+
+#include "net/base/net_export.h"
+
 namespace net {
 
 enum class NetLogEventType {
@@ -14,6 +18,12 @@
   COUNT
 };
 
+// Returns a C-String symbolic name for |type|.
+NET_EXPORT const char* NetLogEventTypeToString(NetLogEventType type);
+
+// For convenience in tests.
+NET_EXPORT std::ostream& operator<<(std::ostream& os, NetLogEventType type);
+
 // The 'phase' of an event trace (whether it marks the beginning or end
 // of an event.).
 enum class NetLogEventPhase {
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index 0e73ffd86..9d76112 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -4025,6 +4025,22 @@
 //  }
 EVENT_TYPE(CORS_PREFLIGHT_RESULT)
 
+// This event is logged when PreflightController detects a failed CORS
+// preflight.
+//
+// It contains the following parameters:
+//  {
+//    "error: <A string representing the network error for the failure.
+//            "ERR_FAILED" for CORS errors.>,
+//    "cors-error": <Optional. An integer representing the more granular reason
+//                  for the CORS error, if any. Values map to
+//                  `network::mojom::CorsError`.>,
+//    "failed-parameter": <Optional, absent if `cors-error` is absent. A string
+//                        representing the parameter that failed validation,
+//                        e.g. a forbidden header.>,
+//  }
+EVENT_TYPE(CORS_PREFLIGHT_ERROR)
+
 // This event identifies the NetLogSource() for a URLRequest of the preflight
 // request.
 EVENT_TYPE(CORS_PREFLIGHT_URL_REQUEST)
diff --git a/net/log/test_net_log_util.cc b/net/log/test_net_log_util.cc
index a165437..8559c92 100644
--- a/net/log/test_net_log_util.cc
+++ b/net/log/test_net_log_util.cc
@@ -6,7 +6,7 @@
 
 #include <cstddef>
 
-#include "net/log/net_log.h"
+#include "net/log/net_log_entry.h"
 
 namespace net {
 
@@ -40,8 +40,8 @@
   const NetLogEntry& entry = entries[index];
   if (expected_event != entry.type) {
     return ::testing::AssertionFailure()
-           << "Actual event: " << NetLog::EventTypeToString(entry.type)
-           << ". Expected event: " << NetLog::EventTypeToString(expected_event)
+           << "Actual event: " << NetLogEventTypeToString(entry.type)
+           << ". Expected event: " << NetLogEventTypeToString(expected_event)
            << ".";
   }
   if (expected_phase != entry.phase) {
diff --git a/net/log/test_net_log_util.h b/net/log/test_net_log_util.h
index 60d0055b..9caef59f 100644
--- a/net/log/test_net_log_util.h
+++ b/net/log/test_net_log_util.h
@@ -6,6 +6,8 @@
 #define NET_LOG_TEST_NET_LOG_UTIL_H_
 
 #include <stddef.h>
+#include <string>
+#include <vector>
 
 #include "base/strings/string_piece.h"
 #include "net/log/net_log_event_type.h"
diff --git a/net/log/trace_net_log_observer.cc b/net/log/trace_net_log_observer.cc
index 543d861..b2998b25 100644
--- a/net/log/trace_net_log_observer.cc
+++ b/net/log/trace_net_log_observer.cc
@@ -59,7 +59,7 @@
   switch (entry.phase) {
     case NetLogEventPhase::BEGIN:
       TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
-          kNetLogTracingCategory, NetLog::EventTypeToString(entry.type),
+          kNetLogTracingCategory, NetLogEventTypeToString(entry.type),
           entry.source.id, "source_type",
           NetLog::SourceTypeToString(entry.source.type), "params",
           std::unique_ptr<base::trace_event::ConvertableToTraceFormat>(
@@ -67,7 +67,7 @@
       break;
     case NetLogEventPhase::END:
       TRACE_EVENT_NESTABLE_ASYNC_END2(
-          kNetLogTracingCategory, NetLog::EventTypeToString(entry.type),
+          kNetLogTracingCategory, NetLogEventTypeToString(entry.type),
           entry.source.id, "source_type",
           NetLog::SourceTypeToString(entry.source.type), "params",
           std::unique_ptr<base::trace_event::ConvertableToTraceFormat>(
@@ -75,7 +75,7 @@
       break;
     case NetLogEventPhase::NONE:
       TRACE_EVENT_NESTABLE_ASYNC_INSTANT2(
-          kNetLogTracingCategory, NetLog::EventTypeToString(entry.type),
+          kNetLogTracingCategory, NetLogEventTypeToString(entry.type),
           entry.source.id, "source_type",
           NetLog::SourceTypeToString(entry.source.type), "params",
           std::unique_ptr<base::trace_event::ConvertableToTraceFormat>(
diff --git a/net/log/trace_net_log_observer_unittest.cc b/net/log/trace_net_log_observer_unittest.cc
index 15ecb779..2083e27 100644
--- a/net/log/trace_net_log_observer_unittest.cc
+++ b/net/log/trace_net_log_observer_unittest.cc
@@ -261,7 +261,7 @@
   EXPECT_EQ(base::StringPrintf("0x%x", entries[0].source.id), actual_item1.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item1.phase);
-  EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::CANCELLED),
+  EXPECT_EQ(NetLogEventTypeToString(NetLogEventType::CANCELLED),
             actual_item1.name);
   EXPECT_EQ(NetLog::SourceTypeToString(entries[0].source.type),
             actual_item1.source_type);
@@ -270,7 +270,7 @@
   EXPECT_EQ(base::StringPrintf("0x%x", entries[1].source.id), actual_item2.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN),
             actual_item2.phase);
-  EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::URL_REQUEST_START_JOB),
+  EXPECT_EQ(NetLogEventTypeToString(NetLogEventType::URL_REQUEST_START_JOB),
             actual_item2.name);
   EXPECT_EQ(NetLog::SourceTypeToString(entries[1].source.type),
             actual_item2.source_type);
@@ -279,7 +279,7 @@
   EXPECT_EQ(base::StringPrintf("0x%x", entries[2].source.id), actual_item3.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_END),
             actual_item3.phase);
-  EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::REQUEST_ALIVE),
+  EXPECT_EQ(NetLogEventTypeToString(NetLogEventType::REQUEST_ALIVE),
             actual_item3.name);
   EXPECT_EQ(NetLog::SourceTypeToString(entries[2].source.type),
             actual_item3.source_type);
@@ -311,7 +311,7 @@
   EXPECT_EQ(base::StringPrintf("0x%x", entries[0].source.id), actual_item1.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item1.phase);
-  EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::CANCELLED),
+  EXPECT_EQ(NetLogEventTypeToString(NetLogEventType::CANCELLED),
             actual_item1.name);
   EXPECT_EQ(NetLog::SourceTypeToString(entries[0].source.type),
             actual_item1.source_type);
@@ -320,7 +320,7 @@
   EXPECT_EQ(base::StringPrintf("0x%x", entries[2].source.id), actual_item2.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item2.phase);
-  EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::URL_REQUEST_START_JOB),
+  EXPECT_EQ(NetLogEventTypeToString(NetLogEventType::URL_REQUEST_START_JOB),
             actual_item2.name);
   EXPECT_EQ(NetLog::SourceTypeToString(entries[2].source.type),
             actual_item2.source_type);
@@ -348,7 +348,7 @@
   EXPECT_EQ(base::StringPrintf("0x%x", entries[0].source.id), actual_item1.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item1.phase);
-  EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::CANCELLED),
+  EXPECT_EQ(NetLogEventTypeToString(NetLogEventType::CANCELLED),
             actual_item1.name);
   EXPECT_EQ(NetLog::SourceTypeToString(entries[0].source.type),
             actual_item1.source_type);
@@ -432,7 +432,7 @@
   EXPECT_EQ(base::StringPrintf("0x%x", entries[0].source.id), actual_item1.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item1.phase);
-  EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::CANCELLED),
+  EXPECT_EQ(NetLogEventTypeToString(NetLogEventType::CANCELLED),
             actual_item1.name);
   EXPECT_EQ(NetLog::SourceTypeToString(entries[0].source.type),
             actual_item1.source_type);
@@ -441,7 +441,7 @@
   EXPECT_EQ(base::StringPrintf("0x%x", entries[1].source.id), actual_item2.id);
   EXPECT_EQ(std::string(1, TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT),
             actual_item2.phase);
-  EXPECT_EQ(NetLog::EventTypeToString(NetLogEventType::REQUEST_ALIVE),
+  EXPECT_EQ(NetLogEventTypeToString(NetLogEventType::REQUEST_ALIVE),
             actual_item2.name);
   EXPECT_EQ(NetLog::SourceTypeToString(entries[1].source.type),
             actual_item2.source_type);
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 12e7e517..0b38995 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -5377,7 +5377,7 @@
       NetLogEventType::NETWORK_DELEGATE_HEADERS_RECEIVED,
   };
   for (NetLogEventType event : kExpectedEvents) {
-    SCOPED_TRACE(NetLog::EventTypeToString(event));
+    SCOPED_TRACE(NetLogEventTypeToString(event));
     log_position = ExpectLogContainsSomewhereAfter(
         entries, log_position + 1, event, NetLogEventPhase::BEGIN);
 
@@ -5430,7 +5430,7 @@
       NetLogEventType::NETWORK_DELEGATE_HEADERS_RECEIVED,
   };
   for (NetLogEventType event : kExpectedEvents) {
-    SCOPED_TRACE(NetLog::EventTypeToString(event));
+    SCOPED_TRACE(NetLogEventTypeToString(event));
     log_position = ExpectLogContainsSomewhereAfter(
         entries, log_position + 1, event, NetLogEventPhase::BEGIN);
 
@@ -5450,7 +5450,7 @@
 
   // The NetworkDelegate logged information in the same three events as before.
   for (NetLogEventType event : kExpectedEvents) {
-    SCOPED_TRACE(NetLog::EventTypeToString(event));
+    SCOPED_TRACE(NetLogEventTypeToString(event));
     log_position = ExpectLogContainsSomewhereAfter(
         entries, log_position + 1, event, NetLogEventPhase::BEGIN);
 
@@ -5552,7 +5552,7 @@
       NetLogEventType::URL_REQUEST_DELEGATE_RESPONSE_STARTED,
   };
   for (NetLogEventType event : kExpectedEvents) {
-    SCOPED_TRACE(NetLog::EventTypeToString(event));
+    SCOPED_TRACE(NetLogEventTypeToString(event));
     log_position = ExpectLogContainsSomewhereAfter(entries, log_position, event,
                                                    NetLogEventPhase::BEGIN);
 
@@ -5612,7 +5612,7 @@
         NetLogEventType::URL_REQUEST_DELEGATE_RESPONSE_STARTED,
     };
     for (NetLogEventType event : kExpectedEvents) {
-      SCOPED_TRACE(NetLog::EventTypeToString(event));
+      SCOPED_TRACE(NetLogEventTypeToString(event));
       log_position = ExpectLogContainsSomewhereAfter(
           entries, log_position, event, NetLogEventPhase::BEGIN);
 
diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc
index 2e8b7e0..9c04f5e 100644
--- a/services/network/cors/cors_url_loader.cc
+++ b/services/network/cors/cors_url_loader.cc
@@ -4,6 +4,9 @@
 
 #include "services/network/cors/cors_url_loader.h"
 
+#include <sstream>
+#include <utility>
+
 #include "base/bind.h"
 #include "base/containers/contains.h"
 #include "base/containers/flat_set.h"
@@ -123,6 +126,23 @@
   return dict;
 }
 
+// Returns net log params for the `CORS_PREFLIGHT_ERROR` event type.
+base::Value::Dict NetLogPreflightErrorParams(
+    int net_error,
+    const absl::optional<CorsErrorStatus>& status) {
+  base::Value::Dict dict;
+
+  dict.Set("error", net::ErrorToShortString(net_error));
+  if (status) {
+    dict.Set("cors-error", static_cast<int>(status->cors_error));
+    if (!status->failed_parameter.empty()) {
+      dict.Set("failed-parameter", status->failed_parameter);
+    }
+  }
+
+  return dict;
+}
+
 // Returns the response tainting value
 // (https://fetch.spec.whatwg.org/#concept-request-response-tainting) for a
 // request and the CORS flag, as specified in
@@ -744,6 +764,10 @@
     return absl::nullopt;
   }
 
+  net_log_.AddEvent(net::NetLogEventType::CORS_PREFLIGHT_ERROR, [&] {
+    return base::Value(NetLogPreflightErrorParams(net_error, status));
+  });
+
   // `kInvalidResponse` is never returned by the preflight controller, so we use
   // it to record the case where there was a net error and no CORS error.
   auto histogram_error = mojom::CorsError::kInvalidResponse;
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index d9b797b6..03266ff4f 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -60,11 +60,15 @@
 
 namespace {
 
+using ::testing::Contains;
 using ::testing::ElementsAre;
+using ::testing::Eq;
 using ::testing::IsEmpty;
+using ::testing::IsNull;
 using ::testing::IsSupersetOf;
 using ::testing::Optional;
 using ::testing::Pair;
+using ::testing::Pointee;
 
 const uint32_t kRendererProcessId = 573;
 
@@ -79,6 +83,29 @@
   return base::Bucket(static_cast<base::HistogramBase::Sample>(error), count);
 }
 
+// Returns a view of the types of the given entries, in the exact same order.
+std::vector<net::NetLogEventType> GetTypesOfNetLogEntries(
+    const std::vector<net::NetLogEntry>& entries) {
+  std::vector<net::NetLogEventType> types;
+  for (const auto& entry : entries) {
+    types.push_back(entry.type);
+  }
+  return types;
+}
+
+// Returns a pointer to the first entry in `entries` with the given `type`.
+// Returns nullptr if none can be found.
+const net::NetLogEntry* FindEntryByType(
+    const std::vector<net::NetLogEntry>& entries,
+    net::NetLogEventType type) {
+  for (const auto& entry : entries) {
+    if (entry.type == type) {
+      return &entry;
+    }
+  }
+  return nullptr;
+}
+
 class TestURLLoaderFactory : public mojom::URLLoaderFactory {
  public:
   TestURLLoaderFactory() {}
@@ -476,13 +503,14 @@
   std::vector<net::NetLogEntry> GetEntries() const {
     std::vector<net::NetLogEntry> entries, filtered;
     entries = net_log_observer_.GetEntries();
-    for (const auto& entry : entries) {
+    for (auto& entry : entries) {
       if (entry.type == net::NetLogEventType::CORS_REQUEST ||
           entry.type == net::NetLogEventType::CHECK_CORS_PREFLIGHT_REQUIRED ||
           entry.type == net::NetLogEventType::CHECK_CORS_PREFLIGHT_CACHE ||
           entry.type == net::NetLogEventType::CORS_PREFLIGHT_RESULT ||
-          entry.type == net::NetLogEventType::CORS_PREFLIGHT_CACHED_RESULT) {
-        filtered.push_back(entry.Clone());
+          entry.type == net::NetLogEventType::CORS_PREFLIGHT_CACHED_RESULT ||
+          entry.type == net::NetLogEventType::CORS_PREFLIGHT_ERROR) {
+        filtered.push_back(std::move(entry));
       }
     }
     return filtered;
@@ -2906,6 +2934,110 @@
   ADD_FAILURE() << "Log entry not found.";
 }
 
+TEST_F(CorsURLLoaderTest, NetLogPreflightMissingAllowOrigin) {
+  auto initiator = url::Origin::Create(GURL("https://foo.example"));
+  ResetFactory(initiator, mojom::kBrowserProcessId);
+
+  ResourceRequest request;
+  request.method = "PUT";
+  request.mode = mojom::RequestMode::kCors;
+  request.url = GURL("https://example.com/");
+  request.request_initiator = initiator;
+
+  CreateLoaderAndStart(request);
+  RunUntilCreateLoaderAndStartCalled();
+  NotifyLoaderClientOnReceiveResponse();
+  NotifyLoaderClientOnComplete(net::OK);
+  RunUntilComplete();
+
+  std::vector<net::NetLogEntry> entries = GetEntries();
+  std::vector<net::NetLogEventType> types = GetTypesOfNetLogEntries(entries);
+  EXPECT_THAT(types,
+              Contains(net::NetLogEventType::CORS_PREFLIGHT_RESULT).Times(0));
+  ASSERT_THAT(types,
+              Contains(net::NetLogEventType::CORS_PREFLIGHT_ERROR).Times(1));
+
+  const net::NetLogEntry* entry =
+      FindEntryByType(entries, net::NetLogEventType::CORS_PREFLIGHT_ERROR);
+  const base::Value::Dict* params = entry->params.GetIfDict();
+  ASSERT_TRUE(params);
+  EXPECT_THAT(params->FindString("error"), Pointee(Eq("ERR_FAILED")));
+  EXPECT_THAT(params->FindInt("cors-error"),
+              Optional(Eq(static_cast<int>(
+                  mojom::CorsError::kPreflightMissingAllowOriginHeader))));
+  EXPECT_THAT(params->FindString("failed-parameter"), IsNull());
+}
+
+TEST_F(CorsURLLoaderTest, NetLogPreflightMethodDisallowed) {
+  auto initiator = url::Origin::Create(GURL("https://foo.example"));
+  ResetFactory(initiator, mojom::kBrowserProcessId);
+
+  ResourceRequest request;
+  request.method = "PUT";
+  request.mode = mojom::RequestMode::kCors;
+  request.url = GURL("https://example.com/");
+  request.request_initiator = initiator;
+
+  CreateLoaderAndStart(request);
+  RunUntilCreateLoaderAndStartCalled();
+  NotifyLoaderClientOnReceiveResponse({
+      {"Access-Control-Allow-Origin", "https://foo.example"},
+      {"Access-Control-Allow-Methods", "GET"},
+      {"Access-Control-Allow-Credentials", "true"},
+  });
+  NotifyLoaderClientOnComplete(net::OK);
+  RunUntilComplete();
+
+  std::vector<net::NetLogEntry> entries = GetEntries();
+  std::vector<net::NetLogEventType> types = GetTypesOfNetLogEntries(entries);
+  ASSERT_THAT(types,
+              Contains(net::NetLogEventType::CORS_PREFLIGHT_RESULT).Times(1));
+  ASSERT_THAT(types,
+              Contains(net::NetLogEventType::CORS_PREFLIGHT_ERROR).Times(1));
+
+  const net::NetLogEntry* entry =
+      FindEntryByType(entries, net::NetLogEventType::CORS_PREFLIGHT_RESULT);
+  ASSERT_EQ(entry->params.type(), base::Value::Type::DICT);
+  EXPECT_THAT(
+      entry->params.GetDict().FindString("access-control-allow-methods"),
+      Pointee(Eq("GET")));
+
+  entry = FindEntryByType(entries, net::NetLogEventType::CORS_PREFLIGHT_ERROR);
+  const base::Value::Dict* params = entry->params.GetIfDict();
+  ASSERT_TRUE(params);
+  EXPECT_THAT(params->FindString("error"), Pointee(Eq("ERR_FAILED")));
+  EXPECT_THAT(params->FindInt("cors-error"),
+              Optional(Eq(static_cast<int>(
+                  mojom::CorsError::kMethodDisallowedByPreflightResponse))));
+  EXPECT_THAT(params->FindString("failed-parameter"), Pointee(Eq("PUT")));
+}
+
+TEST_F(CorsURLLoaderTest, NetLogPreflightNetError) {
+  auto initiator = url::Origin::Create(GURL("https://foo.example"));
+  ResetFactory(initiator, mojom::kBrowserProcessId);
+
+  ResourceRequest request;
+  request.method = "PUT";
+  request.mode = mojom::RequestMode::kCors;
+  request.url = GURL("https://example.com/");
+  request.request_initiator = initiator;
+
+  CreateLoaderAndStart(request);
+  RunUntilCreateLoaderAndStartCalled();
+  NotifyLoaderClientOnComplete(net::ERR_INVALID_ARGUMENT);
+  RunUntilComplete();
+
+  std::vector<net::NetLogEntry> entries = GetEntries();
+  const auto type = net::NetLogEventType::CORS_PREFLIGHT_ERROR;
+  ASSERT_THAT(GetTypesOfNetLogEntries(entries), Contains(type).Times(1));
+
+  const net::NetLogEntry* entry = FindEntryByType(entries, type);
+  const base::Value::Dict* params = entry->params.GetIfDict();
+  EXPECT_THAT(params->FindString("error"), Pointee(Eq("ERR_INVALID_ARGUMENT")));
+  EXPECT_THAT(params->FindInt("cors-error"), Eq(absl::nullopt));
+  EXPECT_THAT(params->FindString("failed-parameter"), IsNull());
+}
+
 TEST_F(CorsURLLoaderTest, PreflightMissingAllowOrigin) {
   auto initiator = url::Origin::Create(GURL("https://foo.example"));
   ResetFactory(initiator, mojom::kBrowserProcessId);
diff --git a/services/network/cors/preflight_controller.cc b/services/network/cors/preflight_controller.cc
index e6073a60..cfe2046 100644
--- a/services/network/cors/preflight_controller.cc
+++ b/services/network/cors/preflight_controller.cc
@@ -338,15 +338,15 @@
 }
 
 absl::optional<CorsErrorStatus> CheckPreflightResult(
-    PreflightResult* result,
+    const PreflightResult& result,
     const ResourceRequest& original_request,
     NonWildcardRequestHeadersSupport non_wildcard_request_headers_support) {
   absl::optional<CorsErrorStatus> status =
-      result->EnsureAllowedCrossOriginMethod(original_request.method);
+      result.EnsureAllowedCrossOriginMethod(original_request.method);
   if (status)
     return status;
 
-  return result->EnsureAllowedCrossOriginHeaders(
+  return result.EnsureAllowedCrossOriginHeaders(
       original_request.headers, original_request.is_revalidating,
       non_wildcard_request_headers_support);
 }
@@ -471,9 +471,8 @@
 
       // Preflight succeeded. Check `original_request_` with `result`.
       DCHECK(!detected_error_status);
-      detected_error_status =
-          CheckPreflightResult(result.get(), original_request_,
-                               non_wildcard_request_headers_support_);
+      detected_error_status = CheckPreflightResult(
+          *result, original_request_, non_wildcard_request_headers_support_);
       has_authorization_covered_by_wildcard =
           result->HasAuthorizationCoveredByWildcard(original_request_.headers);
     }
diff --git a/services/network/cors/preflight_result.cc b/services/network/cors/preflight_result.cc
index 2d6386b..f78671da 100644
--- a/services/network/cors/preflight_result.cc
+++ b/services/network/cors/preflight_result.cc
@@ -102,6 +102,12 @@
   return true;
 }
 
+// Joins the strings in the given `set ` with commas.
+std::string JoinSet(const base::flat_set<std::string>& set) {
+  std::vector<base::StringPiece> values(set.begin(), set.end());
+  return base::JoinString(values, ",");
+}
+
 }  // namespace
 
 // static
@@ -270,14 +276,10 @@
          !headers_.contains(kAuthorization);
 }
 
-base::Value PreflightResult::NetLogParams() {
+base::Value PreflightResult::NetLogParams() const {
   base::Value dict(base::Value::Type::DICTIONARY);
-  std::vector<std::string> methods(methods_.begin(), methods_.end());
-  std::vector<std::string> headers(headers_.begin(), headers_.end());
-  dict.SetStringKey("access-control-allow-methods",
-                    base::JoinString(methods, ","));
-  dict.SetStringKey("access-control-allow-headers",
-                    base::JoinString(headers, ","));
+  dict.SetStringKey("access-control-allow-methods", JoinSet(methods_));
+  dict.SetStringKey("access-control-allow-headers", JoinSet(headers_));
   return dict;
 }
 
diff --git a/services/network/cors/preflight_result.h b/services/network/cors/preflight_result.h
index 5e08b5a8..fedb8bf 100644
--- a/services/network/cors/preflight_result.h
+++ b/services/network/cors/preflight_result.h
@@ -95,11 +95,12 @@
   bool HasAuthorizationCoveredByWildcard(
       const net::HttpRequestHeaders& headers) const;
 
-  // Refers the cache expiry time.
+  // Returns the cache expiry time.
   base::TimeTicks absolute_expiry_time() const { return absolute_expiry_time_; }
 
-  // Create a param for NetLog.
-  base::Value NetLogParams();
+  // Returns params for the `CORS_PREFLIGHT_RESULT` and
+  // `CORS_PREFLIGHT_CACHED_RESULT` net log events.
+  base::Value NetLogParams() const;
 
  protected:
   explicit PreflightResult(const mojom::CredentialsMode credentials_mode);
diff --git a/sql/database.h b/sql/database.h
index 2bc45a94..7482260 100644
--- a/sql/database.h
+++ b/sql/database.h
@@ -232,7 +232,8 @@
   // The callback will never be called after the Database instance is destroyed.
   using ErrorCallback = base::RepeatingCallback<void(int, Statement*)>;
   void set_error_callback(ErrorCallback callback) {
-    DCHECK(error_callback_.is_null() || callback.is_null())
+    DCHECK(!callback.is_null()) << "Use reset_error_callback() explicitly";
+    DCHECK(error_callback_.is_null())
         << "Overwriting previously set error callback";
     error_callback_ = std::move(callback);
   }
diff --git a/sql/error_metrics.cc b/sql/error_metrics.cc
index 1724582..aadc386c 100644
--- a/sql/error_metrics.cc
+++ b/sql/error_metrics.cc
@@ -5,12 +5,14 @@
 #include "sql/error_metrics.h"
 
 #include <ostream>  // Needed to compile NOTREACHED() with operator <<.
+#include <set>
 #include <utility>
 
 #include "base/check_op.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/ranges/algorithm.h"
+#include "base/strings/string_piece.h"
 #include "third_party/sqlite/sqlite3.h"
 
 namespace sql {
@@ -26,6 +28,7 @@
     {SQLITE_INTERNAL, SqliteLoggedResultCode::kUnusedSqlite},
     {SQLITE_PERM, SqliteLoggedResultCode::kPermission},
     {SQLITE_ABORT, SqliteLoggedResultCode::kAbort},
+    {SQLITE_BUSY, SqliteLoggedResultCode::kBusy},
 
     // Chrome features shouldn't execute conflicting statements concurrently.
     {SQLITE_LOCKED, SqliteLoggedResultCode::kUnusedChrome},
@@ -122,7 +125,7 @@
 #endif
 
     // Chrome does not use blocking Posix advisory file lock requests.
-    {SQLITE_ERROR_SNAPSHOT, SqliteLoggedResultCode::kUnusedChrome},
+    {SQLITE_BUSY_TIMEOUT, SqliteLoggedResultCode::kUnusedChrome},
 #ifdef SQLITE_ENABLE_SETLK_TIMEOUT
 #error "This code assumes that Chrome does not use
 #endif
@@ -256,4 +259,55 @@
   base::UmaHistogramEnumeration(histogram_name, logged_code);
 }
 
+void CheckSqliteLoggedResultCodeForTesting() {
+  // Ensure that error codes are alphabetical.
+  const auto* unordered_it = base::ranges::adjacent_find(
+      kResultCodeMapping,
+      [](const std::pair<int, SqliteLoggedResultCode>& lhs,
+         const std::pair<int, SqliteLoggedResultCode>& rhs) {
+        return lhs >= rhs;
+      });
+  DCHECK_EQ(unordered_it, base::ranges::end(kResultCodeMapping))
+      << "Mapping ordering broken at {" << unordered_it->first << ", "
+      << static_cast<int>(unordered_it->second) << "}";
+
+  std::set<int> sqlite_result_codes;
+  for (auto& mapping_entry : kResultCodeMapping)
+    sqlite_result_codes.insert(mapping_entry.first);
+
+  // SQLite doesn't have special messages for extended errors.
+  // At the time of this writing, sqlite3_errstr() has a string table for
+  // primary result codes, and uses it for extended error codes as well.
+  //
+  // So, we can only use sqlite3_errstr() to check for holes in the primary
+  // message table.
+  for (int result_code = 0; result_code <= 256; ++result_code) {
+    if (sqlite_result_codes.count(result_code) != 0)
+      continue;
+
+    const char* error_message = sqlite3_errstr(result_code);
+
+    static constexpr base::StringPiece kUnknownErrorMessage("unknown error");
+    DCHECK_EQ(kUnknownErrorMessage.compare(error_message), 0)
+        << "Unmapped SQLite result code: " << result_code
+        << " SQLite message: " << error_message;
+  }
+
+  // Number of #defines in https://www.sqlite.org/c3ref/c_abort.html
+  //
+  // This number is also stated at
+  // https://www.sqlite.org/rescode.html#primary_result_code_list
+  static constexpr int kPrimaryResultCodes = 31;
+
+  // Number of #defines in https://www.sqlite.org/c3ref/c_abort_rollback.html
+  //
+  // This number is also stated at
+  // https://www.sqlite.org/rescode.html#extended_result_code_list
+  static constexpr int kExtendedResultCodes = 74;
+
+  DCHECK_EQ(std::size(kResultCodeMapping),
+            size_t{kPrimaryResultCodes + kExtendedResultCodes})
+      << "Mapping table has incorrect number of entries";
+}
+
 }  // namespace sql
diff --git a/sql/error_metrics.h b/sql/error_metrics.h
index 300c262e..980439a 100644
--- a/sql/error_metrics.h
+++ b/sql/error_metrics.h
@@ -218,6 +218,12 @@
 COMPONENT_EXPORT(SQL)
 SqliteLoggedResultCode CreateSqliteLoggedResultCode(int sqlite_result_code);
 
+// Called by unit tests.
+//
+// DCHECKs the representation invariants of the mapping table used to convert
+// SQLite result codes to logging-friendly values.
+COMPONENT_EXPORT(SQL) void CheckSqliteLoggedResultCodeForTesting();
+
 }  // namespace sql
 
 #endif  // SQL_ERROR_METRICS_H_
diff --git a/sql/error_metrics_unittest.cc b/sql/error_metrics_unittest.cc
index 75f92bb..63350bc 100644
--- a/sql/error_metrics_unittest.cc
+++ b/sql/error_metrics_unittest.cc
@@ -62,21 +62,13 @@
 #endif
 }
 
-// TODO(crbug.com/1306382): Fails when dcheck_always_on = false.
-#if !DCHECK_IS_ON()
-#define MAYBE_CreateSqliteLoggedResultCode_ChromeBugError \
-  DISABLED_CreateSqliteLoggedResultCode_ChromeBugError
-#else
-#define MAYBE_CreateSqliteLoggedResultCode_ChromeBugError \
-  CreateSqliteLoggedResultCode_ChromeBugError
-#endif  // DCHECK_IS_ON()
-TEST(ErrorMetricsTest, MAYBE_CreateSqliteLoggedResultCode_ChromeBugError) {
+TEST(ErrorMetricsTest, CreateSqliteLoggedResultCode_ChromeBugError) {
 #if DCHECK_IS_ON()
   EXPECT_DCHECK_DEATH_WITH(
       CreateSqliteLoggedResultCode(SQLITE_NOTFOUND),
       "SQLite reported code that should never show up in Chrome: 12");
 #else
-  EXPECT_EQ(SqliteLoggedResultCode::kUnusedSqlite,
+  EXPECT_EQ(SqliteLoggedResultCode::kUnusedChrome,
             CreateSqliteLoggedResultCode(SQLITE_NOTFOUND));
 #endif
 }
@@ -105,6 +97,10 @@
                                      SqliteLoggedResultCode::kCorruptIndex, 1);
 }
 
+TEST(ErrorMetricsTest, CheckMapping) {
+  CheckSqliteLoggedResultCodeForTesting();
+}
+
 }  // namespace
 
 }  // namespace sql
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 64bade6..9ff8668 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5801,7 +5801,7 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "isolate_profile_data": true,
@@ -5809,14 +5809,14 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "dimension_sets": [
@@ -5943,7 +5943,7 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "isolate_profile_data": true,
@@ -5951,14 +5951,14 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 7a6489d..1460351 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -85096,7 +85096,7 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "isolate_profile_data": true,
@@ -85104,14 +85104,14 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -85213,7 +85213,7 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "isolate_profile_data": true,
@@ -85221,14 +85221,14 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -86595,21 +86595,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "dimension_sets": [
@@ -86737,21 +86737,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "dimension_sets": [
@@ -88292,21 +88292,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "dimension_sets": [
@@ -88434,21 +88434,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "dimension_sets": [
@@ -89185,21 +89185,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -89281,21 +89281,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4944.0",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 101.0.4946.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v101.0.4944.0",
-              "revision": "version:101.0.4944.0"
+              "location": "lacros_version_skew_tests_v101.0.4946.0",
+              "revision": "version:101.0.4946.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/testing/buildbot/chromium.rust.json b/testing/buildbot/chromium.rust.json
index a3d05ad..261cb22 100644
--- a/testing/buildbot/chromium.rust.json
+++ b/testing/buildbot/chromium.rust.json
@@ -1,6 +1,84 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
   "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "android-rust-arm-dbg": {
+    "additional_compile_targets": [
+      "mojo_rust",
+      "rust_build_tests"
+    ],
+    "gtest_tests": [
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "rust_gtest_interop_unittests",
+        "test_id_prefix": "ninja://testing/rust_gtest_interop:rust_gtest_interop_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "test_cpp_including_rust_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "test_cpp_including_rust_unittests",
+        "test_id_prefix": "ninja://build/rust/tests/test_cpp_including_rust:test_cpp_including_rust_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "test_serde_jsonrc",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "test_serde_jsonrc",
+        "test_id_prefix": "ninja://build/rust/tests/test_serde_jsonrc:test_serde_jsonrc/"
+      }
+    ]
+  },
   "android-rust-arm-rel": {
     "additional_compile_targets": [
       "mojo_rust",
@@ -168,6 +246,95 @@
       }
     ]
   },
+  "linux-rust-x64-dbg": {
+    "additional_compile_targets": [
+      "mojo_rust",
+      "mojo_rust_tests",
+      "rust_build_tests"
+    ],
+    "gtest_tests": [
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "rust_gtest_interop_unittests",
+        "test_id_prefix": "ninja://testing/rust_gtest_interop:rust_gtest_interop_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "test_cpp_including_rust_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "test_cpp_including_rust_unittests",
+        "test_id_prefix": "ninja://build/rust/tests/test_cpp_including_rust:test_cpp_including_rust_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "test_serde_jsonrc",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "test_serde_jsonrc",
+        "test_id_prefix": "ninja://build/rust/tests/test_serde_jsonrc:test_serde_jsonrc/"
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "isolate_name": "build_rust_tests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "build_rust_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "build_rust_tests",
+        "test_id_prefix": "ninja://build/rust/tests:build_rust_tests/"
+      },
+      {
+        "isolate_name": "mojo_rust_tests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "mojo_rust_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_rust_tests",
+        "test_id_prefix": "ninja://mojo/public/rust:mojo_rust_tests/"
+      }
+    ]
+  },
   "linux-rust-x64-rel": {
     "additional_compile_targets": [
       "mojo_rust",
diff --git a/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_negative.filter b/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_negative.filter
index 5632132..b3a7453 100644
--- a/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_negative.filter
+++ b/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_negative.filter
@@ -88,9 +88,9 @@
 -InitialEnrollmentTest.*
 -InterruptedAutoStartEnrollmentTest.*
 -InvalidPendingScreenTest.*
+-KioskConsumerTest.*
 -KioskEnrollmentTest.*
 -KioskEnterpriseTest.*
--KioskHiddenWebUITest.*
 -KioskTest.*
 -KioskUpdateTest.*
 -KioskVirtualKeyboardTest.*
diff --git a/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter b/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter
index ebdff59e..14ede5a 100644
--- a/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter
+++ b/testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter
@@ -88,9 +88,9 @@
 InitialEnrollmentTest.*
 InterruptedAutoStartEnrollmentTest.*
 InvalidPendingScreenTest.*
+KioskConsumerTest.*
 KioskEnrollmentTest.*
 KioskEnterpriseTest.*
-KioskHiddenWebUITest.*
 KioskTest.*
 KioskUpdateTest.*
 KioskVirtualKeyboardTest.*
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 0383ff0..8779701 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -28,16 +28,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4944.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v101.0.4946.0/test_ash_chrome',
       '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter',
     ],
-    'identifier': 'Lacros version skew testing ash 101.0.4944.0',
+    'identifier': 'Lacros version skew testing ash 101.0.4946.0',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v101.0.4944.0',
-          'revision': 'version:101.0.4944.0',
+          'location': 'lacros_version_skew_tests_v101.0.4946.0',
+          'revision': 'version:101.0.4946.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index c6ac3ca..8d8ce75c 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -5526,6 +5526,19 @@
     'name': 'chromium.rust',
     'mixins': ['chromium-tester-service-account'],
     'machines': {
+      'android-rust-arm-dbg': {
+        'additional_compile_targets': [
+          'mojo_rust',
+          'rust_build_tests',
+        ],
+        'mixins': [ 'marshmallow' ],
+        'test_suites': {
+          'gtest_tests': 'rust_gtests',
+          # Currently `can_build_rust_unit_tests` is false on Android (because
+          # `rustc_can_link` is false).
+          # TODO(https://crbug.com/1260120): Cover `rust_native_tests` here.
+        },
+      },
       'android-rust-arm-rel': {
         'additional_compile_targets': [
           'mojo_rust',
@@ -5550,6 +5563,17 @@
           'isolated_scripts': 'rust_native_tests',
         },
       },
+      'linux-rust-x64-dbg' : {
+        'additional_compile_targets': [
+          'mojo_rust',
+          'mojo_rust_tests',
+          'rust_build_tests',
+        ],
+        'test_suites': {
+          'gtest_tests': 'rust_gtests',
+          'isolated_scripts': 'rust_native_tests',
+        },
+      },
       'linux-rust-x64-rel' : {
         'additional_compile_targets': [
           'mojo_rust',
diff --git a/testing/rust_gtest_interop/rust_gtest_interop.cc b/testing/rust_gtest_interop/rust_gtest_interop.cc
index 126080d95..f75e2b8 100644
--- a/testing/rust_gtest_interop/rust_gtest_interop.cc
+++ b/testing/rust_gtest_interop/rust_gtest_interop.cc
@@ -10,8 +10,8 @@
 
 namespace {
 
-// The C++ test fixture used for all Rust unit tests. It provides nothing except
-// the test body which calls thr Rust function.
+// The default C++ test fixture used for Rust unit tests. It provides nothing
+// except the test body which calls the Rust function.
 class RustTest : public testing::Test {
  public:
   explicit RustTest(void (&test_fn)()) : test_fn_(test_fn) {}
@@ -23,12 +23,17 @@
 
 }  // namespace
 
-void rust_gtest_add_test(void (*test_fn)(),
+GtestFactory rust_gtest_default_factory() {
+  return [](void (*f)()) -> testing::Test* { return new RustTest(*f); };
+}
+
+void rust_gtest_add_test(GtestFactory gtest_factory,
+                         void (*test_function)(),
                          const char* test_suite_name,
                          const char* test_name,
                          const char* file,
                          int32_t line) {
-  auto factory = [=]() -> testing::Test* { return new RustTest(*test_fn); };
+  auto factory = [=]() { return gtest_factory(test_function); };
   testing::RegisterTest(test_suite_name, test_name, nullptr, nullptr, file,
                         line, factory);
 }
diff --git a/testing/rust_gtest_interop/rust_gtest_interop.h b/testing/rust_gtest_interop/rust_gtest_interop.h
index 14d8cbe0..0b44a44 100644
--- a/testing/rust_gtest_interop/rust_gtest_interop.h
+++ b/testing/rust_gtest_interop/rust_gtest_interop.h
@@ -9,8 +9,21 @@
 
 #include "third_party/rust/cxx/v1/crate/include/cxx.h"
 
+namespace testing {
+class Test;
+}
+
 namespace rust_gtest_interop {
 
+// The factory function which will construct a testing::Test subclass that runs
+// the given function pointer as the test body. The factory function exists to
+// allow choosing different subclasses of testing::Test.
+using GtestFactory = testing::Test* (*)(void (*)());
+
+// Returns a factory that will run the test function. Used for any Rust tests
+// that don't need a specific C++ testing::Test subclass.
+GtestFactory rust_gtest_default_factory();
+
 // Register a test to be run via GTest. This must be called before main(), as
 // there's no calls from C++ into Rust to collect tests. Any function given to
 // this function will be included in the set of tests run by the RUN_ALL_TESTS()
@@ -22,7 +35,8 @@
 //
 // SAFETY: This function makes copies of the strings so the pointers do not need
 // to outlive the function call.
-void rust_gtest_add_test(void (*test_fn)(),
+void rust_gtest_add_test(GtestFactory gtest_factory,
+                         void (*test_function)(),
                          const char* test_suite_name,
                          const char* test_name,
                          const char* file,
diff --git a/testing/rust_gtest_interop/rust_gtest_interop.rs b/testing/rust_gtest_interop/rust_gtest_interop.rs
index 817288d..5353617 100644
--- a/testing/rust_gtest_interop/rust_gtest_interop.rs
+++ b/testing/rust_gtest_interop/rust_gtest_interop.rs
@@ -81,38 +81,86 @@
         }
     }
 
-    extern "C" {
-        /// The C++ mangled name for rust_gtest_interop::rust_gtest_add_test(). This comes from
-        /// `objdump -t` on the C++ object file.
-        ///
-        /// TODO(danakj): We do this by hand because cxx doesn't support passing raw function
-        /// pointers nor passing `*const c_char`: https://github.com/dtolnay/cxx/issues/1011 and
-        /// https://github.com/dtolnay/cxx/issues/1015.
-        fn _ZN18rust_gtest_interop19rust_gtest_add_testEPFvvEPKcS3_S3_i(
-            func: extern "C" fn(),
-            test_suite_name: *const std::os::raw::c_char,
-            test_name: *const std::os::raw::c_char,
-            file: *const std::os::raw::c_char,
-            line: i32,
-        );
+    /// Used in the function pointer return type of rust_gtest_default_factory() in order to keep
+    /// some type safety while erasing the C++ rust_gtest_interop::GtestFactory's type.
+    #[non_exhaustive]
+    #[repr(C)]
+    struct GTestFactoryPtr(usize);
+
+    /// Wrapper that calls C++ rust_gtest_default_factory().
+    ///
+    /// The function returns a function pointer to a C++ type that Rust doesn't know about, since
+    /// we're not using generated C++ bindings. So we just represent the function pointer as a
+    ///  `GTestFactoryPtr (*)()`. The function pointer only exists to be passed to
+    /// rust_gtest_add_test(), and Rust code must not call it since the actual signature is lost.
+    ///
+    /// # Safety
+    ///
+    /// Rust must not call the returned function pointer, the only thing Rust can do with it is pass
+    /// it to rust_gtest_add_test().
+    ///
+    /// TODO(danakj): We do this by hand because cxx doesn't support function pointers
+    /// (https://github.com/dtolnay/cxx/issues/1011). We could wrap the function pointer in a
+    /// struct, but then we have to pass it in UniquePtr a function pointer with 'static lifetime.
+    /// We do this instead of introducing multiple levels of extra abstractions (a struct,
+    /// unique_ptr) and leaking heap memory.
+    unsafe fn rust_gtest_default_factory() -> extern "C" fn() -> GTestFactoryPtr {
+        extern "C" {
+            /// The C++ mangled name for rust_gtest_interop::rust_gtest_default_factory(). This
+            /// comes from `objdump -t` on the C++ object file.
+            fn _ZN18rust_gtest_interop26rust_gtest_default_factoryEv()
+            -> extern "C" fn() -> GTestFactoryPtr;
+        }
+
+        _ZN18rust_gtest_interop26rust_gtest_default_factoryEv()
     }
+
     /// Wrapper that calls C++ rust_gtest_add_test().
-    fn rust_add_test(
+    ///
+    /// Note that the `factory` parameter is actually a C++ function pointer, of type
+    /// rust_gtest_interop::GtestFactory. However the function pointer includes C++ types Rust
+    /// doesn't know about and we are not using a C++ bindings generator to know about them. So we
+    /// cheat and just call it a `GTestFactoryPtr (*)()`.
+    ///
+    /// # Safety
+    ///
+    /// The `factory` function pointer can only be a pointer returned from
+    /// rust_gtest_default_factory(), or some other similar function that returns a C++
+    /// `rust_gtest_interop::GtestFactory`. It is not actually a `GTestFactoryPtr (*)()`, so
+    /// other function pointers would be invalid.
+    ///
+    /// TODO(danakj): We do this by hand because cxx doesn't support passing raw function pointers
+    /// nor passing `*const c_char`: https://github.com/dtolnay/cxx/issues/1011 and
+    /// https://github.com/dtolnay/cxx/issues/1015.
+    unsafe fn rust_gtest_add_test(
+        factory: extern "C" fn() -> GTestFactoryPtr,
         func: extern "C" fn(),
         test_suite_name: *const std::os::raw::c_char,
         test_name: *const std::os::raw::c_char,
         file: *const std::os::raw::c_char,
         line: i32,
     ) {
-        unsafe {
-            _ZN18rust_gtest_interop19rust_gtest_add_testEPFvvEPKcS3_S3_i(
-                func,
-                test_suite_name,
-                test_name,
-                file,
-                line,
-            )
+        extern "C" {
+            /// The C++ mangled name for rust_gtest_interop::rust_gtest_add_test(). This comes from
+            /// `objdump -t` on the C++ object file.
+            fn _ZN18rust_gtest_interop19rust_gtest_add_testEPFPN7testing4TestEPFvvEES4_PKcS8_S8_i(
+                factory: extern "C" fn() -> GTestFactoryPtr,
+                func: extern "C" fn(),
+                test_suite_name: *const std::os::raw::c_char,
+                test_name: *const std::os::raw::c_char,
+                file: *const std::os::raw::c_char,
+                line: i32,
+            );
         }
+
+        _ZN18rust_gtest_interop19rust_gtest_add_testEPFPN7testing4TestEPFvvEES4_PKcS8_S8_i(
+            factory,
+            func,
+            test_suite_name,
+            test_name,
+            file,
+            line,
+        )
     }
 
     /// Information used to register a function pointer as a test with the C++ Gtest framework.
@@ -131,13 +179,19 @@
     /// thread, before main() is run. It may not panic, or call anything that may panic.
     pub fn register_test(r: TestRegistration) {
         let line = r.line.try_into().unwrap_or(-1);
-        rust_add_test(
-            r.func,
-            r.test_suite_name.as_ptr(),
-            r.test_name.as_ptr(),
-            r.file.as_ptr(),
-            line,
-        );
+        // SAFETY: The `factory` parameter to rust_gtest_add_test() must be a type-erased C++
+        // GtestFactory, which is what rust_gtest_default_factory() returns.
+        unsafe {
+            let factory = rust_gtest_default_factory();
+            rust_gtest_add_test(
+                factory,
+                r.func,
+                r.test_suite_name.as_ptr(),
+                r.test_name.as_ptr(),
+                r.file.as_ptr(),
+                line,
+            )
+        };
     }
 }
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 0f70de4..4f899a5a 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2468,23 +2468,6 @@
             ]
         }
     ],
-    "DesktopBookmarksBarAppShortcut": [
-        {
-            "platforms": [
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "AppsShortcutDefaultOff"
-                    ]
-                }
-            ]
-        }
-    ],
     "DesktopNtpModules": [
         {
             "platforms": [
@@ -6220,6 +6203,21 @@
             ]
         }
     ],
+    "SmartLockUIRevamp": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_20220214",
+                    "enable_features": [
+                        "SmartLockUIRevamp"
+                    ]
+                }
+            ]
+        }
+    ],
     "SnoozeIPH": [
         {
             "platforms": [
diff --git a/third_party/blink/public/mojom/mediastream/media_stream.mojom b/third_party/blink/public/mojom/mediastream/media_stream.mojom
index f6441e8f..753b1db 100644
--- a/third_party/blink/public/mojom/mediastream/media_stream.mojom
+++ b/third_party/blink/public/mojom/mediastream/media_stream.mojom
@@ -121,6 +121,13 @@
   bool request_pan_tilt_zoom_permission;
 };
 
+// Results returned by a successful GetOpenDevice call.
+struct GetOpenDeviceResponse {
+  string label;
+  MediaStreamDevice device;
+  bool pan_tilt_zoom_allowed;
+};
+
 // Per-frame renderer-side interface that receives device stopped/change
 // notifications or pause requests from the browser process.
 interface MediaStreamDeviceObserver {
@@ -212,6 +219,16 @@
        mojo_base.mojom.Token crop_id,
        uint32 crop_version)
     => (media.mojom.CropRequestResult crop_result);
+
+  // Get a MediaStreamDevice metadata object which refers to the same flow of
+  // media backing an existing MediaStreamDevice.
+  //
+  // This allows for example transferring of MediaStreamTracks between
+  // contexts: the receiving context calls GetOpenDevice with the session id
+  // of the MediaStreamDevice created by the original context.
+  // Response is null if and only if result != OK.
+  GetOpenDevice(mojo_base.mojom.UnguessableToken session_id)
+    => (MediaStreamRequestResult result, GetOpenDeviceResponse? response);
 };
 
 // Browser-side interface that is used by the renderer process to notify the
diff --git a/third_party/blink/renderer/core/css/build.gni b/third_party/blink/renderer/core/css/build.gni
index 4000f3a..ce29f22 100644
--- a/third_party/blink/renderer/core/css/build.gni
+++ b/third_party/blink/renderer/core/css/build.gni
@@ -499,6 +499,7 @@
   "parser/sizes_math_function_parser.h",
   "part_names.cc",
   "part_names.h",
+  "pending_sheet_type.h",
   "properties/computed_style_utils.cc",
   "properties/computed_style_utils.h",
   "properties/css_bitset.h",
diff --git a/third_party/blink/renderer/core/css/pending_sheet_type.h b/third_party/blink/renderer/core/css/pending_sheet_type.h
new file mode 100644
index 0000000..96a1bab
--- /dev/null
+++ b/third_party/blink/renderer/core/css/pending_sheet_type.h
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PENDING_SHEET_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PENDING_SHEET_TYPE_H_
+
+#include <stdint.h>
+
+namespace blink {
+
+enum class PendingSheetType { kNone, kNonBlocking, kBlocking };
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PENDING_SHEET_TYPE_H_
diff --git a/third_party/blink/renderer/core/css/style_element.cc b/third_party/blink/renderer/core/css/style_element.cc
index 200140f..8871ade 100644
--- a/third_party/blink/renderer/core/css/style_element.cc
+++ b/third_party/blink/renderer/core/css/style_element.cc
@@ -31,6 +31,7 @@
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/html/blocking_attribute.h"
 #include "third_party/blink/renderer/core/html/html_style_element.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/svg/svg_style_element.h"
@@ -49,7 +50,8 @@
     : created_by_parser_(created_by_parser),
       loading_(false),
       registered_as_candidate_(false),
-      start_position_(TextPosition::BelowRangePosition()) {
+      start_position_(TextPosition::BelowRangePosition()),
+      pending_sheet_type_(PendingSheetType::kNone) {
   if (created_by_parser && document &&
       document->GetScriptableDocumentParser() &&
       !document->IsInDocumentWrite()) {
@@ -115,8 +117,11 @@
 
   if (sheet_->IsLoading()) {
     DCHECK(IsSameObject(owner_element));
-    owner_element.GetDocument().GetStyleEngine().RemovePendingSheet(
-        owner_element);
+    if (pending_sheet_type_ == PendingSheetType::kBlocking) {
+      owner_element.GetDocument().GetStyleEngine().RemovePendingSheet(
+          owner_element);
+    }
+    pending_sheet_type_ = PendingSheetType::kNone;
   }
 
   sheet_.Release()->ClearOwnerNode();
@@ -155,17 +160,27 @@
   if (IsCSS(element, type) && passes_content_security_policy_checks) {
     scoped_refptr<MediaQuerySet> media_queries;
     const AtomicString& media_string = media();
+    bool media_query_matches = true;
     if (!media_string.IsEmpty()) {
       media_queries =
           MediaQuerySet::Create(media_string, element.GetExecutionContext());
+      if (LocalFrame* frame = document.GetFrame()) {
+        MediaQueryEvaluator evaluator(frame);
+        media_query_matches = evaluator.Eval(*media_queries);
+      }
     }
+    // TODO(crbug.com/1271296): Should be blocking only when created by parser
+    // or has `blocking="render"`, but created_by_parser_ flag is flipped to
+    // false in FinishParsingChildren(), which causes test failures.
+    pending_sheet_type_ = media_query_matches ? PendingSheetType::kBlocking
+                                              : PendingSheetType::kNonBlocking;
     loading_ = true;
     TextPosition start_position =
         start_position_ == TextPosition::BelowRangePosition()
             ? TextPosition::MinimumPosition()
             : start_position_;
-    new_sheet =
-        document.GetStyleEngine().CreateSheet(element, text, start_position);
+    new_sheet = document.GetStyleEngine().CreateSheet(
+        element, text, start_position, pending_sheet_type_);
     new_sheet->SetMediaQueries(media_queries);
     loading_ = false;
   }
@@ -192,12 +207,16 @@
     return false;
 
   DCHECK(IsSameObject(*sheet_->ownerNode()));
-  document.GetStyleEngine().RemovePendingSheet(*sheet_->ownerNode());
+  if (pending_sheet_type_ == PendingSheetType::kBlocking)
+    document.GetStyleEngine().RemovePendingSheet(*sheet_->ownerNode());
+  pending_sheet_type_ = PendingSheetType::kNone;
   return true;
 }
 
 void StyleElement::SetToPendingState(Document& document, Element& element) {
   DCHECK(IsSameObject(element));
+  DCHECK_LT(pending_sheet_type_, PendingSheetType::kBlocking);
+  pending_sheet_type_ = PendingSheetType::kBlocking;
   document.GetStyleEngine().AddPendingSheet(element);
 }
 
diff --git a/third_party/blink/renderer/core/css/style_element.h b/third_party/blink/renderer/core/css/style_element.h
index 11f910a..4ffa573 100644
--- a/third_party/blink/renderer/core/css/style_element.h
+++ b/third_party/blink/renderer/core/css/style_element.h
@@ -22,10 +22,12 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_ELEMENT_H_
 
 #include "third_party/blink/renderer/core/css/css_style_sheet.h"
+#include "third_party/blink/renderer/core/css/pending_sheet_type.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_position.h"
 
 namespace blink {
 
+class BlockingAttribute;
 class ContainerNode;
 class Document;
 class Element;
@@ -42,6 +44,7 @@
 
   virtual const AtomicString& type() const = 0;
   virtual const AtomicString& media() const = 0;
+  virtual BlockingAttribute* blocking() const = 0;
 
   // Returns whether |this| and |node| are the same object. Helps us verify
   // parameter validity in certain member functions with an Element parameter
@@ -70,6 +73,7 @@
   bool loading_ : 1;
   bool registered_as_candidate_ : 1;
   TextPosition start_position_;
+  PendingSheetType pending_sheet_type_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 21da7da0..bb120804 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -739,11 +739,13 @@
 
 CSSStyleSheet* StyleEngine::CreateSheet(Element& element,
                                         const String& text,
-                                        TextPosition start_position) {
+                                        TextPosition start_position,
+                                        PendingSheetType type) {
   DCHECK(element.GetDocument() == GetDocument());
   CSSStyleSheet* style_sheet = nullptr;
 
-  AddPendingSheet(element);
+  if (type == PendingSheetType::kBlocking)
+    AddPendingSheet(element);
 
   AtomicString text_content(text);
 
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index 7e2059c..725d98b 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -44,6 +44,7 @@
 #include "third_party/blink/renderer/core/css/invalidation/pending_invalidations.h"
 #include "third_party/blink/renderer/core/css/invalidation/style_invalidator.h"
 #include "third_party/blink/renderer/core/css/layout_tree_rebuild_root.h"
+#include "third_party/blink/renderer/core/css/pending_sheet_type.h"
 #include "third_party/blink/renderer/core/css/rule_feature_set.h"
 #include "third_party/blink/renderer/core/css/style_invalidation_root.h"
 #include "third_party/blink/renderer/core/css/style_recalc_root.h"
@@ -351,7 +352,8 @@
 
   CSSStyleSheet* CreateSheet(Element&,
                              const String& text,
-                             WTF::TextPosition start_position);
+                             WTF::TextPosition start_position,
+                             PendingSheetType type);
 
   void CollectFeaturesTo(RuleFeatureSet& features);
 
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 7f95472..06b9cf2 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -777,14 +777,14 @@
   String sheet_text("div {}");
   TextPosition min_pos = TextPosition::MinimumPosition();
 
-  CSSStyleSheet* sheet1 =
-      GetStyleEngine().CreateSheet(*element, sheet_text, min_pos);
+  CSSStyleSheet* sheet1 = GetStyleEngine().CreateSheet(
+      *element, sheet_text, min_pos, PendingSheetType::kNonBlocking);
 
   // Check that the first sheet is not using a cached StyleSheetContents.
   EXPECT_FALSE(sheet1->Contents()->IsUsedFromTextCache());
 
-  CSSStyleSheet* sheet2 =
-      GetStyleEngine().CreateSheet(*element, sheet_text, min_pos);
+  CSSStyleSheet* sheet2 = GetStyleEngine().CreateSheet(
+      *element, sheet_text, min_pos, PendingSheetType::kNonBlocking);
 
   // Check that the second sheet uses the cached StyleSheetContents for the
   // first.
@@ -801,7 +801,8 @@
 
   element = MakeGarbageCollected<HTMLStyleElement>(GetDocument(),
                                                    CreateElementFlags());
-  sheet1 = GetStyleEngine().CreateSheet(*element, sheet_text, min_pos);
+  sheet1 = GetStyleEngine().CreateSheet(*element, sheet_text, min_pos,
+                                        PendingSheetType::kNonBlocking);
 
   // Check that we did not use a cached StyleSheetContents after the garbage
   // collection.
diff --git a/third_party/blink/renderer/core/html/html_style_element.h b/third_party/blink/renderer/core/html/html_style_element.h
index f2e6e84..c1c7459 100644
--- a/third_party/blink/renderer/core/html/html_style_element.h
+++ b/third_party/blink/renderer/core/html/html_style_element.h
@@ -44,7 +44,7 @@
   bool disabled() const;
   void setDisabled(bool);
 
-  BlockingAttribute& blocking() const { return *blocking_attribute_; }
+  BlockingAttribute* blocking() const override { return blocking_attribute_; }
 
   void Trace(Visitor*) const override;
 
diff --git a/third_party/blink/renderer/core/html/link_style.cc b/third_party/blink/renderer/core/html/link_style.cc
index 9f48335..ddcb2da2 100644
--- a/third_party/blink/renderer/core/html/link_style.cc
+++ b/third_party/blink/renderer/core/html/link_style.cc
@@ -37,7 +37,7 @@
 LinkStyle::LinkStyle(HTMLLinkElement* owner)
     : LinkResource(owner),
       disabled_state_(kUnset),
-      pending_sheet_type_(kNone),
+      pending_sheet_type_(PendingSheetType::kNone),
       render_blocking_behavior_(RenderBlockingBehavior::kUnset),
       loading_(false),
       fired_load_(false),
@@ -151,8 +151,8 @@
 }
 
 void LinkStyle::SetToPendingState() {
-  DCHECK_LT(pending_sheet_type_, kBlocking);
-  AddPendingSheet(kBlocking);
+  DCHECK_LT(pending_sheet_type_, PendingSheetType::kBlocking);
+  AddPendingSheet(PendingSheetType::kBlocking);
 }
 
 void LinkStyle::ClearSheet() {
@@ -174,7 +174,7 @@
     return;
   pending_sheet_type_ = type;
 
-  if (pending_sheet_type_ == kNonBlocking)
+  if (pending_sheet_type_ == PendingSheetType::kNonBlocking)
     return;
   GetDocument().GetStyleEngine().AddPendingSheet(*owner_);
 }
@@ -182,11 +182,11 @@
 void LinkStyle::RemovePendingSheet() {
   DCHECK(owner_);
   PendingSheetType type = pending_sheet_type_;
-  pending_sheet_type_ = kNone;
+  pending_sheet_type_ = PendingSheetType::kNone;
 
-  if (type == kNone)
+  if (type == PendingSheetType::kNone)
     return;
-  if (type == kNonBlocking) {
+  if (type == PendingSheetType::kNonBlocking) {
     // Tell StyleEngine to re-compute styleSheets of this owner_'s treescope.
     GetDocument().GetStyleEngine().ModifiedStyleSheetCandidateNode(*owner_);
     return;
@@ -215,7 +215,7 @@
     // Check #2: An alternate sheet becomes enabled while it is still loading.
     if (owner_->RelAttribute().IsAlternate() &&
         disabled_state_ == kEnabledViaScript)
-      AddPendingSheet(kBlocking);
+      AddPendingSheet(PendingSheetType::kBlocking);
 
     // Check #3: A main sheet becomes enabled while it was still loading and
     // after it was disabled via script. It takes really terrible code to make
@@ -224,7 +224,7 @@
     // only 3 sheets. :)
     if (!owner_->RelAttribute().IsAlternate() &&
         disabled_state_ == kEnabledViaScript && old_disabled_state == kDisabled)
-      AddPendingSheet(kBlocking);
+      AddPendingSheet(PendingSheetType::kBlocking);
 
     // If the sheet is already loading just bail.
     return;
@@ -285,7 +285,8 @@
                          (RuntimeEnabledFeatures::BlockingAttributeEnabled() &&
                           owner_->blocking().IsRenderBlocking()));
 
-  AddPendingSheet(render_blocking ? kBlocking : kNonBlocking);
+  AddPendingSheet(render_blocking ? PendingSheetType::kBlocking
+                                  : PendingSheetType::kNonBlocking);
 
   // Load stylesheets that are not needed for the layout immediately with low
   // priority.  When the link element is created by scripts, load the
diff --git a/third_party/blink/renderer/core/html/link_style.h b/third_party/blink/renderer/core/html/link_style.h
index 65d52b0..af256d2 100644
--- a/third_party/blink/renderer/core/html/link_style.h
+++ b/third_party/blink/renderer/core/html/link_style.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LINK_STYLE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LINK_STYLE_H_
 
+#include "third_party/blink/renderer/core/css/pending_sheet_type.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/node.h"
 #include "third_party/blink/renderer/core/html/link_resource.h"
@@ -65,8 +66,6 @@
 
   enum DisabledState { kUnset, kEnabledViaScript, kDisabled };
 
-  enum PendingSheetType { kNone, kNonBlocking, kBlocking };
-
   void ClearSheet();
   void AddPendingSheet(PendingSheetType);
   void RemovePendingSheet();
diff --git a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
index d132278..907fd4e 100644
--- a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
+++ b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
@@ -297,6 +297,10 @@
     const String& id,
     const CanvasContextCreationAttributesCore& attributes) {
   DCHECK_EQ(execution_context, GetTopExecutionContext());
+
+  if (execution_context->IsContextDestroyed())
+    return nullptr;
+
   CanvasRenderingContext::CanvasRenderingAPI rendering_api =
       CanvasRenderingContext::RenderingAPIFromId(id, execution_context);
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index dfee14d..837811a 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -824,13 +824,12 @@
   return nullptr;
 }
 
-void PaintLayer::SetNeedsCompositingInputsUpdate(bool mark_ancestor_flags) {
+void PaintLayer::SetNeedsCompositingInputsUpdate() {
   // TODO(chrishtr): These are a bit of a heavy hammer, because not all
   // things which require compositing inputs update require a descendant-
   // dependent flags update. Reduce call sites after CAP launch allows
   /// removal of CompositingInputsUpdater.
-  if (mark_ancestor_flags)
-    MarkAncestorChainForFlagsUpdate(kNeedsDescendantDependentUpdate);
+  MarkAncestorChainForFlagsUpdate(kNeedsDescendantDependentUpdate);
 }
 
 void PaintLayer::SetNeedsVisualOverflowRecalc() {
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index 8ba80ac..66fb0654 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -520,7 +520,7 @@
     return needs_visual_overflow_recalc_;
   }
   void SetNeedsVisualOverflowRecalc();
-  void SetNeedsCompositingInputsUpdate(bool mark_ancestor_flags = true);
+  void SetNeedsCompositingInputsUpdate();
 
   void UpdateAncestorScrollContainerLayer(
       const PaintLayer* ancestor_scroll_container_layer) {
diff --git a/third_party/blink/renderer/core/svg/svg_style_element.h b/third_party/blink/renderer/core/svg/svg_style_element.h
index f6d7401..0383df44 100644
--- a/third_party/blink/renderer/core/svg/svg_style_element.h
+++ b/third_party/blink/renderer/core/svg/svg_style_element.h
@@ -45,6 +45,8 @@
   const AtomicString& media() const override;
   void setMedia(const AtomicString&);
 
+  BlockingAttribute* blocking() const override { return nullptr; }
+
   String title() const override;
   void setTitle(const AtomicString&);
 
diff --git a/third_party/blink/renderer/modules/mediastream/mock_mojo_media_stream_dispatcher_host.h b/third_party/blink/renderer/modules/mediastream/mock_mojo_media_stream_dispatcher_host.h
index 670bca61..1acf7e7 100644
--- a/third_party/blink/renderer/modules/mediastream/mock_mojo_media_stream_dispatcher_host.h
+++ b/third_party/blink/renderer/modules/mediastream/mock_mojo_media_stream_dispatcher_host.h
@@ -59,6 +59,8 @@
                     uint32_t,
                     CropCallback));
 #endif
+  MOCK_METHOD2(GetOpenDevice,
+               void(const base::UnguessableToken&, GetOpenDeviceCallback));
 
   void ResetSessionId() { session_id_ = base::UnguessableToken::Create(); }
   void DoNotRunCallback() { do_not_run_cb_ = true; }
diff --git a/third_party/blink/renderer/modules/subapps/sub_apps.cc b/third_party/blink/renderer/modules/subapps/sub_apps.cc
index 54c7cce..8002bd9 100644
--- a/third_party/blink/renderer/modules/subapps/sub_apps.cc
+++ b/third_party/blink/renderer/modules/subapps/sub_apps.cc
@@ -72,7 +72,9 @@
 // static
 const char SubApps::kSupplementName[] = "SubApps";
 
-SubApps::SubApps(Navigator& navigator) : Supplement<Navigator>(navigator) {}
+SubApps::SubApps(Navigator& navigator)
+    : Supplement<Navigator>(navigator),
+      service_(navigator.GetExecutionContext()) {}
 
 // static
 SubApps* SubApps::subApps(Navigator& navigator) {
@@ -87,21 +89,27 @@
 void SubApps::Trace(Visitor* visitor) const {
   ScriptWrappable::Trace(visitor);
   Supplement<Navigator>::Trace(visitor);
+  visitor->Trace(service_);
 }
 
-mojo::Remote<mojom::blink::SubAppsService>& SubApps::GetService() {
+HeapMojoRemote<mojom::blink::SubAppsService>& SubApps::GetService() {
   if (!service_.is_bound()) {
-    GetSupplementable()
-        ->GetExecutionContext()
-        ->GetBrowserInterfaceBroker()
-        .GetInterface(service_.BindNewPipeAndPassReceiver());
+    auto* context = GetSupplementable()->GetExecutionContext();
+    context->GetBrowserInterfaceBroker().GetInterface(
+        service_.BindNewPipeAndPassReceiver(
+            context->GetTaskRunner(TaskType::kMiscPlatformAPI)));
     // In case the other endpoint gets disconnected, we want to reset our end of
     // the pipe as well so that we don't remain connected to a half-open pipe.
-    service_.reset_on_disconnect();
+    service_.set_disconnect_handler(
+        WTF::Bind(&SubApps::OnConnectionError, WrapWeakPersistent(this)));
   }
   return service_;
 }
 
+void SubApps::OnConnectionError() {
+  service_.reset();
+}
+
 ScriptPromise SubApps::add(ScriptState* script_state,
                            const String& install_url,
                            ExceptionState& exception_state) {
diff --git a/third_party/blink/renderer/modules/subapps/sub_apps.h b/third_party/blink/renderer/modules/subapps/sub_apps.h
index c19e362..66dda28 100644
--- a/third_party/blink/renderer/modules/subapps/sub_apps.h
+++ b/third_party/blink/renderer/modules/subapps/sub_apps.h
@@ -5,9 +5,9 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SUBAPPS_SUB_APPS_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SUBAPPS_SUB_APPS_H_
 
-#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/subapps/sub_apps_service.mojom-blink.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
@@ -37,10 +37,11 @@
   ScriptPromise list(ScriptState*, ExceptionState&);
 
  private:
-  mojo::Remote<mojom::blink::SubAppsService>& GetService();
+  HeapMojoRemote<mojom::blink::SubAppsService>& GetService();
+  void OnConnectionError();
   bool CheckPreconditionsMaybeThrow(ExceptionState&);
 
-  mojo::Remote<mojom::blink::SubAppsService> service_;
+  HeapMojoRemote<mojom::blink::SubAppsService> service_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 81b671e..ff9e74b3 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3377,6 +3377,8 @@
 crbug.com/626703 [ Win ] virtual/partitioned-cookies/http/tests/inspector-protocol/network/disabled-cache-navigation.js [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/webrtc-encoded-transform/RTCPeerConnection-insertable-streams-simulcast.https.html [ Timeout ]
+crbug.com/626703 [ Mac10.15 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/registered-property-value-010.https.html [ Failure ]
 crbug.com/626703 [ Mac10.12 ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/maxframes.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] virtual/portals/external/wpt/portals/history/history-manipulation-inside-portal-with-subframes.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] virtual/portals/wpt_internal/portals/create-many-portals.html [ Timeout ]
@@ -7544,9 +7546,6 @@
 # Need implementation of blocking=render on link headers
 crbug.com/1271296 virtual/no-forced-frame-updates/external/wpt/html/dom/render-blocking/header-inserted-preload-link.tentative.html [ Skip ]
 
-# Need to unblock scripts on style elements when media doesn't match
-crbug.com/1296736 external/wpt/html/semantics/document-metadata/interactions-of-styling-and-scripting/style-element-media-not-match-does-not-block-script.html [ Failure ]
-
 # Sheriff 2022-02-07
 crbug.com/1289759 [ Linux ] http/tests/loading/slow-parsing-subframe.html [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index 03995769..82e89019 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -41,7 +41,7 @@
 [ Win ] wpt_internal/webgpu/cts.https.html?q=webgpu:web_platform,copyToTexture,ImageBitmap:from_ImageData:* [ Slow ]
 [ Win ] wpt_internal/webgpu/cts.https.html?q=webgpu:api,operation,rendering,draw:arguments:* [ Slow ]
 [ Win ] wpt_internal/webgpu/cts.https.html?q=webgpu:api,operation,render_pass,storeOp:render_pass_store_op,color_attachment_only:* [ Slow ]
-# [ Mac ] wpt_internal/webgpu/cts.https.html?q=webgpu:api,operation,command_buffer,image_copy:rowsPerImage_and_bytesPerRow_depth_stencil:* [ Slow ]
+[ Mac ] wpt_internal/webgpu/cts.https.html?q=webgpu:api,operation,command_buffer,image_copy:rowsPerImage_and_bytesPerRow_depth_stencil:* [ Slow ]
 # Intel only. Really slow and killed by the GPU watchdog?
 crbug.com/dawn/1046 [ Mac ] wpt_internal/webgpu/cts.https.html?q=webgpu:shader,execution,robust_access_vertex:vertex_buffer_access:indexed=false;indirect=true;drawCallTestParameter="firstVertex";type="float32x4";* [ Slow Crash ]
 crbug.com/dawn/1046 [ Mac ] wpt_internal/webgpu/cts.https.html?q=webgpu:shader,execution,robust_access_vertex:vertex_buffer_access:indexed=false;indirect=true;drawCallTestParameter="instanceCount";type="float32x4";* [ Slow Crash ]
@@ -163,13 +163,6 @@
 # Replace depth-clamp feature with depth-clip
 crbug.com/dawn/1178 wpt_internal/webgpu/cts.https.html?q=webgpu:api,operation,rendering,depth_clip_clamp:* [ Failure ]
 
-# crbug.com/dawn/1281
-# Temporary. Failing a loadOp/storeOp validation error in Dawn.
-# https://dawn-review.googlesource.com/c/dawn/+/82540
-wpt_internal/webgpu/cts.https.html?q=webgpu:api,validation,attachment_compatibility:render_pass_and_bundle,depth_format:* [ Failure ]
-wpt_internal/webgpu/cts.https.html?q=webgpu:api,operation,command_buffer,image_copy:rowsPerImage_and_bytesPerRow_depth_stencil:* [ Skip ] # Skipped because it conflicted with a Slow expectation.
-wpt_internal/webgpu/cts.https.html?q=webgpu:api,operation,command_buffer,image_copy:offsets_and_sizes_copy_depth_stencil:* [ Failure ]
-
 # Our automated build does not support mp4 currently (fails on Linux, Mac, and Win Intel)
 wpt_internal/webgpu/cts.https.html?q=webgpu:web_platform,external_texture,video:importExternalTexture,sample:videoSource="red-green.mp4" [ Failure ]
 
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index 809cf8e9..57450c9 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: 6bce541b6701e2517f5ddcd447e29d875bd8c822
+Version: 5f5ec4cff46e14ef86c249bbf24b63d35e6611ff
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 2b6d060..0b12f36 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -156547,6 +156547,32 @@
        {}
       ]
      ],
+     "replaced-aspect-ratio-intrinsic-size-001.html": [
+      "8c1f213dec9b200ad874e449aca38deb46d6bd91",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square-only.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "replaced-aspect-ratio-intrinsic-size-002.html": [
+      "daf6f36022d9cbda58fe7d758863617c5acf646f",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square-only.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "replaced-aspect-ratio-stretch-fit-001.html": [
       "0653c8284a4ad029f5fb6d21f706559a24e5bf72",
       [
@@ -247276,10 +247302,6 @@
       "9d1fbaaf47b97cb82dc3003ffd2d46268100774e",
       []
      ],
-     "idlharness-expected.txt": [
-      "fda5da9e18277deb767a4abaf1479863dbac771b",
-      []
-     ],
      "js": {
       "CSS-supports-CSSStyleDeclaration-expected.txt": [
        "2e621a2f0ec3509e414ecdf7693d621c4de5c361",
@@ -306126,7 +306148,7 @@
      []
     ],
     "css-conditional.idl": [
-     "16f25583be97cfce8d4178fa68f43518e51195d2",
+     "d87f305fddf9e7c3f0151d51a595ae7140dce908",
      []
     ],
     "css-counter-styles.idl": [
@@ -307214,7 +307236,7 @@
     "early-hints": {
      "resources": {
       "early-hints-helpers.sub.js": [
-       "72cebf9540ca0240a3360feb1cb5ce47e8b5838d",
+       "5c1b2198eaee880fe6cd0a97684619788fd07b8c",
        []
       ],
       "early-hints-test-loader.h2.py": [
@@ -307226,7 +307248,7 @@
        []
       ],
       "empty.js.headers": [
-       "175cdf80464a658981a55d09d788dee26bf1e1a0",
+       "74b688d1dcefad4376e4176eb4c023aecea1a632",
        []
       ],
       "fetch-and-record-js.h2.py": [
@@ -307238,7 +307260,19 @@
        []
       ],
       "preload-initiator-type.html": [
-       "39b0db0f89534c1f012985f9e7e86b715dccf6a9",
+       "0fdeb2b9037be8785880f71045a9facbc85fb394",
+       []
+      ],
+      "redirect-cross-origin.html": [
+       "39b37f81300bd722381de0b47e2d998a7c976574",
+       []
+      ],
+      "redirect-same-origin.html": [
+       "6a2246a2acd8cd0b035c04d9b18be7bff82811e0",
+       []
+      ],
+      "redirect-with-early-hints.h2.py": [
+       "e501d85a6b5c22469bec8133b846a9ff445e4f0d",
        []
       ],
       "referrer-policy-test-loader.h2.py": [
@@ -307246,7 +307280,7 @@
        []
       ],
       "referrer-policy-test.html": [
-       "af4f393d56f13ce62e9a436d60f97eb3749f40f7",
+       "d0389c2e11cce36ae0ed0ddee454809e7360b539",
        []
       ],
       "utils.py": [
@@ -460411,6 +460445,15 @@
          {}
         ]
        ],
+       "modal-dialog-selection.html": [
+        "ab8dc4fd9842aaa6509a0bcae22df0b305fc3c6b",
+        [
+         null,
+         {
+          "testdriver": true
+         }
+        ]
+       ],
        "multiple-centered-dialogs.html": [
         "f9a62c5503c62aa019e6345a9080e0097af75faa",
         [
@@ -473200,6 +473243,42 @@
        }
       ]
      ],
+     "redirect-cross-origin.h2.window.js": [
+      "548759b19cc8441f3defbf6b412cce5e7b224179",
+      [
+       "loading/early-hints/redirect-cross-origin.h2.window.html",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "/common/utils.js"
+         ],
+         [
+          "script",
+          "resources/early-hints-helpers.sub.js"
+         ]
+        ]
+       }
+      ]
+     ],
+     "redirect-same-origin.h2.window.js": [
+      "88d64f399a77f975cc11ebc380e400b801fc06b3",
+      [
+       "loading/early-hints/redirect-same-origin.h2.window.html",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "/common/utils.js"
+         ],
+         [
+          "script",
+          "resources/early-hints-helpers.sub.js"
+         ]
+        ]
+       }
+      ]
+     ],
      "referrer-policy-no-referrer.h2.window.js": [
       "10f2c2c4bcda67541489dd1dd1a14426568d61a3",
       [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-conditional/idlharness-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-conditional/idlharness-expected.txt
deleted file mode 100644
index fda5da9e..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-conditional/idlharness-expected.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-This is a testharness.js-based test.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface CSSRule: original interface defined
-PASS Partial interface CSSRule: member names are unique
-PASS Partial namespace CSS: original namespace defined
-PASS Partial namespace CSS: member names are unique
-PASS CSSConditionRule interface: existence and properties of interface object
-PASS CSSConditionRule interface object length
-PASS CSSConditionRule interface object name
-PASS CSSConditionRule interface: existence and properties of interface prototype object
-PASS CSSConditionRule interface: existence and properties of interface prototype object's "constructor" property
-PASS CSSConditionRule interface: existence and properties of interface prototype object's @@unscopables property
-FAIL CSSConditionRule interface: attribute conditionText assert_equals: setter must be function for PutForwards, Replaceable, or non-readonly attributes expected "function" but got "undefined"
-PASS CSSMediaRule interface: existence and properties of interface object
-PASS CSSMediaRule interface object length
-PASS CSSMediaRule interface object name
-PASS CSSMediaRule interface: existence and properties of interface prototype object
-PASS CSSMediaRule interface: existence and properties of interface prototype object's "constructor" property
-PASS CSSMediaRule interface: existence and properties of interface prototype object's @@unscopables property
-PASS CSSMediaRule interface: attribute media
-PASS CSSMediaRule must be primary interface of cssMediaRule
-PASS Stringification of cssMediaRule
-PASS CSSMediaRule interface: cssMediaRule must inherit property "media" with the proper type
-PASS CSSConditionRule interface: cssMediaRule must inherit property "conditionText" with the proper type
-PASS CSSRule interface: cssMediaRule must inherit property "SUPPORTS_RULE" with the proper type
-PASS CSSSupportsRule interface: existence and properties of interface object
-PASS CSSSupportsRule interface object length
-PASS CSSSupportsRule interface object name
-PASS CSSSupportsRule interface: existence and properties of interface prototype object
-PASS CSSSupportsRule interface: existence and properties of interface prototype object's "constructor" property
-PASS CSSSupportsRule interface: existence and properties of interface prototype object's @@unscopables property
-PASS CSSSupportsRule must be primary interface of cssSupportsRule
-PASS Stringification of cssSupportsRule
-PASS CSSConditionRule interface: cssSupportsRule must inherit property "conditionText" with the proper type
-PASS CSSRule interface: cssSupportsRule must inherit property "SUPPORTS_RULE" with the proper type
-PASS CSSRule interface: constant SUPPORTS_RULE on interface object
-PASS CSSRule interface: constant SUPPORTS_RULE on interface prototype object
-PASS CSSRule interface: cssRule must inherit property "SUPPORTS_RULE" with the proper type
-PASS CSS namespace: operation escape(CSSOMString)
-PASS CSS namespace: operation supports(CSSOMString, CSSOMString)
-PASS CSS namespace: operation supports(CSSOMString)
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/css-conditional.idl b/third_party/blink/web_tests/external/wpt/interfaces/css-conditional.idl
index 16f25583..d87f305 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/css-conditional.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/css-conditional.idl
@@ -9,7 +9,7 @@
 
 [Exposed=Window]
 interface CSSConditionRule : CSSGroupingRule {
-    attribute CSSOMString conditionText;
+    readonly attribute CSSOMString conditionText;
 };
 
 [Exposed=Window]
diff --git a/third_party/blink/web_tests/fast/canvas-api/OffscreenCanvas-getContext-executionContext-detached.html b/third_party/blink/web_tests/fast/canvas-api/OffscreenCanvas-getContext-executionContext-detached.html
new file mode 100644
index 0000000..97d7de8
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas-api/OffscreenCanvas-getContext-executionContext-detached.html
@@ -0,0 +1,18 @@
+<html>
+<body>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  function go() {
+    var iframe = document.createElement("iframe");
+    document.body.appendChild(iframe);
+    var offc = new iframe.contentWindow.OffscreenCanvas(69,69);
+    iframe.remove();
+    var context = offc.getContext("2d");
+  }
+  window.onload = go;
+}, "Verify that calling getContext on an OffscreenCanvas whose parent iframe has been remove does not crash.");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/service-worker-interception-late-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/service-worker-interception-late-expected.txt
index dbcc877..4787d80 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/service-worker-interception-late-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/service-worker-interception-late-expected.txt
@@ -5,4 +5,6 @@
 [renderer] Request to http://127.0.0.1:8000/inspector-protocol/fetch/resources/empty.html, type: XHR
 [renderer] Request to http://127.0.0.1:8000/inspector-protocol/fetch/resources/fetch-data.txt, type: XHR
 Response after interception enabled: overriden response body
+Stopped service worker
+Disconnected from service worker
 
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/service-worker-interception-late.js b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/service-worker-interception-late.js
index aa439be..4f3db6f 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/service-worker-interception-late.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/service-worker-interception-late.js
@@ -9,7 +9,6 @@
       {autoAttach: true, waitForDebuggerOnStart: false, flatten: true});
   dp.Target.onAttachedToTarget(async event => {
     serviceWorkerSession = session.createChild(event.params.sessionId);
-
   });
 
   await dp.ServiceWorker.enable();
@@ -23,9 +22,10 @@
       const result = await dp.ServiceWorker.onceWorkerVersionUpdated();
       versions = result.params.versions;
     } while (!versions.length || versions[0].status !== phase);
+    return versions[0];
   }
 
-  await waitForServiceWorkerPhase("installing");
+  const version = await waitForServiceWorkerPhase("installing");
 
   const url = 'fetch-data.txt';
   let content = await session.evaluateAsync(`fetch("${url}").then(r => r.text())`);
@@ -63,5 +63,14 @@
   content = await session.evaluateAsync(`fetch("${url}").then(r => r.text())`);
   testRunner.log(`Response after interception enabled: ${content}`);
 
+  // Stop worker and wait till it stopped, to make sure worker shutdown
+  // is covered (see https://crbug.com/1306006).
+  dp.ServiceWorker.stopWorker({versionId: version.versionId});
+  await dp.ServiceWorker.onceWorkerVersionUpdated(
+      e => e.params.versions.length && e.params.versions[0].runningStatus === "stopped"),
+
+  testRunner.log("Stopped service worker");
+  await serviceWorkerSession.disconnect();
+  testRunner.log("Disconnected from service worker");
   testRunner.completeTest();
 })
diff --git a/third_party/node/clean_json_attrs.py b/third_party/node/clean_json_attrs.py
index 0ee0026..d5ea48c38 100755
--- a/third_party/node/clean_json_attrs.py
+++ b/third_party/node/clean_json_attrs.py
@@ -13,7 +13,7 @@
 
     removed = False
 
-    for key, val in json_dict.items():
+    for key, val in list(json_dict.items()):
       if isinstance(val, dict):
         if _remove_attrs(val, attr_pattern):
           removed = True
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index 3473f28..a1f4cb2 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -54,8 +54,8 @@
 src/stress/listing.ts
 src/webgpu/constants.ts
 src/stress/adapter/device_allocation.spec.ts
+src/webgpu/util/constants.ts
 src/webgpu/util/conversion.ts
-src/webgpu/shader/execution/builtin/builtin.ts
 src/webgpu/util/math.ts
 src/webgpu/util/unions.ts
 src/webgpu/util/texture/base.ts
@@ -248,6 +248,8 @@
 src/webgpu/idl/constants/flags.spec.ts
 src/webgpu/shader/types.ts
 src/webgpu/shader/values.ts
+src/webgpu/util/compare.ts
+src/webgpu/shader/execution/expression.ts
 src/webgpu/shader/execution/robust_access.spec.ts
 src/webgpu/shader/execution/robust_access_vertex.spec.ts
 src/webgpu/shader/execution/zero_init.spec.ts
diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl.h b/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl.h
index ccc21b1..465b358 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl.h
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl.h
@@ -682,6 +682,7 @@
         

         virtual HRESULT STDMETHODCALLTYPE Update( 

             /* [string][in] */ const WCHAR *app_id,

+            /* [string][in] */ const WCHAR *install_data_index,

             /* [in] */ BOOL same_version_update_allowed,

             /* [in] */ IUpdaterObserver *observer) = 0;

         

@@ -733,6 +734,7 @@
         HRESULT ( STDMETHODCALLTYPE *Update )( 

             IUpdater * This,

             /* [string][in] */ const WCHAR *app_id,

+            /* [string][in] */ const WCHAR *install_data_index,

             /* [in] */ BOOL same_version_update_allowed,

             /* [in] */ IUpdaterObserver *observer);

         

@@ -775,8 +777,8 @@
 #define IUpdater_RunPeriodicTasks(This,callback)	\

     ( (This)->lpVtbl -> RunPeriodicTasks(This,callback) ) 

 

-#define IUpdater_Update(This,app_id,same_version_update_allowed,observer)	\

-    ( (This)->lpVtbl -> Update(This,app_id,same_version_update_allowed,observer) ) 

+#define IUpdater_Update(This,app_id,install_data_index,same_version_update_allowed,observer)	\

+    ( (This)->lpVtbl -> Update(This,app_id,install_data_index,same_version_update_allowed,observer) ) 

 

 #define IUpdater_UpdateAll(This,observer)	\

     ( (This)->lpVtbl -> UpdateAll(This,observer) ) 

diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl.tlb b/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl.tlb
index 584f330f..725177697 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl.tlb
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl.tlb
Binary files differ
diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl_p.c b/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl_p.c
index 246d3df..e55c3b1d 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl_p.c
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/arm64/updater_idl_p.c
@@ -47,7 +47,7 @@
 #include "updater_idl.h"

 

 #define TYPE_FORMAT_STRING_SIZE   145                               

-#define PROC_FORMAT_STRING_SIZE   805                               

+#define PROC_FORMAT_STRING_SIZE   811                               

 #define EXPR_FORMAT_STRING_SIZE   1                                 

 #define TRANSMIT_AS_TABLE_SIZE    0            

 #define WIRE_MARSHAL_TABLE_SIZE   1            

@@ -768,23 +768,23 @@
 			0x6c,		/* Old Flags:  object, Oi2 */

 /* 708 */	NdrFcLong( 0x0 ),	/* 0 */

 /* 712 */	NdrFcShort( 0x7 ),	/* 7 */

-/* 714 */	NdrFcShort( 0x28 ),	/* ARM64 Stack size/offset = 40 */

+/* 714 */	NdrFcShort( 0x30 ),	/* ARM64 Stack size/offset = 48 */

 /* 716 */	NdrFcShort( 0x8 ),	/* 8 */

 /* 718 */	NdrFcShort( 0x8 ),	/* 8 */

 /* 720 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

-			0x4,		/* 4 */

+			0x5,		/* 5 */

 /* 722 */	0x10,		/* 16 */

 			0x1,		/* Ext Flags:  new corr desc, */

 /* 724 */	NdrFcShort( 0x0 ),	/* 0 */

 /* 726 */	NdrFcShort( 0x0 ),	/* 0 */

 /* 728 */	NdrFcShort( 0x0 ),	/* 0 */

-/* 730 */	NdrFcShort( 0x4 ),	/* 4 */

-/* 732 */	0x4,		/* 4 */

+/* 730 */	NdrFcShort( 0x5 ),	/* 5 */

+/* 732 */	0x5,		/* 5 */

 			0x80,		/* 128 */

 /* 734 */	0x81,		/* 129 */

 			0x82,		/* 130 */

 /* 736 */	0x83,		/* 131 */

-			0x0,		/* 0 */

+			0x84,		/* 132 */

 

 	/* Parameter app_id */

 

@@ -792,59 +792,65 @@
 /* 740 */	NdrFcShort( 0x8 ),	/* ARM64 Stack size/offset = 8 */

 /* 742 */	NdrFcShort( 0x58 ),	/* Type Offset=88 */

 

+	/* Parameter install_data_index */

+

+/* 744 */	NdrFcShort( 0x10b ),	/* Flags:  must size, must free, in, simple ref, */

+/* 746 */	NdrFcShort( 0x10 ),	/* ARM64 Stack size/offset = 16 */

+/* 748 */	NdrFcShort( 0x58 ),	/* Type Offset=88 */

+

 	/* Parameter same_version_update_allowed */

 

-/* 744 */	NdrFcShort( 0x48 ),	/* Flags:  in, base type, */

-/* 746 */	NdrFcShort( 0x10 ),	/* ARM64 Stack size/offset = 16 */

-/* 748 */	0x8,		/* FC_LONG */

+/* 750 */	NdrFcShort( 0x48 ),	/* Flags:  in, base type, */

+/* 752 */	NdrFcShort( 0x18 ),	/* ARM64 Stack size/offset = 24 */

+/* 754 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 	/* Parameter observer */

 

-/* 750 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

-/* 752 */	NdrFcShort( 0x18 ),	/* ARM64 Stack size/offset = 24 */

-/* 754 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

+/* 756 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

+/* 758 */	NdrFcShort( 0x20 ),	/* ARM64 Stack size/offset = 32 */

+/* 760 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

 

 	/* Return value */

 

-/* 756 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

-/* 758 */	NdrFcShort( 0x20 ),	/* ARM64 Stack size/offset = 32 */

-/* 760 */	0x8,		/* FC_LONG */

+/* 762 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

+/* 764 */	NdrFcShort( 0x28 ),	/* ARM64 Stack size/offset = 40 */

+/* 766 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 	/* Procedure UpdateAll */

 

-/* 762 */	0x33,		/* FC_AUTO_HANDLE */

+/* 768 */	0x33,		/* FC_AUTO_HANDLE */

 			0x6c,		/* Old Flags:  object, Oi2 */

-/* 764 */	NdrFcLong( 0x0 ),	/* 0 */

-/* 768 */	NdrFcShort( 0x8 ),	/* 8 */

-/* 770 */	NdrFcShort( 0x18 ),	/* ARM64 Stack size/offset = 24 */

-/* 772 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 770 */	NdrFcLong( 0x0 ),	/* 0 */

 /* 774 */	NdrFcShort( 0x8 ),	/* 8 */

-/* 776 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

+/* 776 */	NdrFcShort( 0x18 ),	/* ARM64 Stack size/offset = 24 */

+/* 778 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 780 */	NdrFcShort( 0x8 ),	/* 8 */

+/* 782 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

 			0x2,		/* 2 */

-/* 778 */	0xe,		/* 14 */

+/* 784 */	0xe,		/* 14 */

 			0x1,		/* Ext Flags:  new corr desc, */

-/* 780 */	NdrFcShort( 0x0 ),	/* 0 */

-/* 782 */	NdrFcShort( 0x0 ),	/* 0 */

-/* 784 */	NdrFcShort( 0x0 ),	/* 0 */

-/* 786 */	NdrFcShort( 0x2 ),	/* 2 */

-/* 788 */	0x2,		/* 2 */

+/* 786 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 788 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 790 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 792 */	NdrFcShort( 0x2 ),	/* 2 */

+/* 794 */	0x2,		/* 2 */

 			0x80,		/* 128 */

-/* 790 */	0x81,		/* 129 */

+/* 796 */	0x81,		/* 129 */

 			0x0,		/* 0 */

 

 	/* Parameter observer */

 

-/* 792 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

-/* 794 */	NdrFcShort( 0x8 ),	/* ARM64 Stack size/offset = 8 */

-/* 796 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

+/* 798 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

+/* 800 */	NdrFcShort( 0x8 ),	/* ARM64 Stack size/offset = 8 */

+/* 802 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

 

 	/* Return value */

 

-/* 798 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

-/* 800 */	NdrFcShort( 0x10 ),	/* ARM64 Stack size/offset = 16 */

-/* 802 */	0x8,		/* FC_LONG */

+/* 804 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

+/* 806 */	NdrFcShort( 0x10 ),	/* ARM64 Stack size/offset = 16 */

+/* 808 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 			0x0

@@ -1274,7 +1280,7 @@
     588,

     664,

     706,

-    762

+    768

     };

 

 static const MIDL_STUBLESS_PROXY_INFO IUpdater_ProxyInfo =

diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl.h b/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl.h
index 9733925..c73126d 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl.h
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl.h
@@ -682,6 +682,7 @@
         

         virtual HRESULT STDMETHODCALLTYPE Update( 

             /* [string][in] */ const WCHAR *app_id,

+            /* [string][in] */ const WCHAR *install_data_index,

             /* [in] */ BOOL same_version_update_allowed,

             /* [in] */ IUpdaterObserver *observer) = 0;

         

@@ -733,6 +734,7 @@
         HRESULT ( STDMETHODCALLTYPE *Update )( 

             IUpdater * This,

             /* [string][in] */ const WCHAR *app_id,

+            /* [string][in] */ const WCHAR *install_data_index,

             /* [in] */ BOOL same_version_update_allowed,

             /* [in] */ IUpdaterObserver *observer);

         

@@ -775,8 +777,8 @@
 #define IUpdater_RunPeriodicTasks(This,callback)	\

     ( (This)->lpVtbl -> RunPeriodicTasks(This,callback) ) 

 

-#define IUpdater_Update(This,app_id,same_version_update_allowed,observer)	\

-    ( (This)->lpVtbl -> Update(This,app_id,same_version_update_allowed,observer) ) 

+#define IUpdater_Update(This,app_id,install_data_index,same_version_update_allowed,observer)	\

+    ( (This)->lpVtbl -> Update(This,app_id,install_data_index,same_version_update_allowed,observer) ) 

 

 #define IUpdater_UpdateAll(This,observer)	\

     ( (This)->lpVtbl -> UpdateAll(This,observer) ) 

diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl.tlb b/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl.tlb
index 584f330f..725177697 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl.tlb
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl.tlb
Binary files differ
diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl_p.c b/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl_p.c
index 770f5bb..09976330 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl_p.c
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/x64/updater_idl_p.c
@@ -47,7 +47,7 @@
 #include "updater_idl.h"

 

 #define TYPE_FORMAT_STRING_SIZE   145                               

-#define PROC_FORMAT_STRING_SIZE   727                               

+#define PROC_FORMAT_STRING_SIZE   733                               

 #define EXPR_FORMAT_STRING_SIZE   1                                 

 #define TRANSMIT_AS_TABLE_SIZE    0            

 #define WIRE_MARSHAL_TABLE_SIZE   1            

@@ -700,11 +700,11 @@
 			0x6c,		/* Old Flags:  object, Oi2 */

 /* 640 */	NdrFcLong( 0x0 ),	/* 0 */

 /* 644 */	NdrFcShort( 0x7 ),	/* 7 */

-/* 646 */	NdrFcShort( 0x28 ),	/* X64 Stack size/offset = 40 */

+/* 646 */	NdrFcShort( 0x30 ),	/* X64 Stack size/offset = 48 */

 /* 648 */	NdrFcShort( 0x8 ),	/* 8 */

 /* 650 */	NdrFcShort( 0x8 ),	/* 8 */

 /* 652 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

-			0x4,		/* 4 */

+			0x5,		/* 5 */

 /* 654 */	0xa,		/* 10 */

 			0x1,		/* Ext Flags:  new corr desc, */

 /* 656 */	NdrFcShort( 0x0 ),	/* 0 */

@@ -718,55 +718,61 @@
 /* 666 */	NdrFcShort( 0x8 ),	/* X64 Stack size/offset = 8 */

 /* 668 */	NdrFcShort( 0x58 ),	/* Type Offset=88 */

 

+	/* Parameter install_data_index */

+

+/* 670 */	NdrFcShort( 0x10b ),	/* Flags:  must size, must free, in, simple ref, */

+/* 672 */	NdrFcShort( 0x10 ),	/* X64 Stack size/offset = 16 */

+/* 674 */	NdrFcShort( 0x58 ),	/* Type Offset=88 */

+

 	/* Parameter same_version_update_allowed */

 

-/* 670 */	NdrFcShort( 0x48 ),	/* Flags:  in, base type, */

-/* 672 */	NdrFcShort( 0x10 ),	/* X64 Stack size/offset = 16 */

-/* 674 */	0x8,		/* FC_LONG */

+/* 676 */	NdrFcShort( 0x48 ),	/* Flags:  in, base type, */

+/* 678 */	NdrFcShort( 0x18 ),	/* X64 Stack size/offset = 24 */

+/* 680 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 	/* Parameter observer */

 

-/* 676 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

-/* 678 */	NdrFcShort( 0x18 ),	/* X64 Stack size/offset = 24 */

-/* 680 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

+/* 682 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

+/* 684 */	NdrFcShort( 0x20 ),	/* X64 Stack size/offset = 32 */

+/* 686 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

 

 	/* Return value */

 

-/* 682 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

-/* 684 */	NdrFcShort( 0x20 ),	/* X64 Stack size/offset = 32 */

-/* 686 */	0x8,		/* FC_LONG */

+/* 688 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

+/* 690 */	NdrFcShort( 0x28 ),	/* X64 Stack size/offset = 40 */

+/* 692 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 	/* Procedure UpdateAll */

 

-/* 688 */	0x33,		/* FC_AUTO_HANDLE */

+/* 694 */	0x33,		/* FC_AUTO_HANDLE */

 			0x6c,		/* Old Flags:  object, Oi2 */

-/* 690 */	NdrFcLong( 0x0 ),	/* 0 */

-/* 694 */	NdrFcShort( 0x8 ),	/* 8 */

-/* 696 */	NdrFcShort( 0x18 ),	/* X64 Stack size/offset = 24 */

-/* 698 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 696 */	NdrFcLong( 0x0 ),	/* 0 */

 /* 700 */	NdrFcShort( 0x8 ),	/* 8 */

-/* 702 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

+/* 702 */	NdrFcShort( 0x18 ),	/* X64 Stack size/offset = 24 */

+/* 704 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 706 */	NdrFcShort( 0x8 ),	/* 8 */

+/* 708 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

 			0x2,		/* 2 */

-/* 704 */	0xa,		/* 10 */

+/* 710 */	0xa,		/* 10 */

 			0x1,		/* Ext Flags:  new corr desc, */

-/* 706 */	NdrFcShort( 0x0 ),	/* 0 */

-/* 708 */	NdrFcShort( 0x0 ),	/* 0 */

-/* 710 */	NdrFcShort( 0x0 ),	/* 0 */

 /* 712 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 714 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 716 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 718 */	NdrFcShort( 0x0 ),	/* 0 */

 

 	/* Parameter observer */

 

-/* 714 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

-/* 716 */	NdrFcShort( 0x8 ),	/* X64 Stack size/offset = 8 */

-/* 718 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

+/* 720 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

+/* 722 */	NdrFcShort( 0x8 ),	/* X64 Stack size/offset = 8 */

+/* 724 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

 

 	/* Return value */

 

-/* 720 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

-/* 722 */	NdrFcShort( 0x10 ),	/* X64 Stack size/offset = 16 */

-/* 724 */	0x8,		/* FC_LONG */

+/* 726 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

+/* 728 */	NdrFcShort( 0x10 ),	/* X64 Stack size/offset = 16 */

+/* 730 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 			0x0

@@ -1196,7 +1202,7 @@
     532,

     600,

     638,

-    688

+    694

     };

 

 static const MIDL_STUBLESS_PROXY_INFO IUpdater_ProxyInfo =

diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl.h b/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl.h
index 699f0cf2..5770af15 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl.h
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl.h
@@ -682,6 +682,7 @@
         

         virtual HRESULT STDMETHODCALLTYPE Update( 

             /* [string][in] */ const WCHAR *app_id,

+            /* [string][in] */ const WCHAR *install_data_index,

             /* [in] */ BOOL same_version_update_allowed,

             /* [in] */ IUpdaterObserver *observer) = 0;

         

@@ -733,6 +734,7 @@
         HRESULT ( STDMETHODCALLTYPE *Update )( 

             IUpdater * This,

             /* [string][in] */ const WCHAR *app_id,

+            /* [string][in] */ const WCHAR *install_data_index,

             /* [in] */ BOOL same_version_update_allowed,

             /* [in] */ IUpdaterObserver *observer);

         

@@ -775,8 +777,8 @@
 #define IUpdater_RunPeriodicTasks(This,callback)	\

     ( (This)->lpVtbl -> RunPeriodicTasks(This,callback) ) 

 

-#define IUpdater_Update(This,app_id,same_version_update_allowed,observer)	\

-    ( (This)->lpVtbl -> Update(This,app_id,same_version_update_allowed,observer) ) 

+#define IUpdater_Update(This,app_id,install_data_index,same_version_update_allowed,observer)	\

+    ( (This)->lpVtbl -> Update(This,app_id,install_data_index,same_version_update_allowed,observer) ) 

 

 #define IUpdater_UpdateAll(This,observer)	\

     ( (This)->lpVtbl -> UpdateAll(This,observer) ) 

diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl.tlb b/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl.tlb
index 33a56d60..95c7dd6 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl.tlb
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl.tlb
Binary files differ
diff --git a/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl_p.c b/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl_p.c
index b0e6006..d7521b6b2 100644
--- a/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl_p.c
+++ b/third_party/win_build_output/midl/chrome/updater/app/server/win/x86/updater_idl_p.c
@@ -50,7 +50,7 @@
 #include "updater_idl.h"

 

 #define TYPE_FORMAT_STRING_SIZE   145                               

-#define PROC_FORMAT_STRING_SIZE   691                               

+#define PROC_FORMAT_STRING_SIZE   697                               

 #define EXPR_FORMAT_STRING_SIZE   1                                 

 #define TRANSMIT_AS_TABLE_SIZE    0            

 #define WIRE_MARSHAL_TABLE_SIZE   1            

@@ -695,11 +695,11 @@
 			0x6c,		/* Old Flags:  object, Oi2 */

 /* 608 */	NdrFcLong( 0x0 ),	/* 0 */

 /* 612 */	NdrFcShort( 0x7 ),	/* 7 */

-/* 614 */	NdrFcShort( 0x14 ),	/* x86 Stack size/offset = 20 */

+/* 614 */	NdrFcShort( 0x18 ),	/* x86 Stack size/offset = 24 */

 /* 616 */	NdrFcShort( 0x8 ),	/* 8 */

 /* 618 */	NdrFcShort( 0x8 ),	/* 8 */

 /* 620 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

-			0x4,		/* 4 */

+			0x5,		/* 5 */

 /* 622 */	0x8,		/* 8 */

 			0x1,		/* Ext Flags:  new corr desc, */

 /* 624 */	NdrFcShort( 0x0 ),	/* 0 */

@@ -712,54 +712,60 @@
 /* 632 */	NdrFcShort( 0x4 ),	/* x86 Stack size/offset = 4 */

 /* 634 */	NdrFcShort( 0x58 ),	/* Type Offset=88 */

 

+	/* Parameter install_data_index */

+

+/* 636 */	NdrFcShort( 0x10b ),	/* Flags:  must size, must free, in, simple ref, */

+/* 638 */	NdrFcShort( 0x8 ),	/* x86 Stack size/offset = 8 */

+/* 640 */	NdrFcShort( 0x58 ),	/* Type Offset=88 */

+

 	/* Parameter same_version_update_allowed */

 

-/* 636 */	NdrFcShort( 0x48 ),	/* Flags:  in, base type, */

-/* 638 */	NdrFcShort( 0x8 ),	/* x86 Stack size/offset = 8 */

-/* 640 */	0x8,		/* FC_LONG */

+/* 642 */	NdrFcShort( 0x48 ),	/* Flags:  in, base type, */

+/* 644 */	NdrFcShort( 0xc ),	/* x86 Stack size/offset = 12 */

+/* 646 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 	/* Parameter observer */

 

-/* 642 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

-/* 644 */	NdrFcShort( 0xc ),	/* x86 Stack size/offset = 12 */

-/* 646 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

+/* 648 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

+/* 650 */	NdrFcShort( 0x10 ),	/* x86 Stack size/offset = 16 */

+/* 652 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

 

 	/* Return value */

 

-/* 648 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

-/* 650 */	NdrFcShort( 0x10 ),	/* x86 Stack size/offset = 16 */

-/* 652 */	0x8,		/* FC_LONG */

+/* 654 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

+/* 656 */	NdrFcShort( 0x14 ),	/* x86 Stack size/offset = 20 */

+/* 658 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 	/* Procedure UpdateAll */

 

-/* 654 */	0x33,		/* FC_AUTO_HANDLE */

+/* 660 */	0x33,		/* FC_AUTO_HANDLE */

 			0x6c,		/* Old Flags:  object, Oi2 */

-/* 656 */	NdrFcLong( 0x0 ),	/* 0 */

-/* 660 */	NdrFcShort( 0x8 ),	/* 8 */

-/* 662 */	NdrFcShort( 0xc ),	/* x86 Stack size/offset = 12 */

-/* 664 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 662 */	NdrFcLong( 0x0 ),	/* 0 */

 /* 666 */	NdrFcShort( 0x8 ),	/* 8 */

-/* 668 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

+/* 668 */	NdrFcShort( 0xc ),	/* x86 Stack size/offset = 12 */

+/* 670 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 672 */	NdrFcShort( 0x8 ),	/* 8 */

+/* 674 */	0x46,		/* Oi2 Flags:  clt must size, has return, has ext, */

 			0x2,		/* 2 */

-/* 670 */	0x8,		/* 8 */

+/* 676 */	0x8,		/* 8 */

 			0x1,		/* Ext Flags:  new corr desc, */

-/* 672 */	NdrFcShort( 0x0 ),	/* 0 */

-/* 674 */	NdrFcShort( 0x0 ),	/* 0 */

-/* 676 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 678 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 680 */	NdrFcShort( 0x0 ),	/* 0 */

+/* 682 */	NdrFcShort( 0x0 ),	/* 0 */

 

 	/* Parameter observer */

 

-/* 678 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

-/* 680 */	NdrFcShort( 0x4 ),	/* x86 Stack size/offset = 4 */

-/* 682 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

+/* 684 */	NdrFcShort( 0xb ),	/* Flags:  must size, must free, in, */

+/* 686 */	NdrFcShort( 0x4 ),	/* x86 Stack size/offset = 4 */

+/* 688 */	NdrFcShort( 0x7e ),	/* Type Offset=126 */

 

 	/* Return value */

 

-/* 684 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

-/* 686 */	NdrFcShort( 0x8 ),	/* x86 Stack size/offset = 8 */

-/* 688 */	0x8,		/* FC_LONG */

+/* 690 */	NdrFcShort( 0x70 ),	/* Flags:  out, return, base type, */

+/* 692 */	NdrFcShort( 0x8 ),	/* x86 Stack size/offset = 8 */

+/* 694 */	0x8,		/* FC_LONG */

 			0x0,		/* 0 */

 

 			0x0

@@ -1189,7 +1195,7 @@
     504,

     570,

     606,

-    654

+    660

     };

 

 static const MIDL_STUBLESS_PROXY_INFO IUpdater_ProxyInfo =

diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index 6e450f7d..a68eb85 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -443,6 +443,8 @@
     sys.exit(1)
 
 
+# TODO(https://crbug.com/1286289): remove once Chrome targets don't rely on
+# libstdc++.so existing in the clang package.
 def CopyLibstdcpp(args, build_dir):
   if not args.gcc_toolchain:
     return
@@ -453,33 +455,8 @@
   ],
                                       universal_newlines=True).rstrip()
 
-  # Copy libstdc++.so.6 into the build dir so that the built binaries can find
-  # it. Binaries get their rpath set to $origin/../lib/. For clang, lld,
-  # etc. that live in the bin/ directory, this means they expect to find the .so
-  # in their neighbouring lib/ dir.
-  # For unit tests we pass -Wl,-rpath to the linker pointing to the lib64 dir
-  # in the gcc toolchain, via LLVM_LOCAL_RPATH below.
-  # The two fuzzer tests are weird in that they copy the fuzzer binary from bin/
-  # into the test tree under a different name. To make the relative rpath in
-  # them work, copy libstdc++ to the copied location for now.
-  # There is also a compiler-rt test that copies llvm-symbolizer out of bin/.
-  # TODO(thakis): Instead, make the upstream lit.local.cfg.py for these 2 tests
-  # check if the binary contains an rpath and if so disable the tests.
-  for d in ['lib',
-            'test/tools/llvm-isel-fuzzer/lib',
-            'test/tools/llvm-opt-fuzzer/lib']:
-    EnsureDirExists(os.path.join(build_dir, d))
-    CopyFile(libstdcpp, os.path.join(build_dir, d))
-
-  sanitizer_common_tests = os.path.join(build_dir,
-                                 'projects/compiler-rt/test/sanitizer_common')
-  if os.path.exists(sanitizer_common_tests):
-    for d in ['asan-i386-Linux', 'asan-x86_64-Linux', 'lsan-i386-Linux',
-              'lsan-x86_64-Linux', 'msan-x86_64-Linux', 'tsan-x86_64-Linux',
-              'ubsan-i386-Linux', 'ubsan-x86_64-Linux']:
-      libpath = os.path.join(sanitizer_common_tests, d, 'Output', 'lib')
-      EnsureDirExists(libpath)
-      CopyFile(libstdcpp, libpath)
+  EnsureDirExists(os.path.join(build_dir, 'lib'))
+  CopyFile(libstdcpp, os.path.join(build_dir, 'lib'))
 
 
 def gn_arg(v):
@@ -683,7 +660,16 @@
       print('Invalid --gcc-toolchain: ' + args.gcc_toolchain)
       return 1
     base_cmake_args += [
-        '-DLLVM_LOCAL_RPATH=' + os.path.join(args.gcc_toolchain, 'lib64')
+        '-DLLVM_STATIC_LINK_CXX_STDLIB=ON',
+        # Force compiler-rt tests to use our gcc toolchain
+        # because the one on the host may be too old.
+        # Even with -static-libstdc++ the compiler-rt tests add -lstdc++
+        # which adds a DT_NEEDED to libstdc++.so so we need to add RPATHs
+        # to the gcc toolchain.
+        '-DCOMPILER_RT_TEST_COMPILER_CFLAGS=--gcc-toolchain=' +
+        args.gcc_toolchain + ' -Wl,-rpath,' +
+        os.path.join(args.gcc_toolchain, 'lib64') + ' -Wl,-rpath,' +
+        os.path.join(args.gcc_toolchain, 'lib32')
     ]
 
   if sys.platform == 'darwin':
@@ -695,15 +681,6 @@
         '-DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF',
     ])
 
-  if args.gcc_toolchain:
-    # Force compiler-rt tests to use our gcc toolchain (including libstdc++.so)
-    # because the one on the host may be too old.
-    base_cmake_args.append(
-        '-DCOMPILER_RT_TEST_COMPILER_CFLAGS=--gcc-toolchain=' +
-        args.gcc_toolchain + ' -Wl,-rpath,' +
-        os.path.join(args.gcc_toolchain, 'lib64') + ' -Wl,-rpath,' +
-        os.path.join(args.gcc_toolchain, 'lib32'))
-
   if sys.platform == 'win32':
     base_cmake_args.append('-DLLVM_USE_CRT_RELEASE=MT')
     # TODO(crbug.com/1292528): We need Visual Studio 19.27 or later.
@@ -799,8 +776,6 @@
     if lld is not None: bootstrap_args.append('-DCMAKE_LINKER=' + lld)
     RunCommand(['cmake'] + bootstrap_args + [os.path.join(LLVM_DIR, 'llvm')],
                msvc_arch='x64')
-    CopyLibstdcpp(args, LLVM_BOOTSTRAP_DIR)
-    CopyLibstdcpp(args, LLVM_BOOTSTRAP_INSTALL_DIR)
     RunCommand(['ninja'], msvc_arch='x64')
     if args.run_tests:
       test_targets = ['check-all']
@@ -861,7 +836,6 @@
 
     RunCommand(['cmake'] + instrument_args + [os.path.join(LLVM_DIR, 'llvm')],
                msvc_arch='x64')
-    CopyLibstdcpp(args, LLVM_INSTRUMENTED_DIR)
     RunCommand(['ninja'], msvc_arch='x64')
     print('Instrumented compiler built.')
 
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index d22944d8..ceeeedc 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -36,7 +36,7 @@
 # Reverting problematic clang rolls is safe, though.
 # This is the output of `git describe` and is usable as a commit-ish.
 CLANG_REVISION = 'llvmorg-15-init-3677-g8133778d'
-CLANG_SUB_REVISION = 1
+CLANG_SUB_REVISION = 4
 
 PACKAGE_VERSION = '%s-%s' % (CLANG_REVISION, CLANG_SUB_REVISION)
 RELEASE_VERSION = '15.0.0'
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index f34920061..069f1c7 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -637,7 +637,9 @@
     },
 
     'chromium.rust': {
+      'linux-rust-x64-dbg': 'debug_bot_rust_linux_x64',
       'linux-rust-x64-rel': 'release_rust_linux_x64',
+      'android-rust-arm-dbg': 'debug_bot_rust_android_arm_reclient',
       'android-rust-arm-rel': 'release_rust_android_arm_reclient',
     },
 
@@ -1200,8 +1202,10 @@
     },
 
     'tryserver.chromium.rust': {
+      'linux-rust-x64-dbg': 'debug_bot_rust_linux_x64',
       'linux-rust-x64-rel': 'release_rust_linux_x64',
       'linux-rust-intree-x64-rel': 'release_rust_intree_linux_x64',
+      'android-rust-arm-dbg': 'debug_bot_rust_android_arm',
       'android-rust-arm-rel': 'release_rust_android_arm',
     },
 
@@ -2307,6 +2311,18 @@
       'debug_bot', 'paeverywhere', 'x64',
     ],
 
+    'debug_bot_rust_android_arm': [
+      'debug_bot', 'enable_rust', 'android', 'arm',
+    ],
+
+    'debug_bot_rust_android_arm_reclient': [
+      'debug_bot_reclient', 'enable_rust', 'android', 'arm',
+    ],
+
+    'debug_bot_rust_linux_x64': [
+      'debug_bot', 'enable_rust', 'x64',
+    ],
+
     'debug_bot_x86': [
       'debug_bot', 'x86',
     ],
diff --git a/tools/mb/mb_config_expectations/chromium.rust.json b/tools/mb/mb_config_expectations/chromium.rust.json
index b9ca679..d7cf8ada 100644
--- a/tools/mb/mb_config_expectations/chromium.rust.json
+++ b/tools/mb/mb_config_expectations/chromium.rust.json
@@ -1,4 +1,18 @@
 {
+  "android-rust-arm-dbg": {
+    "gn_args": {
+      "enable_rust": true,
+      "ffmpeg_branding": "Chrome",
+      "is_component_build": true,
+      "is_debug": true,
+      "proprietary_codecs": true,
+      "symbol_level": 1,
+      "target_cpu": "arm",
+      "target_os": "android",
+      "use_rbe": true,
+      "use_remoteexec": true
+    }
+  },
   "android-rust-arm-rel": {
     "gn_args": {
       "blink_enable_generated_code_formatting": false,
@@ -15,6 +29,16 @@
       "use_remoteexec": true
     }
   },
+  "linux-rust-x64-dbg": {
+    "gn_args": {
+      "enable_rust": true,
+      "is_component_build": true,
+      "is_debug": true,
+      "symbol_level": 1,
+      "target_cpu": "x64",
+      "use_goma": true
+    }
+  },
   "linux-rust-x64-rel": {
     "gn_args": {
       "blink_enable_generated_code_formatting": false,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.rust.json b/tools/mb/mb_config_expectations/tryserver.chromium.rust.json
index 707054f..bacb51c 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.rust.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.rust.json
@@ -1,4 +1,17 @@
 {
+  "android-rust-arm-dbg": {
+    "gn_args": {
+      "enable_rust": true,
+      "ffmpeg_branding": "Chrome",
+      "is_component_build": true,
+      "is_debug": true,
+      "proprietary_codecs": true,
+      "symbol_level": 1,
+      "target_cpu": "arm",
+      "target_os": "android",
+      "use_goma": true
+    }
+  },
   "android-rust-arm-rel": {
     "gn_args": {
       "blink_enable_generated_code_formatting": false,
@@ -27,6 +40,16 @@
       "use_goma": true
     }
   },
+  "linux-rust-x64-dbg": {
+    "gn_args": {
+      "enable_rust": true,
+      "is_component_build": true,
+      "is_debug": true,
+      "symbol_level": 1,
+      "target_cpu": "x64",
+      "use_goma": true
+    }
+  },
   "linux-rust-x64-rel": {
     "gn_args": {
       "blink_enable_generated_code_formatting": false,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index ea7925e..c8e8ea1 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -8619,6 +8619,7 @@
   <int value="269" label="RFH_UNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME"/>
   <int value="270"
       label="RFH_BEFOREUNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME"/>
+  <int value="271" label="MSDH_GET_OPEN_DEVICE_USE_WITHOUT_FEATURE"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
@@ -21895,6 +21896,9 @@
   <int value="7"
       label="Unable to upload new signing key to server after maximum retries
              and failed to restore old key"/>
+  <int value="8"
+      label="Unable to store the signing key due to signing key file
+             permissions failure."/>
 </enum>
 
 <enum name="DevToolsAction">
@@ -51782,6 +51786,7 @@
   <int value="-1972312724" label="OfflinePagesLoadSignalCollecting:enabled"/>
   <int value="-1972219399" label="NTPSaveToOffline:enabled"/>
   <int value="-1971086581" label="print-scaling"/>
+  <int value="-1970672551" label="CanvasContextLostInBackground:disabled"/>
   <int value="-1969636234" label="OmniboxRefinedFocusState:disabled"/>
   <int value="-1966445414" label="StylusBatteryStatus:disabled"/>
   <int value="-1965587041" label="omnibox-tab-switch-suggestions"/>
@@ -57616,6 +57621,7 @@
   <int value="2059322877" label="new-avatar-menu"/>
   <int value="2063091429" label="OfflinePagesSharing:enabled"/>
   <int value="2063420769" label="SharedHighlightingV2:disabled"/>
+  <int value="2065833330" label="CanvasContextLostInBackground:enabled"/>
   <int value="2066015302" label="QueryTiles:disabled"/>
   <int value="2066508701" label="WebViewOriginTrials:disabled"/>
   <int value="2067634730" label="LsdPermissionPrompt:disabled"/>
@@ -61379,6 +61385,8 @@
   <int value="2" label="Update Password"/>
   <int value="3" label="Save Card"/>
   <int value="4" label="Translate"/>
+  <int value="5" label="Permissions"/>
+  <int value="6" label="Autofill Save Address Profile"/>
 </enum>
 
 <enum name="MobileMessagesModalEvent">
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index a83ecc83..cd395dd4 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -102,9 +102,9 @@
   </summary>
 </histogram>
 
-<histogram name="Arc.AndroidBootTime" units="ms" expires_after="2022-04-24">
-  <owner>elijahtaylor@google.com</owner>
-  <owner>shihuis@google.com</owner>
+<histogram name="Arc.AndroidBootTime" units="ms" expires_after="2022-12-24">
+  <owner>yusukes@google.com</owner>
+  <owner>khmel@google.com</owner>
   <summary>The time elapsed for booting up the ARC instance.</summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 2e831b23..9f74e93 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -2470,17 +2470,6 @@
   </token>
 </histogram>
 
-<histogram name="Ash.NumberOfVisibleWindowsInPrimaryDisplay" units="Windows"
-    expires_after="2022-04-24">
-  <owner>jamescook@chromium.org</owner>
-  <summary>
-    An upper bound on the number of windows visible to the user on the primary
-    display. Determined by processing the windows in increasing z-order and
-    counting all non-minimized windows until a maximized or fullscreen window is
-    processed. This metric is logged periodically every 30 minutes.
-  </summary>
-</histogram>
-
 <histogram base="true" name="Ash.Overview.AnimationSmoothness.Close" units="%"
     expires_after="2023-04-10">
 <!-- Name completed by histogram_suffixes
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index c6afb73..cfbcdc5 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -7381,21 +7381,6 @@
   <affected-histogram name="PerformanceHints.Observer.HintForURLResult"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="PerformanceMonitor" separator=".">
-  <suffix name="BrowserProcess" label=""/>
-  <suffix name="GPUProcess" label=""/>
-  <suffix name="NetworkProcess" label=""/>
-  <suffix name="PluginProcess" label=""/>
-  <suffix name="PPAPIProcess" label=""/>
-  <suffix name="RendererExtensionEventProcess" label=""/>
-  <suffix name="RendererExtensionPersistentProcess" label=""/>
-  <suffix name="RendererProcess" label=""/>
-  <suffix name="Total" label=""/>
-  <suffix name="UtilityProcess" label=""/>
-  <suffix name="WorkerProcess" label=""/>
-  <affected-histogram name="PerformanceMonitor.AverageCPU"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="PermissionRequestGesture" separator=".">
   <suffix name="Gesture" label="With user gesture"/>
   <suffix name="NoGesture" label="Without user gesture"/>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index e4fbdd9b..2ec77651 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -6303,18 +6303,6 @@
   <summary>The scheme of the URL showing a JavaScript dialog.</summary>
 </histogram>
 
-<histogram name="Keyboard.ShortcutViewer.StartupTime" units="ms"
-    expires_after="2022-04-24">
-  <owner>jamescook@chromium.org</owner>
-  <owner>msw@chromium.org</owner>
-  <owner>wutao@chromium.org</owner>
-  <summary>
-    Time delay between the user gesture that triggered the keyboard shortcut
-    viewer dialog (e.g. pressing Ctrl-Alt-/) and the dialog widget being shown,
-    including layout time for the views::Views.
-  </summary>
-</histogram>
-
 <histogram name="Kiosk.Extensions.InstallDuration" units="ms"
     expires_after="2022-09-01">
   <owner>yixie@chromium.org</owner>
@@ -8755,425 +8743,6 @@
   </summary>
 </histogram>
 
-<histogram name="PerformanceMonitor.AverageCPU" units="PercentCPUUsage"
-    expires_after="2021-10-30">
-  <obsolete>
-    Replaced in 2021-03 by PerformanceMonitor.AverageCPU2, which reports this
-    value in permyriad (1/10000) instead of percent and changes the upper bound
-    of the histogram.
-  </obsolete>
-  <owner>fdoray@chromium.org</owner>
-  <owner>oysteine@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Average CPU utilization of a process, read out at each two-minute interval.
-    The utilization is in the 0-100% range per CPU, which is then summed up.
-    I.e. a quadcore system fully loaded would read as 400%.
-  </summary>
-</histogram>
-
-<histogram name="PerformanceMonitor.AverageCPU2.Total{UsageScenario}"
-    units="1/100 %" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    See definitioin of PerformanceClass.AverageCPU2.ProcessName. This is
-    recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.AverageCPU2.{ProcessName}" units="1/100 %"
-    expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Average CPU utilization of process type {ProcessName}, read out at each
-    two-minute interval. The utilization is in the 0-100% range per CPU, which
-    is then summed up and multiplied by 100. The histogram is capped at 20000
-    (equivalent to 2 cores fully loaded). I.e. 4 cores busy at 25% each will
-    read as 25 * 4 * 100 = 10000.
-
-    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
-    usage of processes that terminate before the end of the interval. This means
-    that short lived processes will rarely be included in the data. Furthermore,
-    we know that short-lived processes are very common (see
-    Renderer.ProcessLifetime). A future version of this metric will address this
-    limitation.
-  </summary>
-  <token key="ProcessName" variants="ProcessName"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.AverageDisk" units="BytesPerSecond"
-    expires_after="M85">
-  <obsolete>
-    Removed 04/2021. Not needed for current investigations.
-  </obsolete>
-  <owner>etienneb@chromium.org</owner>
-  <owner>oysteine@chromium.org</owner>
-  <summary>
-    Average disk utilization of a process, recorded at every two-minute
-    interval. The amount of data transferred (Total I/O bytes per second) for a
-    given process.
-
-    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
-    usage of processes that terminate before the end of the interval. This means
-    that short lived processes will rarely be included in the data. Furthermore,
-    we know that short-lived processes are very common (see
-    Renderer.ProcessLifetime). A future version of this metric will address this
-    limitation.
-  </summary>
-</histogram>
-
-<histogram name="PerformanceMonitor.EnergyImpact.Total{UsageScenario}"
-    units="ScaledUnits" expires_after="2022-11-30">
-  <owner>olivierli@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    See definition of PerformanceMonitor.EnergyImpact.ProcessName. This is
-    recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.EnergyImpact.{ProcessName}"
-    units="ScaledUnits" expires_after="2022-11-30">
-  <owner>olivierli@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    (Mac only) A synthetic power use estimate, as displayed in macOS Activity
-    Monitor and the battery menu. This incorporates CPU utilization, idle
-    wakeups, IO, and task QoS level using per-machine-model weights. Divide by
-    100 to match Activity Monitor's scale. Recorded every two minutes.
-
-    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
-    usage of processes that terminate before the end of the interval. This means
-    that short lived processes will rarely be included in the data. Furthermore,
-    we know that short-lived processes are very common (see
-    Renderer.ProcessLifetime). A future version of this metric will address this
-    limitation.
-  </summary>
-  <token key="ProcessName" variants="ProcessName"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.HighCPU" enum="BooleanHit"
-    expires_after="M85">
-  <obsolete>
-    Removed 04/2021. PerformanceMonitor.AverageCPU2 has the same data with more
-    granularity.
-  </obsolete>
-  <owner>oysteine@chromium.org</owner>
-  <summary>
-    The number of times a process has continuously stayed above a certain
-    threshold of CPU utilization over a certain time period (currently set to
-    two minutes).
-  </summary>
-</histogram>
-
-<histogram name="PerformanceMonitor.IdleWakeups.Total{UsageScenario}"
-    units="WakeupsPerSecond" expires_after="2022-11-30">
-  <owner>olivierli@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    See definition of PerformanceMonitor.IdleWakeups.ProcessName. This is
-    recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.IdleWakeups.{ProcessName}"
-    units="WakeupsPerSecond" expires_after="2022-11-30">
-  <owner>olivierli@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    The average CPU idle wakeups per second, sampled every two minutes.
-
-    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
-    usage of processes that terminate before the end of the interval. This means
-    that short lived processes will rarely be included in the data. Furthermore,
-    we know that short-lived processes are very common (see
-    Renderer.ProcessLifetime). A future version of this metric will address this
-    limitation.
-  </summary>
-  <token key="ProcessName" variants="ProcessName"/>
-</histogram>
-
-<histogram
-    name="PerformanceMonitor.PackageExitIdleWakeups.Total{UsageScenario}"
-    units="WakeupsPerSecond" expires_after="2022-11-30">
-  <owner>olivierli@chromium.org</owner>
-  <owner>catan-team@chromium.orgg</owner>
-  <summary>
-    See definition of PerformanceMonitor.PackageExitIdleWakeups.ProcessName.
-    This is recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.PackageExitIdleWakeups.{ProcessName}"
-    units="WakeupsPerSecond" expires_after="2022-11-30">
-  <owner>olivierli@chromium.org</owner>
-  <owner>catan-team@chromium.orgg</owner>
-  <summary>
-    (Mac only) The average package exit idle wakeups per second, sampled every
-    two minutes. This is a subset of wakeups that indicate that the processor
-    complex was taken out of low-power state. For more info, see the
-    powermetrics man page on macOS.
-
-    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
-    usage of processes that terminate before the end of the interval. This means
-    that short lived processes will rarely be included in the data. Furthermore,
-    we know that short-lived processes are very common (see
-    Renderer.ProcessLifetime). A future version of this metric will address this
-    limitation.
-  </summary>
-  <token key="ProcessName" variants="ProcessName"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.ResourceCoalition.Availability"
-    enum="CoalitionIDAvailability" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Details about whether or not it's possible to get coalition resource usage
-    data on the system. Only on macOS, recorded once at startup.
-  </summary>
-</histogram>
-
-<histogram
-    name="PerformanceMonitor.ResourceCoalition.BytesReadPerSecond2{UsageScenario}"
-    units="BytesPerSecond" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    The IO reads reported by the resource coalition mechanism on macOS. The data
-    is reported as the rate per second during this interval with a byte
-    granularity. This is recorded for {UsageScenario} (see
-    go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram
-    name="PerformanceMonitor.ResourceCoalition.BytesWrittenPerSecond2{UsageScenario}"
-    units="BytesPerSecond" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    The IO writes reported by the resource coalition mechanism on macOS. The
-    data is reported as the rate per second during this interval with a byte
-    granularity. This is recorded for {UsageScenario} (see
-    go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.ResourceCoalition.CPUTime"
-    units="hundredth of percent" expires_after="2022-11-30">
-  <obsolete>
-    Deprecated in 07/2021 because the computation was wrong. Replaced by
-    PerformanceMonitor.ResourceCoalition.CPUTime2.
-  </obsolete>
-  <owner>sebmarchand@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Average CPU utilization reported by the resource coalition mechanism on
-    macOS. Read out at each two-minute interval. The utilization is in the
-    0-100% range per CPU, which is then summed up and multiplied by 100. The
-    histogram is capped at 20000 (equivalent to 2 cores fully loaded). I.e. 4
-    cores busy at 25% each will read as 25 * 4 * 100 = 10000.
-  </summary>
-</histogram>
-
-<histogram
-    name="PerformanceMonitor.ResourceCoalition.CPUTime2_10sec{UsageScenario10sec}"
-    units="1/100 %" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Average CPU utilization reported by the resource coalition mechanism on
-    macOS. The utilization is in the 0-100% range per CPU, which is then summed
-    up and multiplied by 100. The histogram is capped at 20000 (equivalent to 2
-    cores fully loaded). I.e. 4 cores busy at 25% each will read as 25 * 4 * 100
-    = 10000. This is recorded for {UsageScenario10sec} (see
-    go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario10sec" variants="UsageScenario10sec"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.ResourceCoalition.CPUTime2{UsageScenario}"
-    units="1/100 %" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Average CPU utilization reported by the resource coalition mechanism on
-    macOS. The utilization is in the 0-100% range per CPU, which is then summed
-    up and multiplied by 100. The histogram is capped at 20000 (equivalent to 2
-    cores fully loaded). I.e. 4 cores busy at 25% each will read as 25 * 4 * 100
-    = 10000. This is recorded for {UsageScenario} (see
-    go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.ResourceCoalition.Energy"
-    units="milliwatts" expires_after="2022-11-30">
-  <obsolete>
-    Deprecated in 07/2021 because the name of the metric was wrong. Replaced by
-    PerformanceMonitor.ResourceCoalition.Power.
-  </obsolete>
-  <owner>sebmarchand@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    The energy usage reported by the resource coalition mechanism on macOS.
-    Reported every 2 minutes. Only available on devices with an ARM CPU.
-  </summary>
-</histogram>
-
-<histogram
-    name="PerformanceMonitor.ResourceCoalition.EnergyImpact{UsageScenario}"
-    units="centi-EnergyImpact" expires_after="2022-11-30">
-  <owner>siggi@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    (Mac only) A synthetic power use estimate, as displayed in macOS Activity
-    Monitor and the battery menu. This incorporates CPU utilization, idle
-    wakeups, IO, and task QoS level using per-machine-model weights. Divide by
-    100 to match Activity Monitor's scale. Only available on macs with an Intel
-    CPU.
-
-    This EnergyImpact score is computed from the usage reported by the resource
-    coalition mechanism on macOS. It accounts for the resource usage of all
-    Chrome processes no matter how short-lived, as well as XPC services running
-    on Chrome's behalf.
-
-    This is recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.ResourceCoalition.GPUTime"
-    units="hundredth of percent" expires_after="2022-11-30">
-  <obsolete>
-    Deprecated in 07/2021 because the computation was wrong. Replaced by
-    PerformanceMonitor.ResourceCoalition.GPUTime2.
-  </obsolete>
-  <owner>sebmarchand@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Average GPU utilization reported by the resource coalition mechanism on
-    macOS. Read out at each two-minute interval. The utilization is in the
-    0-100% range and is multiplied by 100. The histogram is capped at 10000
-    (equivalent to the GPU being used 100% of the time).
-  </summary>
-</histogram>
-
-<histogram name="PerformanceMonitor.ResourceCoalition.GPUTime2{UsageScenario}"
-    units="1/100 %" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Average GPU utilization reported by the resource coalition mechanism on
-    macOS. The utilization is in the 0-100% range and is multiplied by 100. The
-    histogram is capped at 10000 (equivalent to the GPU being used 100% of the
-    time). This is recorded for {UsageScenario} (see
-    go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram
-    name="PerformanceMonitor.ResourceCoalition.InterruptWakeupsPerSecond{UsageScenario}"
-    units="milliWakeupsPerSecond" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    The interrupt wakeup rate reported by the resource coalition mechanism on
-    macOS. The data is reported as the rate per second during this interval with
-    a milliwakeup granularity. This is recorded for {UsageScenario} (see
-    go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram
-    name="PerformanceMonitor.ResourceCoalition.PlatformIdleWakeupsPerSecond{UsageScenario}"
-    units="milliWakeupsPerSecond" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    The platform idle wakeup rate reported by the resource coalition mechanism
-    on macOS. The data is reported as the rate per second during this interval
-    with a milliwakeup granularity. This is recorded for {UsageScenario} (see
-    go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram name="PerformanceMonitor.ResourceCoalition.Power2{UsageScenario}"
-    units="milliwatts" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    The power usage reported by the resource coalition mechanism on macOS. Only
-    reported on devices with an ARM CPU. This is recorded for {UsageScenario}
-    (see go/chrome_power_use_per_scenario).
-  </summary>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
-<histogram
-    name="PerformanceMonitor.ResourceCoalition.QoSLevel.{QoSLevel}{UsageScenario}"
-    units="1/100 %" expires_after="2022-11-30">
-  <owner>fdoray@chromium.org</owner>
-  <owner>catan-team@chromium.org</owner>
-  <summary>
-    Average CPU time spent in a given QoS level, as reported by the resource
-    coalition mechanism on macOS. The utilization is in the 0-100% range and is
-    multiplied by 100. The histogram is capped at 10000 (equivalent to the GPU
-    being used 100% of the time). This is recorded for {UsageScenario} (see
-    go/chrome_power_use_per_scenario).
-  </summary>
-  <token key="QoSLevel">
-    <variant name="Background"/>
-    <variant name="Default"/>
-    <variant name="Legacy"/>
-    <variant name="Maintenance"/>
-    <variant name="UserInitiated"/>
-    <variant name="UserInteractive"/>
-    <variant name="Utility"/>
-  </token>
-<!-- Usage scenario variant defined in tools/metrics/histograms/metadata/power/histograms.xml  -->
-
-  <token key="UsageScenario" variants="UsageScenario"/>
-</histogram>
-
 <histogram name="PeriodicBackgroundSync.Event.BatchSize" units="events"
     expires_after="2022-07-31">
   <owner>nator@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 072b188c..a3460fe7 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -993,6 +993,9 @@
 
 <histogram name="PasswordManager.BulkCheck.LeaksFoundOnErrorOrCanceled"
     units="credentials" expires_after="2022-04-24">
+  <obsolete>
+    Removed in M101.
+  </obsolete>
   <owner>vasilii@chromium.org</owner>
   <owner>vsemeniuk@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/permissions/histograms.xml b/tools/metrics/histograms/metadata/permissions/histograms.xml
index 6f9d9813..24adaeb 100644
--- a/tools/metrics/histograms/metadata/permissions/histograms.xml
+++ b/tools/metrics/histograms/metadata/permissions/histograms.xml
@@ -137,7 +137,7 @@
 </histogram>
 
 <histogram name="Permissions.AutoBlocker.EmbargoStatus"
-    enum="PermissionEmbargoStatus" expires_after="2022-04-24">
+    enum="PermissionEmbargoStatus" expires_after="2022-10-24">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/power/histograms.xml b/tools/metrics/histograms/metadata/power/histograms.xml
index bb0f4814..600bafed 100644
--- a/tools/metrics/histograms/metadata/power/histograms.xml
+++ b/tools/metrics/histograms/metadata/power/histograms.xml
@@ -26,9 +26,6 @@
 <!--
   Variants describing the usage scenario for a 2 minutes interval. Consider
   updating UsageScenario10Sec when updating this.
-
-  Used in tools/metrics/histograms/metadata/others/histograms.xml
-  When changing this variant changes need to verified against histograms there.
 -->
 
   <variant name="" summary="2 minutes runtime intervals"/>
@@ -85,9 +82,6 @@
 <!--
   Variants describing the usage scenario for a 10 seconds intervals. Contains
   more variants than "UsageScenario".
-
-  Used in tools/metrics/histograms/metadata/others/histograms.xml
-  When changing this variant changes need to verified against histograms there.
 -->
 
   <variant name="" summary="10 seconds runtime intervals"/>
@@ -140,6 +134,345 @@
                but there was in the last 2 minutes"/>
 </variants>
 
+<histogram name="PerformanceMonitor.AverageCPU2.Total{UsageScenario}"
+    units="1/100 %" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    See definitioin of PerformanceClass.AverageCPU2.ProcessName. This is
+    recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.AverageCPU2.{ProcessName}" units="1/100 %"
+    expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    Average CPU utilization of process type {ProcessName}, read out at each
+    two-minute interval. The utilization is in the 0-100% range per CPU, which
+    is then summed up and multiplied by 100. The histogram is capped at 20000
+    (equivalent to 2 cores fully loaded). I.e. 4 cores busy at 25% each will
+    read as 25 * 4 * 100 = 10000.
+
+    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
+    usage of processes that terminate before the end of the interval. This means
+    that short lived processes will rarely be included in the data. Furthermore,
+    we know that short-lived processes are very common (see
+    Renderer.ProcessLifetime). A future version of this metric will address this
+    limitation.
+  </summary>
+  <token key="ProcessName" variants="ProcessName"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.EnergyImpact.Total{UsageScenario}"
+    units="ScaledUnits" expires_after="2022-11-30">
+  <owner>olivierli@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    See definition of PerformanceMonitor.EnergyImpact.ProcessName. This is
+    recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.EnergyImpact.{ProcessName}"
+    units="ScaledUnits" expires_after="2022-11-30">
+  <owner>olivierli@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    (Mac only) A synthetic power use estimate, as displayed in macOS Activity
+    Monitor and the battery menu. This incorporates CPU utilization, idle
+    wakeups, IO, and task QoS level using per-machine-model weights. Divide by
+    100 to match Activity Monitor's scale. Recorded every two minutes.
+
+    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
+    usage of processes that terminate before the end of the interval. This means
+    that short lived processes will rarely be included in the data. Furthermore,
+    we know that short-lived processes are very common (see
+    Renderer.ProcessLifetime). A future version of this metric will address this
+    limitation.
+  </summary>
+  <token key="ProcessName" variants="ProcessName"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.IdleWakeups.Total{UsageScenario}"
+    units="WakeupsPerSecond" expires_after="2022-11-30">
+  <owner>olivierli@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    See definition of PerformanceMonitor.IdleWakeups.ProcessName. This is
+    recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.IdleWakeups.{ProcessName}"
+    units="WakeupsPerSecond" expires_after="2022-11-30">
+  <owner>olivierli@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    The average CPU idle wakeups per second, sampled every two minutes.
+
+    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
+    usage of processes that terminate before the end of the interval. This means
+    that short lived processes will rarely be included in the data. Furthermore,
+    we know that short-lived processes are very common (see
+    Renderer.ProcessLifetime). A future version of this metric will address this
+    limitation.
+  </summary>
+  <token key="ProcessName" variants="ProcessName"/>
+</histogram>
+
+<histogram
+    name="PerformanceMonitor.PackageExitIdleWakeups.Total{UsageScenario}"
+    units="WakeupsPerSecond" expires_after="2022-11-30">
+  <owner>olivierli@chromium.org</owner>
+  <owner>catan-team@chromium.orgg</owner>
+  <summary>
+    See definition of PerformanceMonitor.PackageExitIdleWakeups.ProcessName.
+    This is recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.PackageExitIdleWakeups.{ProcessName}"
+    units="WakeupsPerSecond" expires_after="2022-11-30">
+  <owner>olivierli@chromium.org</owner>
+  <owner>catan-team@chromium.orgg</owner>
+  <summary>
+    (Mac only) The average package exit idle wakeups per second, sampled every
+    two minutes. This is a subset of wakeups that indicate that the processor
+    complex was taken out of low-power state. For more info, see the
+    powermetrics man page on macOS.
+
+    NOTE: This metric has one signigicant limitation, it doesn't report the CPU
+    usage of processes that terminate before the end of the interval. This means
+    that short lived processes will rarely be included in the data. Furthermore,
+    we know that short-lived processes are very common (see
+    Renderer.ProcessLifetime). A future version of this metric will address this
+    limitation.
+  </summary>
+  <token key="ProcessName" variants="ProcessName"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.ResourceCoalition.Availability"
+    enum="CoalitionIDAvailability" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    Details about whether or not it's possible to get coalition resource usage
+    data on the system. Only on macOS, recorded once at startup.
+  </summary>
+</histogram>
+
+<histogram
+    name="PerformanceMonitor.ResourceCoalition.BytesReadPerSecond2{UsageScenario}"
+    units="BytesPerSecond" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    The IO reads reported by the resource coalition mechanism on macOS. The data
+    is reported as the rate per second during this interval with a byte
+    granularity. This is recorded for {UsageScenario} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram
+    name="PerformanceMonitor.ResourceCoalition.BytesWrittenPerSecond2{UsageScenario}"
+    units="BytesPerSecond" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    The IO writes reported by the resource coalition mechanism on macOS. The
+    data is reported as the rate per second during this interval with a byte
+    granularity. This is recorded for {UsageScenario} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.ResourceCoalition.CPUTime"
+    units="hundredth of percent" expires_after="2022-11-30">
+  <obsolete>
+    Deprecated in 07/2021 because the computation was wrong. Replaced by
+    PerformanceMonitor.ResourceCoalition.CPUTime2.
+  </obsolete>
+  <owner>sebmarchand@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    Average CPU utilization reported by the resource coalition mechanism on
+    macOS. Read out at each two-minute interval. The utilization is in the
+    0-100% range per CPU, which is then summed up and multiplied by 100. The
+    histogram is capped at 20000 (equivalent to 2 cores fully loaded). I.e. 4
+    cores busy at 25% each will read as 25 * 4 * 100 = 10000.
+  </summary>
+</histogram>
+
+<histogram
+    name="PerformanceMonitor.ResourceCoalition.CPUTime2_10sec{UsageScenario10sec}"
+    units="1/100 %" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    Average CPU utilization reported by the resource coalition mechanism on
+    macOS. The utilization is in the 0-100% range per CPU, which is then summed
+    up and multiplied by 100. The histogram is capped at 20000 (equivalent to 2
+    cores fully loaded). I.e. 4 cores busy at 25% each will read as 25 * 4 * 100
+    = 10000. This is recorded for {UsageScenario10sec} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario10sec" variants="UsageScenario10sec"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.ResourceCoalition.CPUTime2{UsageScenario}"
+    units="1/100 %" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    Average CPU utilization reported by the resource coalition mechanism on
+    macOS. The utilization is in the 0-100% range per CPU, which is then summed
+    up and multiplied by 100. The histogram is capped at 20000 (equivalent to 2
+    cores fully loaded). I.e. 4 cores busy at 25% each will read as 25 * 4 * 100
+    = 10000. This is recorded for {UsageScenario} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.ResourceCoalition.Energy"
+    units="milliwatts" expires_after="2022-11-30">
+  <obsolete>
+    Deprecated in 07/2021 because the name of the metric was wrong. Replaced by
+    PerformanceMonitor.ResourceCoalition.Power.
+  </obsolete>
+  <owner>sebmarchand@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    The energy usage reported by the resource coalition mechanism on macOS.
+    Reported every 2 minutes. Only available on devices with an ARM CPU.
+  </summary>
+</histogram>
+
+<histogram
+    name="PerformanceMonitor.ResourceCoalition.EnergyImpact{UsageScenario}"
+    units="centi-EnergyImpact" expires_after="2022-11-30">
+  <owner>siggi@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    (Mac only) A synthetic power use estimate, as displayed in macOS Activity
+    Monitor and the battery menu. This incorporates CPU utilization, idle
+    wakeups, IO, and task QoS level using per-machine-model weights. Divide by
+    100 to match Activity Monitor's scale. Only available on macs with an Intel
+    CPU.
+
+    This EnergyImpact score is computed from the usage reported by the resource
+    coalition mechanism on macOS. It accounts for the resource usage of all
+    Chrome processes no matter how short-lived, as well as XPC services running
+    on Chrome's behalf.
+
+    This is recorded for {UsageScenario} (see go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.ResourceCoalition.GPUTime"
+    units="hundredth of percent" expires_after="2022-11-30">
+  <obsolete>
+    Deprecated in 07/2021 because the computation was wrong. Replaced by
+    PerformanceMonitor.ResourceCoalition.GPUTime2.
+  </obsolete>
+  <owner>sebmarchand@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    Average GPU utilization reported by the resource coalition mechanism on
+    macOS. Read out at each two-minute interval. The utilization is in the
+    0-100% range and is multiplied by 100. The histogram is capped at 10000
+    (equivalent to the GPU being used 100% of the time).
+  </summary>
+</histogram>
+
+<histogram name="PerformanceMonitor.ResourceCoalition.GPUTime2{UsageScenario}"
+    units="1/100 %" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    Average GPU utilization reported by the resource coalition mechanism on
+    macOS. The utilization is in the 0-100% range and is multiplied by 100. The
+    histogram is capped at 10000 (equivalent to the GPU being used 100% of the
+    time). This is recorded for {UsageScenario} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram
+    name="PerformanceMonitor.ResourceCoalition.InterruptWakeupsPerSecond{UsageScenario}"
+    units="milliWakeupsPerSecond" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    The interrupt wakeup rate reported by the resource coalition mechanism on
+    macOS. The data is reported as the rate per second during this interval with
+    a milliwakeup granularity. This is recorded for {UsageScenario} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram
+    name="PerformanceMonitor.ResourceCoalition.PlatformIdleWakeupsPerSecond{UsageScenario}"
+    units="milliWakeupsPerSecond" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    The platform idle wakeup rate reported by the resource coalition mechanism
+    on macOS. The data is reported as the rate per second during this interval
+    with a milliwakeup granularity. This is recorded for {UsageScenario} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram name="PerformanceMonitor.ResourceCoalition.Power2{UsageScenario}"
+    units="milliwatts" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    The power usage reported by the resource coalition mechanism on macOS. Only
+    reported on devices with an ARM CPU. This is recorded for {UsageScenario}
+    (see go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
+<histogram
+    name="PerformanceMonitor.ResourceCoalition.QoSLevel.{QoSLevel}{UsageScenario}"
+    units="1/100 %" expires_after="2022-11-30">
+  <owner>fdoray@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    Average CPU time spent in a given QoS level, as reported by the resource
+    coalition mechanism on macOS. The utilization is in the 0-100% range and is
+    multiplied by 100. The histogram is capped at 10000 (equivalent to the GPU
+    being used 100% of the time). This is recorded for {UsageScenario} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="QoSLevel">
+    <variant name="Background"/>
+    <variant name="Default"/>
+    <variant name="Legacy"/>
+    <variant name="Maintenance"/>
+    <variant name="UserInitiated"/>
+    <variant name="UserInteractive"/>
+    <variant name="Utility"/>
+  </token>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
 <histogram name="Power.ApproxCpuTimeSecondsPerCoreTypeAndFrequency"
     units="50 MHz" expires_after="2022-08-28">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 8b3e159..0c44681 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -548,7 +548,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.DeepScan.Paste.BytesPerSeconds" units="bytes"
-    expires_after="2022-04-24">
+    expires_after="2023-02-01">
   <owner>domfc@chromium.org</owner>
   <owner>webprotect-team@google.com</owner>
   <summary>
@@ -570,7 +570,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.DeepScan.Paste.Duration" units="ms"
-    expires_after="2022-05-01">
+    expires_after="2023-02-01">
   <owner>domfc@chromium.org</owner>
   <owner>webprotect-team@google.com</owner>
   <summary>
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index 242f841..bf8865b4 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -661,7 +661,8 @@
                                       _FUCHSIA_ATLAS_PERF_FYI_BENCHMARK_CONFIGS,
                                       1,
                                       'fuchsia',
-                                      is_fyi=True)
+                                      is_fyi=True,
+                                      executables=FUCHSIA_EXEC_CONFIGS['atlas'])
 
 # Calibration bots
 LINUX_PERF_CALIBRATION = PerfPlatform(
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 281eeda..fa1b597a 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,16 +5,16 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "6a87f3d6ab9205abf31d28f5367b04a7bc89217e",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/bce4286eebf41c7e7f6689684dea989c0b010fe1/trace_processor_shell.exe"
+            "hash": "7da27714c4ea778f83e3b05cd083ae5e7cace7a2",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/981be5ab5ba9674eded95ed5493811a061d7ed2a/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "mac": {
-            "hash": "b7b68e705f9e64650b588326af40c038a6a33f5b",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/bce4286eebf41c7e7f6689684dea989c0b010fe1/trace_processor_shell"
+            "hash": "5520efee30c243deda3278e696dc2b15f1f591a5",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/bc7a9c2bb682f97e2c5666bf19ec0a742c26ae26/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "c0397e87456ad6c6a7aa0133e5b81c97adbab4ab",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "182bc90682ca7a54b10ae45fdd6a7a40e17a2334",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/981be5ab5ba9674eded95ed5493811a061d7ed2a/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/06dab69bb9dcb7063a20820348d47cb60c9fc970/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json b/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
index 130d400..8f95e74 100644
--- a/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
+++ b/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
@@ -4,14 +4,26 @@
             "system_health.common_desktop": {
                 "abridged": false
             }
+        },
+        "executables": {
+            "base_perftests": {
+                "path": "bin/run_base_perftests",
+                "arguments": [
+                    "--test-launcher-jobs=1",
+                    "--test-launcher-retry-limit=0",
+                    "-d",
+                    "--os-check=update",
+                    "--system-image-dir=../../third_party/fuchsia-sdk/images-internal/chromebook-x64-release/sucrose_eng"
+                ]
+            }
         }
     },
     "extra_infos": {
-        "num_stories": 82,
-        "predicted_min_shard_time": 879.0,
+        "num_stories": 83,
+        "predicted_min_shard_time": 4033.0,
         "predicted_min_shard_index": 0,
-        "predicted_max_shard_time": 879.0,
+        "predicted_max_shard_time": 4033.0,
         "predicted_max_shard_index": 0,
-        "shard #0": 879.0
+        "shard #0": 4033.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/timing_data/fuchsia-perf-atlas-fyi_timing.json b/tools/perf/core/shard_maps/timing_data/fuchsia-perf-atlas-fyi_timing.json
index c3c17d8..e177bf7f 100644
--- a/tools/perf/core/shard_maps/timing_data/fuchsia-perf-atlas-fyi_timing.json
+++ b/tools/perf/core/shard_maps/timing_data/fuchsia-perf-atlas-fyi_timing.json
@@ -1,62 +1,334 @@
 [
     {
-        "duration": "3.0",
+        "duration": "39.0",
+        "name": "system_health.common_desktop/browse:media:googleplaystore:2021"
+    },
+    {
+        "duration": "77.0",
+        "name": "system_health.common_desktop/browse:media:imgur"
+    },
+    {
+        "duration": "96.0",
+        "name": "system_health.common_desktop/browse:media:pinterest:2018"
+    },
+    {
+        "duration": "65.0",
+        "name": "system_health.common_desktop/browse:media:tumblr:2018"
+    },
+    {
+        "duration": "9.0",
         "name": "system_health.common_desktop/browse:media:youtube:2019"
     },
     {
-        "duration": "3.0",
+        "duration": "72.0",
+        "name": "system_health.common_desktop/browse:media:youtubetv:2019"
+    },
+    {
+        "duration": "78.0",
+        "name": "system_health.common_desktop/browse:media:youtubetv_watch:2020"
+    },
+    {
+        "duration": "55.0",
+        "name": "system_health.common_desktop/browse:news:cnn:2021"
+    },
+    {
+        "duration": "55.0",
+        "name": "system_health.common_desktop/browse:news:flipboard:2020"
+    },
+    {
+        "duration": "60.0",
+        "name": "system_health.common_desktop/browse:news:hackernews:2020"
+    },
+    {
+        "duration": "79.0",
+        "name": "system_health.common_desktop/browse:news:nytimes:2020"
+    },
+    {
+        "duration": "69.0",
+        "name": "system_health.common_desktop/browse:news:reddit:2020"
+    },
+    {
+        "duration": "53.0",
+        "name": "system_health.common_desktop/browse:search:google:2020"
+    },
+    {
+        "duration": "38.0",
+        "name": "system_health.common_desktop/browse:search:google_india:2021"
+    },
+    {
+        "duration": "72.0",
+        "name": "system_health.common_desktop/browse:social:facebook_infinite_scroll:2018"
+    },
+    {
+        "duration": "62.0",
+        "name": "system_health.common_desktop/browse:social:tumblr_infinite_scroll:2018"
+    },
+    {
+        "duration": "56.0",
+        "name": "system_health.common_desktop/browse:social:twitter:2018"
+    },
+    {
+        "duration": "68.0",
+        "name": "system_health.common_desktop/browse:social:twitter_infinite_scroll:2018"
+    },
+    {
+        "duration": "63.0",
+        "name": "system_health.common_desktop/browse:tech:discourse_infinite_scroll:2018"
+    },
+    {
+        "duration": "84.0",
+        "name": "system_health.common_desktop/browse:tools:autocad:2021"
+    },
+    {
+        "duration": "43.0",
+        "name": "system_health.common_desktop/browse:tools:docs_scrolling"
+    },
+    {
+        "duration": "9.0",
         "name": "system_health.common_desktop/browse:tools:earth:2020"
     },
     {
-        "duration": "3.0",
+        "duration": "10.0",
         "name": "system_health.common_desktop/browse:tools:gmail-compose:2020"
     },
     {
-        "duration": "3.0",
+        "duration": "9.0",
         "name": "system_health.common_desktop/browse:tools:gmail-labelclick:2020"
     },
     {
-        "duration": "3.0",
+        "duration": "41.0",
+        "name": "system_health.common_desktop/browse:tools:gmail-openconversation:2020"
+    },
+    {
+        "duration": "9.0",
         "name": "system_health.common_desktop/browse:tools:gmail-search:2020"
     },
     {
-        "duration": "3.0",
+        "duration": "80.0",
+        "name": "system_health.common_desktop/browse:tools:maps:2019"
+    },
+    {
+        "duration": "9.0",
         "name": "system_health.common_desktop/browse:tools:photoshop:2021"
     },
     {
-        "duration": "3.0",
+        "duration": "9.0",
         "name": "system_health.common_desktop/browse:tools:photoshop_warm:2021"
     },
     {
-        "duration": "3.0",
+        "duration": "9.0",
         "name": "system_health.common_desktop/browse:tools:sheets:2019"
     },
     {
-        "duration": "3.0",
+        "duration": "10.0",
         "name": "system_health.common_desktop/browse_accessibility:media:youtube"
     },
     {
+        "duration": "33.0",
+        "name": "system_health.common_desktop/browse_accessibility:tech:codesearch:2018"
+    },
+    {
         "duration": "23.0",
+        "name": "system_health.common_desktop/load:chrome:blank"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:games:alphabetty:2018"
+    },
+    {
+        "duration": "23.0",
+        "name": "system_health.common_desktop/load:games:bubbles:2020"
+    },
+    {
+        "duration": "22.0",
+        "name": "system_health.common_desktop/load:games:lazors"
+    },
+    {
+        "duration": "26.0",
+        "name": "system_health.common_desktop/load:games:miniclip:2018"
+    },
+    {
+        "duration": "28.0",
+        "name": "system_health.common_desktop/load:games:spychase:2018"
+    },
+    {
+        "duration": "26.0",
         "name": "system_health.common_desktop/load:media:9gag"
     },
     {
-        "duration": "19.0",
+        "duration": "23.0",
+        "name": "system_health.common_desktop/load:media:dailymotion:2019"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:media:facebook_feed:desktop:2020"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:media:facebook_photos:2018"
+    },
+    {
+        "duration": "27.0",
+        "name": "system_health.common_desktop/load:media:facebook_photos:desktop:2020"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:media:flickr:2018"
+    },
+    {
+        "duration": "23.0",
+        "name": "system_health.common_desktop/load:media:google_images:2018"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:media:imgur:2018"
+    },
+    {
+        "duration": "26.0",
+        "name": "system_health.common_desktop/load:media:soundcloud:2018"
+    },
+    {
+        "duration": "24.0",
+        "name": "system_health.common_desktop/load:media:youtube:2018"
+    },
+    {
+        "duration": "22.0",
+        "name": "system_health.common_desktop/load:media:youtubelivingroom:2020"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:news:bbc:2018"
+    },
+    {
+        "duration": "26.0",
+        "name": "system_health.common_desktop/load:news:cnn:2020"
+    },
+    {
+        "duration": "23.0",
         "name": "system_health.common_desktop/load:news:flipboard"
     },
     {
-        "duration": "3.0",
+        "duration": "22.0",
+        "name": "system_health.common_desktop/load:news:hackernews:2018"
+    },
+    {
+        "duration": "28.0",
+        "name": "system_health.common_desktop/load:news:nytimes:2018"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:news:qq:2018"
+    },
+    {
+        "duration": "27.0",
+        "name": "system_health.common_desktop/load:news:reddit:2018"
+    },
+    {
+        "duration": "24.0",
+        "name": "system_health.common_desktop/load:news:wikipedia:2018"
+    },
+    {
+        "duration": "24.0",
+        "name": "system_health.common_desktop/load:search:amazon:2018"
+    },
+    {
+        "duration": "24.0",
+        "name": "system_health.common_desktop/load:search:baidu:2018"
+    },
+    {
+        "duration": "23.0",
+        "name": "system_health.common_desktop/load:search:ebay:2018"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:search:flipkart:2018"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:search:google:2018"
+    },
+    {
+        "duration": "24.0",
+        "name": "system_health.common_desktop/load:search:taobao:2018"
+    },
+    {
+        "duration": "23.0",
+        "name": "system_health.common_desktop/load:search:yahoo:2018"
+    },
+    {
+        "duration": "24.0",
+        "name": "system_health.common_desktop/load:search:yandex:2018"
+    },
+    {
+        "duration": "24.0",
+        "name": "system_health.common_desktop/load:social:instagram:2018"
+    },
+    {
+        "duration": "26.0",
+        "name": "system_health.common_desktop/load:social:pinterest:2019"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:social:vk:2018"
+    },
+    {
+        "duration": "9.0",
         "name": "system_health.common_desktop/load:tools:chat:2020"
     },
     {
-        "duration": "3.0",
+        "duration": "76.0",
+        "name": "system_health.common_desktop/load:tools:docs:2019"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:tools:drive:2019"
+    },
+    {
+        "duration": "10.0",
         "name": "system_health.common_desktop/load:tools:gmail:2019"
     },
     {
-        "duration": "131.0",
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:tools:stackoverflow:2018"
+    },
+    {
+        "duration": "25.0",
+        "name": "system_health.common_desktop/load:tools:weather:2019"
+    },
+    {
+        "duration": "26.0",
+        "name": "system_health.common_desktop/load_accessibility:media:wikipedia:2018"
+    },
+    {
+        "duration": "24.0",
+        "name": "system_health.common_desktop/load_accessibility:shopping:amazon:2018"
+    },
+    {
+        "duration": "136.0",
+        "name": "system_health.common_desktop/long_running:tools:gmail-background"
+    },
+    {
+        "duration": "136.0",
         "name": "system_health.common_desktop/long_running:tools:gmail-foreground"
     },
     {
-        "duration": "3.0",
+        "duration": "10.0",
         "name": "system_health.common_desktop/multitab:misc:typical24"
+    },
+    {
+        "duration": "126.0",
+        "name": "system_health.common_desktop/multitab:misc:typical24:2018"
+    },
+    {
+        "duration": "49.0",
+        "name": "system_health.common_desktop/play:media:google_play_music"
+    },
+    {
+        "duration": "50.0",
+        "name": "system_health.common_desktop/play:media:soundcloud:2018"
+    },
+    {
+        "duration": "900.0",
+        "name": "base_perftests/_gtest_"
     }
 ]
\ No newline at end of file
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index c440a3fc..020c874 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -2,10 +2,10 @@
 # Instructions of how to use this file:
 # https://chromium.googlesource.com/chromium/src/+/main/docs/speed/bot_health_sheriffing/how_to_disable_a_story.md
 # tags: [ android chromeos chromeos-local chromeos-remote
-#         linux mac mac-10.12 mac-10.13 mac-arm64 mac-x86_64 win win10 win7 win-laptop
+#         linux mac mac-10.12 mac-10.13 mac-arm64 mac-x86_64 win win10 win-laptop
 #         sierra highsierra android-marshmallow android-lollipop android-nougat android-oreo android-pie android-10
 #         android-kitkat ubuntu fuchsia fuchsia-board-astro fuchsia-board-sherlock fuchsia-chrome ]
-# tags: [ android-go android-low-end android-nexus-5 android-nexus-5x android-nexus-6
+# tags: [ android-go android-low-end android-nexus-6
 #         android-pixel-2 android-pixel-4 desktop mobile android-pixel-4a ]
 # tags: [ android-not-webview android-webview android-webview-google android-chromium
 #         reference release debug exact release-x64 debug-x64 android-weblayer ]
@@ -13,81 +13,38 @@
 # conflicts_allowed: True
 
 # Benchmark: blink_perf.accessibility
-crbug.com/1114112 [ android-nexus-5x android-webview ] blink_perf.accessibility/build-table.html [ Skip ]
-crbug.com/1226849 [ android-nexus-5x android-webview ] blink_perf.accessibility/line-breaks.html [ Skip ]
 crbug.com/1234000 [ android-pixel-2 ] blink_perf.accessibility/line-breaks.html [ Skip ]
-crbug.com/1266142 [ android-nexus-5x android-webview ] blink_perf.accessibility/many-nodes-toggle-content-visibility-auto.html [ Skip ]
-crbug.com/1266142 [ android-nexus-5x android-webview ] blink_perf.accessibility/many-nodes-toggle-content-visibility-hidden.html [ Skip ]
-crbug.com/1290813 [ android-nexus-5x ] blink_perf.accessibility/many-nodes-toggle-display-none.html [ Skip ]
 
 # Benchmark: blink_perf.bindings
-crbug.com/882881 [ android-nexus-5 ] blink_perf.bindings/structured-clone-json-deserialize.html [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] blink_perf.bindings/structured-clone-json-deserialize.html [ Skip ]
-crbug.com/882881 [ android-nexus-5 ] blink_perf.bindings/structured-clone-json-serialize.html [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] blink_perf.bindings/structured-clone-json-serialize.html [ Skip ]
-crbug.com/882881 [ android-nexus-5 ] blink_perf.bindings/structured-clone-long-string-deserialize.html [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] blink_perf.bindings/structured-clone-long-string-deserialize.html [ Skip ]
 crbug.com/931780 [ android-webview ] blink_perf.bindings/structured-clone-long-string-serialize.html [ Skip ]
 crbug.com/893209 [ android-webview ] blink_perf.bindings/structured-clone-long-string-deserialize.html [ Skip ]
-crbug.com/882881 [ android-nexus-5 ] blink_perf.bindings/structured-clone-long-string-serialize.html [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] blink_perf.bindings/structured-clone-long-string-serialize.html [ Skip ]
-crbug.com/882881 [ android-nexus-5 ] blink_perf.bindings/worker-structured-clone-json-roundtrip.html [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] blink_perf.bindings/worker-structured-clone-json-roundtrip.html [ Skip ]
-crbug.com/882881 [ android-nexus-5 ] blink_perf.bindings/worker-structured-clone-json-to-worker.html [ Skip ]
-crbug.com/882881 [ android-nexus-5x ] blink_perf.bindings/worker-structured-clone-json-to-worker.html [ Skip ]
-crbug.com/882881 [ android-nexus-5 ] blink_perf.bindings/worker-structured-clone-json-from-worker.html [ Skip ]
-crbug.com/882881 [ android-nexus-5x ] blink_perf.bindings/worker-structured-clone-json-from-worker.html [ Skip ]
 crbug.com/865400 [ android-pixel-2 ] blink_perf.bindings/structured-clone-long-string-deserialize.html [ Skip ]
 crbug.com/865400 [ android-pixel-2 ] blink_perf.bindings/structured-clone-long-string-serialize.html [ Skip ]
 
-# Benchmark: blink_perf.css
-crbug.com/891878 [ android-nexus-5x android-webview ] blink_perf.css/CustomPropertiesVarAlias.html [ Skip ]
-
 # Benchmark: blink_perf.events
-crbug.com/963945 [ android-nexus-5x android-webview ] blink_perf.events/EventsDispatchingInDeeplyNestedV1ShadowTrees.html [ Skip ]
-crbug.com/963945 [ android-nexus-5x android-webview ] blink_perf.events/EventsDispatchingInV1ShadowTrees.html [ Skip ]
 crbug.com/963945 [ android-pixel-2 ] blink_perf.events/EventsDispatchingInDeeplyNestedV1ShadowTrees.html [ Skip ]
 crbug.com/963945 [ android-pixel-2 ] blink_perf.events/EventsDispatchingInV1ShadowTrees.html [ Skip ]
 
 # Benchmark: blink_perf.layout
 crbug.com/551950 [ android-low-end ] blink_perf.layout/* [ Skip ]
-crbug.com/832686 [ android-nexus-5 ] blink_perf.layout/subtree-detaching.html [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] blink_perf.layout/subtree-detaching.html [ Skip ]
 crbug.com/966921 [ android ] blink_perf.layout/line-layout-fit-content.html [ Skip ]
 crbug.com/966921 [ android ] blink_perf.layout/line-layout-fit-content-break-word.html [ Skip ]
 
 # Benchmark: blink_perf.paint
 crbug.com/574483 [ android-low-end ] blink_perf.paint/* [ Skip ]
-crbug.com/799540 [ android-nexus-5 ] blink_perf.paint/* [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] blink_perf.paint/* [ Skip ]
 crbug.com/859979 [ android-webview ] blink_perf.paint/paint-offset-changes.html [ Skip ]
 crbug.com/901493 [ android-nexus-6 android-webview ] blink_perf.paint/* [ Skip ]
 crbug.com/1000460 [ android-pixel-2 ] blink_perf.paint/color-changes.html [ Skip ]
 
 # Benchmark: blink_perf.parser
-crbug.com/966913 [ android-nexus-5x android-webview ] blink_perf.parser/query-selector-all-class-deep.html [ Skip ]
-crbug.com/966913 [ android-nexus-5x android-webview ] blink_perf.parser/query-selector-all-deep.html [ Skip ]
-crbug.com/966913 [ android-nexus-5x android-webview ] blink_perf.parser/query-selector-all-id-deep.html [ Skip ]
 crbug.com/966613 [ android-pixel-2 ] blink_perf.parser/query-selector-all-class-deep.html [ Skip ]
 crbug.com/966613 [ android-pixel-2 ] blink_perf.parser/query-selector-all-deep.html [ Skip ]
 crbug.com/966613 [ android-pixel-2 ] blink_perf.parser/query-selector-all-id-deep.html [ Skip ]
 
-# Benchmark: blink_perf.shadow_dom
-crbug.com/702319 [ android-nexus-5x ] blink_perf.shadow_dom/* [ Skip ]
-
 # Benchmark: blink_perf.svg
-crbug.com/736817 [ android-nexus-5x ] blink_perf.svg/SvgCubics.html [ Skip ]
-crbug.com/736817 [ android-nexus-5x ] blink_perf.svg/Debian.html [ Skip ]
-crbug.com/736817 [ android-nexus-5x ] blink_perf.svg/HarveyRayner.html [ Skip ]
-crbug.com/736817 [ android-nexus-5x ] blink_perf.svg/CrawFishGanson.html [ Skip ]
-crbug.com/736817 [ android-nexus-5x ] blink_perf.svg/Worldcup.html [ Skip ]
-crbug.com/736817 [ android-nexus-5x ] blink_perf.svg/FlowerFromMyGarden.html [ Skip ]
-crbug.com/736817 [ android-nexus-5x ] blink_perf.svg/SvgNestedUse.html [ Skip ]
 crbug.com/846061 [ win ] blink_perf.svg/SierpinskiCarpet.html [ Skip ]
 crbug.com/846061 [ mac ] blink_perf.svg/SierpinskiCarpet.html [ Skip ]
 crbug.com/850293 blink_perf.svg/Cowboy_transform.html [ Skip ]
-crbug.com/894147 [ android-nexus-5 ] blink_perf.svg/SierpinskiCarpet.html [ Skip ]
-crbug.com/894147 [ android-nexus-5x ] blink_perf.svg/SierpinskiCarpet.html [ Skip ]
 crbug.com/1023792 [ android ] blink_perf.svg/SierpinskiCarpet.html [ Skip ]
 
 # Benchmark: desktop_ui
@@ -105,7 +62,6 @@
 crbug.com/1050065 [ android-pixel-2 ] dromaeo/http://dromaeo.com?dom-modify [ Skip ]
 
 # Benchmark: jetstream
-crbug.com/830600 [ android-nexus-5x android-webview ] jetstream/JetStream [ Skip ]
 crbug.com/1009843 [ android ] jetstream/JetStream [ Skip ]
 
 # Benchmark: loading.desktop
@@ -117,7 +73,6 @@
 crbug.com/876636 [ chromeos ] loading.desktop/TheOnion_cold [ Skip ]
 crbug.com/876636 [ chromeos ] loading.desktop/TheOnion_warm [ Skip ]
 crbug.com/876636 [ win10 ] loading.desktop/TheOnion_warm [ Skip ]
-crbug.com/876636 [ win7 ] loading.desktop/TheOnion_warm [ Skip ]
 crbug.com/876636 [ chromeos ] loading.desktop/AllRecipes_cold [ Skip ]
 crbug.com/876636 [ chromeos ] loading.desktop/AllRecipes_warm [ Skip ]
 crbug.com/879833 [ mac-10.12 ] loading.desktop/AllRecipes_cold [ Skip ]
@@ -129,10 +84,6 @@
 
 # Benchmark: loading.mobile
 crbug.com/656861 loading.mobile/G1 [ Skip ]
-crbug.com/857108 [ android-nexus-5 ] loading.mobile/G1_3g [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] loading.mobile/G1_3g [ Skip ]
-[ android-nexus-5x ] loading.mobile/Hongkiat [ Skip ]
-[ android-nexus-5x ] loading.mobile/Dramaq [ Skip ]
 crbug.com/859597 loading.mobile/Bradesco_3g [ Skip ]
 crbug.com/859597 loading.mobile/Dailymotion_3g [ Skip ]
 crbug.com/859597 loading.mobile/Dawn_3g [ Skip ]
@@ -151,24 +102,6 @@
 crbug.com/942631 loading.mobile/VoiceMemos_warm [ Skip ]
 crbug.com/942631 loading.mobile/VoiceMemos_hot_3g [ Skip ]
 crbug.com/942631 loading.mobile/VoiceMemos_warm_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5 ] loading.mobile/FlipBoard_cold_3g [ Skip ]
-crbug.com/862663 [ android-nexus-5 ] loading.mobile/GoogleBrazil_3g [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] loading.mobile/GoogleBrazil_3g [ Skip ]
-crbug.com/862663 [ android-nexus-5 ] loading.mobile/GoogleIndonesia_3g [ Skip ]
-crbug.com/862663 [ android-nexus-5 ] loading.mobile/GoogleRedirectToGoogleJapan_3g [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] loading.mobile/GoogleRedirectToGoogleJapan_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x ] loading.mobile/FlipBoard_cold_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5 ] loading.mobile/Hongkiat_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x ] loading.mobile/Hongkiat_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x android-webview ] loading.mobile/Hongkiat_3g [ Skip ]
-crbug.com/896088 [ android-nexus-5 ] loading.mobile/TribunNews_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x ] loading.mobile/TribunNews_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x android-webview ] loading.mobile/TribunNews_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x ] loading.mobile/GoogleIndonesia_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x ] loading.mobile/QQNews_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x ] loading.mobile/Youtube_3g [ Skip ]
-crbug.com/859597 [ android-nexus-5x android-webview ] loading.mobile/GoogleBrazil_3g [ Skip ]
-crbug.com/867103 [ android-nexus-5x android-webview ] loading.mobile/FlipBoard_warm_3g [ Skip ]
 crbug.com/873032 [ android-webview ] loading.mobile/GoogleRedirectToGoogleJapan_3g [ Skip ]
 crbug.com/873033 [ android-not-webview ] loading.mobile/FlipKart_warm_3g [ Skip ]
 crbug.com/676612 [ android-nexus-6 android-webview ] loading.mobile/FlipBoard_cold_3g [ Skip ]
@@ -194,7 +127,6 @@
 crbug.com/865400 [ android-pixel-2 android-webview ] loading.mobile/Hongkiat_3g [ Skip ]
 crbug.com/865400 [ android-pixel-2 android-webview ] loading.mobile/OLX_3g [ Skip ]
 crbug.com/865400 [ android-pixel-2 android-webview ] loading.mobile/VoiceMemos_cold_3g [ Skip ]
-crbug.com/919191 [ android-nexus-5x android-webview ] loading.mobile/OLX_3g [ Skip ]
 
 # Benchmark: media.desktop
 crbug.com/1114681 [ win-laptop ] media.desktop/video.html?src=smpte_3840x2160_60fps_vp9.webm&seek [ Skip ]
@@ -211,7 +143,6 @@
 crbug.com/1202988 [ fuchsia-board-sherlock ] media.mobile/video.html?src=foodmarket_720p30fps.mp4 [ Skip ]
 
 # Benchmark: power.desktop
-crbug.com/1036360 [ win7 ] power.desktop/abcnews [ Skip ]
 crbug.com/1082094 [ win10 ] power.desktop/abcnews [ Skip ]
 
 # Benchmark: power.mobile
@@ -238,11 +169,8 @@
 crbug.com/785485 [ android-webview ] rendering.mobile/kevs_3d [ Skip ]
 crbug.com/785286 [ android-webview ] rendering.mobile/smash_cat [ Skip ]
 crbug.com/785286 [ android-webview ] rendering.mobile/effect_games [ Skip ]
-crbug.com/364248 [ android-nexus-5 ] rendering.mobile/geo_apis [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] rendering.mobile/geo_apis [ Skip ]
 crbug.com/825234 [ android-webview ] rendering.mobile/bouncing_balls_shadow [ Skip ]
 crbug.com/755556 [ android ] rendering.mobile/balls_css_key_frame_animations_composited_transform [ Skip ]
-crbug.com/840964 [ android-nexus-5x ] rendering.mobile/web_animations_many_keyframes [ Skip ]
 crbug.com/653993 [ android-webview ] rendering.mobile/maps_perf_test [ Skip ]
 crbug.com/785473 [ android-webview ] rendering.mobile/canvas_20000_pixels_per_second [ Skip ]
 crbug.com/785473 [ android-webview ] rendering.mobile/canvas_40000_pixels_per_second [ Skip ]
@@ -254,22 +182,14 @@
 crbug.com/461127 rendering.mobile/famo_us_twitter_demo [ Skip ]
 crbug.com/850295 rendering.mobile/aquarium_20k [ Skip ]
 crbug.com/873013 [ android-webview ] rendering.mobile/yahoo_answers_mobile_2018 [ Skip ]
-crbug.com/893197 [ android-nexus-5x ] rendering.mobile/yahoo_answers_mobile_2018 [ Skip ]
 crbug.com/865400 [ android-pixel-2 ] rendering.mobile/yahoo_answers_mobile_2018 [ Skip ]
-crbug.com/874935 [ android-nexus-5 ] rendering.mobile/yahoo_news_2018 [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] rendering.mobile/yahoo_news_2018 [ Skip ]
 crbug.com/901526 rendering.mobile/microsoft_fireflies [ Skip ]
 crbug.com/924400 [ android-nexus-6 android-webview ] rendering.mobile/canvas_animation_no_clear [ Skip ]
-crbug.com/949366 [ android-nexus-5x android-webview ] rendering.mobile/google_news_mobile_2018 [ Skip ]
 crbug.com/954948 [ android-webview ] rendering.mobile/cnn_mobile_pinch_2018 [ Skip ]
 crbug.com/966637 [ android-pixel-2 android-webview ] rendering.mobile/google_news_mobile_2018 [ Skip ]
 crbug.com/967809 [ android-webview ] rendering.mobile/microsoft_video_city [ Skip ]
 crbug.com/1017244 [ mobile ] rendering.mobile/youtube_2018 [ Skip ]
-crbug.com/1036357 [ android-nexus-5 ] rendering.mobile/idle_power_blank [ Skip ]
-crbug.com/1036357 [ android-nexus-5 ] rendering.mobile/balls_svg_animations [ Skip ]
 crbug.com/1116469 [ android-webview ] rendering.mobile/webgl_to_texture [ Skip ]
-crbug.com/1124237 [ android-nexus-5x android-webview ] rendering.mobile/toBlob_duration.html [ Skip ]
-crbug.com/1150490 [ android-nexus-5x android-webview ] rendering.mobile/microgame_fps [ Skip ]
 crbug.com/1153607 [ android-pixel-2 ] rendering.mobile/many_images [ Skip ]
 crbug.com/1178690 [ android-webview ] rendering.mobile/wsj_mobile_2018 [ Skip ]
 crbug.com/1193722 [ android-pixel-2 android-webview ] rendering.mobile/espn_pathological_2018 [ Skip ]
@@ -335,17 +255,11 @@
 crbug.com/815193 [ android ] rasterize_and_record_micro.top_25/file://static_top_25/wikipedia.html [ Skip ]
 crbug.com/873011 [ android-webview ] rasterize_and_record_micro.top_25/file://static_top_25/yahoonews.html [ Skip ]
 crbug.com/865400 [ android-pixel-2 ] rasterize_and_record_micro.top_25/file://static_top_25/yahoonews.html [ Skip ]
-crbug.com/865400 [ android-nexus-5 ] rasterize_and_record_micro.top_25/file://static_top_25/yahoonews.html [ Skip ]
-crbug.com/865400 [ android-nexus-5x ] rasterize_and_record_micro.top_25/file://static_top_25/yahoonews.html [ Skip ]
-crbug.com/892223 [ android-nexus-5 ] rasterize_and_record_micro.top_25/file://static_top_25/yahoogames.html [ Skip ]
-crbug.com/910207 [ android-nexus-5x ] rasterize_and_record_micro.top_25/file://static_top_25/yahoogames.html [ Skip ]
 crbug.com/875878 [ android-nexus-6 android-webview ] rasterize_and_record_micro.top_25/file://static_top_25/yahoogames.html [ Skip ]
 crbug.com/1228228 [ android-webview ] rasterize_and_record_micro.top_25/file://static_top_25/gmail.html [ Skip ]
 crbug.com/1228228 [ mac-10.12 ] rasterize_and_record_micro.top_25/file://static_top_25/gmail.html [ Skip ]
 
 # Benchmark: startup.mobile
-crbug.com/948789 [ android-nexus-5 ] startup.mobile/maps_pwa:with_http_cache [ Skip ]
-crbug.com/948789 [ android-nexus-5x ] startup.mobile/maps_pwa:with_http_cache [ Skip ]
 crbug.com/1030735 [ android-weblayer ] startup.mobile/maps_pwa:with_http_cache [ Skip ]
 crbug.com/1059717 [ android-pixel-2 ] startup.mobile/maps_pwa:with_http_cache [ Skip ]
 
@@ -360,7 +274,6 @@
 crbug.com/1008093 [ linux ] system_health.common_desktop/multitab:misc:typical24:2018 [ Skip ]
 crbug.com/1008093 [ mac ] system_health.common_desktop/multitab:misc:typical24 [ Skip ]
 crbug.com/1008093 [ mac ] system_health.common_desktop/multitab:misc:typical24:2018 [ Skip ]
-crbug.com/931185 [ win7 ] system_health.common_desktop/browse:media:youtubetv:2019 [ Skip ]
 crbug.com/1008028 [ desktop ] system_health.common_desktop/browse:news:hackernews:2020 [ Skip ]
 crbug.com/1009838 [ mac ] system_health.common_desktop/browse:tools:maps:2019 [ Skip ]
 crbug.com/1008001 [ win ] system_health.common_desktop/browse:tools:sheets:2019 [ Skip ]
@@ -368,7 +281,6 @@
 crbug.com/1023366 [ desktop ] system_health.common_desktop/browse:media:youtube:2019 [ Skip ]
 crbug.com/1259608 [ desktop ] system_health.common_desktop/load:media:imgur:2018 [ Skip ]
 crbug.com/1042632 [ win ] system_health.common_desktop/load:tools:gmail:2019 [ Skip ]
-crbug.com/1050068 [ win7 ] system_health.common_desktop/load:media:9gag [ Skip ]
 crbug.com/1044682 [ desktop ] system_health.common_desktop/browse:tools:gmail-labelclick:2020 [ Skip ]
 crbug.com/1044682 [ desktop ] system_health.common_desktop/browse:tools:gmail-openconversation:2020 [ Skip ]
 crbug.com/1044682 [ desktop ] system_health.common_desktop/browse:tools:gmail-search:2020 [ Skip ]
@@ -458,9 +370,7 @@
 crbug.com/869118 [ linux ] system_health.memory_desktop/long_running:tools:gmail-background [ Skip ]
 crbug.com/899887 [ linux ] system_health.memory_desktop/browse:social:facebook_infinite_scroll:2018 [ Skip ]
 crbug.com/924330 [ linux ] system_health.memory_desktop/browse:media:pinterest:2018 [ Skip ]
-crbug.com/1088447 [ win7 ] system_health.memory_desktop/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
 crbug.com/874803 [ win10 ] system_health.memory_desktop/multitab:misc:typical24 [ Skip ]
-crbug.com/944978 [ win7 ] system_health.memory_desktop/multitab:misc:typical24 [ Skip ]
 crbug.com/934270 [ win ] system_health.memory_desktop/multitab:misc:typical24:2018 [ Skip ]
 crbug.com/942952 [ chromeos ] system_health.memory_desktop/browse:news:hackernews:2020 [ Skip ]
 crbug.com/959418 [ chromeos ] system_health.memory_desktop/long_running:tools:gmail-background [ Skip ]
@@ -473,7 +383,6 @@
 crbug.com/1017346 [ desktop ] system_health.memory_desktop/browse:media:youtube:2019 [ Skip ]
 crbug.com/1042632 [ win ] system_health.memory_desktop/load:tools:gmail:2019 [ Skip ]
 crbug.com/1042632 [ win ] system_health.memory_desktop/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
-crbug.com/956191 [ win7 ] system_health.memory_desktop/browse:news:nytimes:2020 [ Skip ]
 crbug.com/1097065 [ linux ] system_health.memory_desktop/load:social:vk:2018 [ Skip ]
 crbug.com/1097065 [ linux ] system_health.memory_desktop/browse_accessibility:tech:codesearch:2018 [ Skip ]
 crbug.com/1114120 [ win ] system_health.memory_desktop/browse:social:twitter_infinite_scroll:2018 [ Skip ]
@@ -574,12 +483,10 @@
 crbug.com/883320 [ android-go ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
 crbug.com/1064658 [ android ] system_health.memory_mobile/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
 crbug.com/923527 [ android-webview ] system_health.memory_mobile/load:media:soundcloud:2018 [ Skip ]
-crbug.com/1017661 [ android-nexus-5x ] system_health.memory_mobile/browse:social:facebook_infinite_scroll:2018 [ Skip ]
 crbug.com/1036143 [ android-pixel-2 ] system_health.memory_mobile/browse:chrome:omnibox:2019 [ Skip ]
 crbug.com/1036141 [ android-webview ] system_health.memory_mobile/browse:shopping:lazada:2019 [ Skip ]
 crbug.com/1044956 [ android-pixel-2 ] system_health.memory_mobile/background:tools:gmail:2019 [ Skip ]
 crbug.com/1049984 [ android-go android-webview ] system_health.memory_mobile/browse:social:instagram:2019 [ Skip ]
-crbug.com/1097719 [ android-nexus-5x android-webview ] system_health.memory_mobile/browse:social:instagram:2019 [ Skip ]
 crbug.com/1112337 [ android ] system_health.memory_mobile/browse:news:cricbuzz:2019 [ Skip ]
 crbug.com/1113921 [ android-go ] system_health.memory_mobile/load:tools:dropbox:2019 [ Skip ]
 crbug.com/1139057 [ mobile ] system_health.memory_mobile/browse:social:facebook:2019 [ Skip ]
@@ -607,7 +514,6 @@
 crbug.com/1009838 [ mac ] v8.browsing_desktop/browse:tools:maps:2019 [ Skip ]
 crbug.com/1009838 [ chromeos ] v8.browsing_desktop/browse:tools:maps:2019 [ Skip ]
 crbug.com/1023366 [ desktop ] v8.browsing_desktop/browse:media:youtube:2019 [ Skip ]
-crbug.com/1044653 [ win7 ] v8.browsing_desktop/browse:media:youtubetv:2019 [ Skip ]
 crbug.com/1057975 [ mac ] v8.browsing_desktop/browse:tools:maps:2019 [ Skip ]
 crbug.com/1100138 [ desktop ] v8.browsing_desktop/browse:tools:gmail-labelclick:2020 [ Skip ]
 crbug.com/1100138 [ desktop ] v8.browsing_desktop/browse:tools:gmail-openconversation:2020 [ Skip ]
@@ -642,7 +548,6 @@
 crbug.com/1009838 [ mac ] v8.browsing_desktop-future/browse:tools:maps:2019 [ Skip ]
 crbug.com/1009838 [ chromeos ] v8.browsing_desktop-future/browse:tools:maps:2019 [ Skip ]
 crbug.com/1023366 [ desktop ] v8.browsing_desktop-future/browse:media:youtube:2019 [ Skip ]
-crbug.com/1044653 [ win7 ] v8.browsing_desktop-future/browse:media:youtubetv:2019 [ Skip ]
 crbug.com/1057975 [ mac ] v8.browsing_desktop-future/browse:tools:maps:2019 [ Skip ]
 crbug.com/1100138 [ desktop ] v8.browsing_desktop-future/browse:tools:gmail-labelclick:2020 [ Skip ]
 crbug.com/1100138 [ desktop ] v8.browsing_desktop-future/browse:tools:gmail-openconversation:2020 [ Skip ]
@@ -675,7 +580,6 @@
 crbug.com/1036141 [ android-webview ] v8.browsing_mobile/browse:shopping:lazada:2019 [ Skip ]
 crbug.com/1036143 [ android-pixel-2 ] v8.browsing_mobile/browse:chrome:omnibox:2019 [ Skip ]
 crbug.com/1064658 [ android ] v8.browsing_mobile/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
-crbug.com/1088419 [ android-nexus-5x android-webview ] v8.browsing_mobile/browse:media:imgur:2019 [ Skip ]
 crbug.com/1112337 [ android ] v8.browsing_mobile/browse:news:cricbuzz:2019 [ Skip ]
 crbug.com/1139057 [ mobile ] v8.browsing_mobile/browse:social:facebook:2019 [ Skip ]
 crbug.com/1143740 [ android-pixel-2 android-webview ] v8.browsing_mobile/browse:media:imgur:2019 [ Skip ]
@@ -696,7 +600,6 @@
 crbug.com/1036141 [ android-webview ] v8.browsing_mobile-future/browse:shopping:lazada:2019 [ Skip ]
 crbug.com/1036143 [ android-pixel-2 ] v8.browsing_mobile-future/browse:chrome:omnibox:2019 [ Skip ]
 crbug.com/1064658 [ android ] v8.browsing_mobile-future/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
-crbug.com/1088419 [ android-nexus-5x android-webview ] v8.browsing_mobile-future/browse:media:imgur:2019 [ Skip ]
 crbug.com/1112337 [ android ] v8.browsing_mobile-future/browse:news:cricbuzz:2019 [ Skip ]
 crbug.com/1139057 [ mobile ] v8.browsing_mobile-future/browse:social:facebook:2019 [ Skip ]
 crbug.com/1143740 [ android-pixel-2 android-webview ] v8.browsing_mobile-future/browse:media:imgur:2019 [ Skip ]
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 12e31e53..c189a4d9 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -49,7 +49,7 @@
  <item id="client_download_request" added_in_milestone="62" content_hash_code="04bc89c5" os_list="linux,windows,chromeos" file_path="chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc" />
  <item id="content_hash_verification_job" added_in_milestone="62" content_hash_code="079fc9db" os_list="linux,windows,chromeos" file_path="extensions/browser/content_hash_fetcher.cc" />
  <item id="content_suggestion_get_favicon" added_in_milestone="62" content_hash_code="0800f6e5" os_list="linux,windows,chromeos,android" file_path="components/ntp_snippets/content_suggestions_service.cc" />
- <item id="conversion_measurement_report" added_in_milestone="84" content_hash_code="07884f7e" os_list="linux,windows,chromeos,android" file_path="content/browser/attribution_reporting/attribution_report_network_sender.cc" />
+ <item id="conversion_measurement_report" added_in_milestone="84" content_hash_code="07884f7e" os_list="linux,windows,android" file_path="content/browser/attribution_reporting/attribution_report_network_sender.cc" />
  <item id="credenential_avatar" added_in_milestone="62" content_hash_code="06bcc86b" os_list="linux,windows,chromeos,android" file_path="chrome/browser/ui/passwords/account_avatar_fetcher.cc" />
  <item id="cros_recovery_image_download" added_in_milestone="62" content_hash_code="00a7d792" os_list="linux,windows,chromeos" file_path="chrome/browser/extensions/api/image_writer_private/write_from_url_operation.cc" />
  <item id="desktop_screenshot_save" added_in_milestone="94" content_hash_code="019480c9" os_list="linux,windows,chromeos" file_path="chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc" />
@@ -125,7 +125,7 @@
  <item id="indexed_db_internals_handler" added_in_milestone="62" content_hash_code="0384abe6" os_list="linux,windows,chromeos,android" file_path="content/browser/indexed_db/indexed_db_internals_ui.cc" />
  <item id="interest_feedv2_image_send" added_in_milestone="86" content_hash_code="06687258" os_list="linux,windows,chromeos,android" file_path="components/feed/core/v2/image_fetcher.cc" />
  <item id="interest_feedv2_send" added_in_milestone="83" content_hash_code="02f676af" os_list="linux,windows,chromeos,android" file_path="components/feed/core/v2/feed_network_impl.cc" />
- <item id="interest_group_update_fetcher" added_in_milestone="94" content_hash_code="0272b26d" os_list="linux,windows,android,chromeos" file_path="content/browser/interest_group/interest_group_update_manager.cc" />
+ <item id="interest_group_update_fetcher" added_in_milestone="94" content_hash_code="0272b26d" os_list="linux,windows,android" file_path="content/browser/interest_group/interest_group_update_manager.cc" />
  <item id="intranet_redirect_detector" added_in_milestone="62" content_hash_code="03b26f7b" os_list="linux,windows,chromeos" file_path="chrome/browser/intranet_redirect_detector.cc" />
  <item id="javascript_report_error" added_in_milestone="87" content_hash_code="006e4e54" os_list="linux" file_path="chrome/browser/error_reporting/chrome_js_error_report_processor_nonchromeos.cc" />
  <item id="lib_address_input" added_in_milestone="62" content_hash_code="0374aae8" os_list="linux,windows,chromeos,android" file_path="third_party/libaddressinput/chromium/chrome_metadata_source.cc" />
@@ -163,7 +163,7 @@
  <item id="openscreen_message" added_in_milestone="83" content_hash_code="076a1faf" os_list="linux,windows,chromeos,android" file_path="components/openscreen_platform/udp_socket.cc" />
  <item id="openscreen_tls_message" added_in_milestone="83" content_hash_code="00f4022a" os_list="linux,windows,chromeos,android" file_path="components/openscreen_platform/tls_connection_factory.cc" />
  <item id="optimization_guide_model" added_in_milestone="79" content_hash_code="01ee6e67" os_list="linux,windows,chromeos,android" file_path="components/optimization_guide/core/prediction_model_fetcher_impl.cc" />
- <item id="optimization_guide_model_download" added_in_milestone="88" content_hash_code="05d71d9b" os_list="linux,windows,chromeos,android" file_path="components/optimization_guide/core/prediction_model_download_manager.cc" />
+ <item id="optimization_guide_model_download" added_in_milestone="88" content_hash_code="05d71d9b" os_list="linux,windows,android" file_path="components/optimization_guide/core/prediction_model_download_manager.cc" />
  <item id="origin_policy_loader" added_in_milestone="69" content_hash_code="07fd1eaf" os_list="linux,windows,chromeos,android" file_path="services/network/origin_policy/origin_policy_fetcher.cc" />
  <item id="parallel_download_job" added_in_milestone="62" content_hash_code="064736f3" os_list="linux,windows,chromeos,android" file_path="components/download/internal/common/parallel_download_job.cc" />
  <item id="password_protection_request" added_in_milestone="62" content_hash_code="01869413" os_list="linux,windows,chromeos,android" file_path="components/safe_browsing/core/browser/password_protection/password_protection_request.cc" />
@@ -291,8 +291,6 @@
  <item id="ambient_photo_cache" added_in_milestone="98" content_hash_code="07dbf21e" os_list="chromeos" file_path="ash/ambient/ambient_photo_cache.cc" />
  <item id="ambient_photo_controller" added_in_milestone="98" content_hash_code="03284b8a" os_list="chromeos" file_path="ash/ambient/ambient_photo_controller.cc" />
  <item id="image_downloader" added_in_milestone="98" content_hash_code="05b52680" os_list="chromeos" file_path="ash/assistant/assistant_controller_impl.cc" />
- <item id="fast_pair_footprints_request" added_in_milestone="98" type="partial" second_id="oauth2_api_call_flow" content_hash_code="01d3d58d" os_list="chromeos" semantics_fields="1,2,3,4,5" policy_fields="-1,3,5" file_path="ash/quick_pair/repository/fast_pair/footprints_fetcher.cc" />
- <item id="fast_pair" added_in_milestone="99" content_hash_code="060b214f" os_list="chromeos" file_path="ash/quick_pair/repository/fast_pair/fast_pair_image_decoder_impl.cc" />
  <item id="kiosk_app_icon" added_in_milestone="98" content_hash_code="04f02fef" os_list="chromeos" file_path="chrome/browser/ash/app_mode/web_app/web_kiosk_app_data.cc" />
  <item id="arc_auth_code_fetcher" added_in_milestone="98" content_hash_code="057519ca" os_list="chromeos" file_path="chrome/browser/ash/arc/auth/arc_background_auth_code_fetcher.cc" />
  <item id="customization_wallpaper_downloader" added_in_milestone="98" content_hash_code="03ee8364" os_list="chromeos" file_path="chrome/browser/ash/customization/customization_wallpaper_downloader.cc" />
@@ -312,7 +310,7 @@
  <item id="launcher_item_suggest" added_in_milestone="98" content_hash_code="04a4041e" os_list="chromeos" file_path="chrome/browser/ui/app_list/search/files/item_suggest_cache.cc" />
  <item id="ambient_client" added_in_milestone="98" content_hash_code="062d821f" os_list="chromeos" file_path="chrome/browser/ui/ash/ambient/ambient_client_impl.cc" />
  <item id="calendar_get_events" added_in_milestone="98" content_hash_code="0108842a" os_list="chromeos" file_path="chrome/browser/ui/ash/calendar/calendar_keyed_service.cc" />
- <item id="side_search_availability_test" added_in_milestone="98" content_hash_code="04410970" os_list="chromeos" file_path="chrome/browser/ui/side_search/side_search_tab_contents_helper.cc" />
+ <item id="side_search_availability_test" added_in_milestone="98" content_hash_code="04b1ffa5" os_list="chromeos,linux,windows" file_path="chrome/browser/ui/side_search/side_search_tab_contents_helper.cc" />
  <item id="edu_account_login_profile_image_fetcher" added_in_milestone="98" content_hash_code="0689301c" os_list="chromeos" file_path="chrome/browser/ui/webui/chromeos/edu_account_login_handler_chromeos.cc" />
  <item id="management_ui_customer_logo" added_in_milestone="98" content_hash_code="01a17a58" os_list="chromeos" file_path="chrome/browser/ui/webui/management/management_ui_handler_chromeos.cc" />
  <item id="gaia_reauth_token_fetcher" added_in_milestone="98" content_hash_code="04ff463d" os_list="chromeos" file_path="chrome/browser/ash/login/gaia_reauth_token_fetcher.cc" />
@@ -360,4 +358,5 @@
  <item id="sct_auditing_hashdance" added_in_milestone="100" content_hash_code="00ea07d2" os_list="linux,windows,android,chromeos" file_path="chrome/browser/ssl/sct_reporting_service.cc" />
  <item id="pull_template_request" added_in_milestone="101" content_hash_code="06335b4a" os_list="android" file_path="components/content_creation/notes/core/templates/template_fetcher.cc" />
  <item id="webapk_create_for_service" added_in_milestone="101" content_hash_code="00107cfb" os_list="android" file_path="chrome/browser/android/webapk/webapk_installer.cc" />
+ <item id="wallpaper_google_photos_enabled" added_in_milestone="101" content_hash_code="0264e793" os_list="chromeos" file_path="chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index 7447e61..7ba954dc 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -67,8 +67,6 @@
       <traffic_annotation unique_id="chromebook_mail_api"/>
       <traffic_annotation unique_id="customization_wallpaper_downloader"/>
       <traffic_annotation unique_id="edu_account_login_profile_image_fetcher"/>
-      <traffic_annotation unique_id="fast_pair"/>
-      <traffic_annotation unique_id="fast_pair_footprints_request"/>
       <traffic_annotation unique_id="fast_pair_image_decoder"/>
       <traffic_annotation unique_id="fast_pair_device_metadata_fetcher"/>
       <traffic_annotation unique_id="fwupd_firmware_update"/>
@@ -95,6 +93,7 @@
       <traffic_annotation unique_id="wallpaper_fetcher"/>
       <traffic_annotation unique_id="wallpaper_google_photos_albums"/>
       <traffic_annotation unique_id="wallpaper_google_photos_count"/>
+      <traffic_annotation unique_id="wallpaper_google_photos_enabled"/>
       <traffic_annotation unique_id="wallpaper_google_photos_photos"/>
     </sender>
     <!-- ChromeOS-specific features -->
diff --git a/ui/accessibility/ax_role_properties.cc b/ui/accessibility/ax_role_properties.cc
index c507113..87170d6 100644
--- a/ui/accessibility/ax_role_properties.cc
+++ b/ui/accessibility/ax_role_properties.cc
@@ -903,6 +903,15 @@
   }
 }
 
+bool IsWindow(ax::mojom::Role role) {
+  switch (role) {
+    case ax::mojom::Role::kWindow:
+      return true;
+    default:
+      return false;
+  }
+}
+
 bool ShouldHaveReadonlyStateByDefault(const ax::mojom::Role role) {
   switch (role) {
     case ax::mojom::Role::kArticle:
diff --git a/ui/accessibility/ax_role_properties.h b/ui/accessibility/ax_role_properties.h
index ec4ded7..8585cf04 100644
--- a/ui/accessibility/ax_role_properties.h
+++ b/ui/accessibility/ax_role_properties.h
@@ -214,6 +214,9 @@
 // objects. See the method definition for more details.
 AX_BASE_EXPORT bool IsUIAEmbeddedObject(ax::mojom::Role role);
 
+// Returns true if the provided role represents a window.
+AX_BASE_EXPORT bool IsWindow(const ax::mojom::Role role);
+
 // Returns true if the node should be read only by default
 AX_BASE_EXPORT bool ShouldHaveReadonlyStateByDefault(
     const ax::mojom::Role role);
diff --git a/ui/accessibility/platform/ax_platform_node_cocoa.mm b/ui/accessibility/platform/ax_platform_node_cocoa.mm
index d5c5492d..d7c4d1b 100644
--- a/ui/accessibility/platform/ax_platform_node_cocoa.mm
+++ b/ui/accessibility/platform/ax_platform_node_cocoa.mm
@@ -500,8 +500,12 @@
       break;
   }
 
-  // VoiceOver computes the wrong description for a link.
+  // No label for windows.
   ax::mojom::Role role = _node->GetRole();
+  if (ui::IsWindow(role))
+    return false;
+
+  // VoiceOver computes the wrong description for a link.
   if (ui::IsLink(role))
     return true;
 
@@ -1474,10 +1478,7 @@
 }
 
 - (NSString*)AXTitle {
-  if (ui::IsNameExposedInAXValueForRole(_node->GetRole()))
-    return @"";
-
-  return [self getName];
+  return [self accessibilityTitle];
 }
 
 - (id)AXTitleUIElement {
@@ -1638,7 +1639,7 @@
 
 - (NSString*)description {
   return [NSString stringWithFormat:@"%@ - %@ (%@)", [super description],
-                                    [self AXTitle], [self AXRole]];
+                                    [self accessibilityTitle], [self AXRole]];
 }
 
 //
@@ -1737,7 +1738,29 @@
 }
 
 - (NSString*)accessibilityTitle {
-  return [self AXTitle];
+  if (![self instanceActive])
+    return nil;
+
+  if (ui::IsNameExposedInAXValueForRole(_node->GetRole()))
+    return @"";
+
+  if ([self isNameFromLabel])
+    return @"";
+
+  // If we're exposing the title in TitleUIElement, don't also redundantly
+  // expose it in AXDescription.
+  if ([self titleUIElement])
+    return @"";
+
+  // On macOS cell titles are empty if they came from content.
+  ax::mojom::NameFrom nameFrom = _node->GetNameFrom();
+  if (nameFrom == ax::mojom::NameFrom::kContents) {
+    NSString* role = [self accessibilityRole];
+    if ([role isEqualToString:NSAccessibilityCellRole])
+      return @"";
+  }
+
+  return [self getName];
 }
 
 - (id)accessibilityValue {
diff --git a/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm b/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm
index 40ce4c1..2194a81 100644
--- a/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm
+++ b/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm
@@ -78,6 +78,7 @@
        NSAccessibilityPopupValueAttribute,
        NSAccessibilityRequiredAttributeChrome,
        NSAccessibilityRoleDescriptionAttribute,
+       NSAccessibilityTitleAttribute,
        NSAccessibilityTitleUIElementAttribute,
        NSAccessibilityURLAttribute,
        NSAccessibilityVisitedAttribute},
diff --git a/ui/gfx/animation/tween.cc b/ui/gfx/animation/tween.cc
index 926c30fc..9e0a711 100644
--- a/ui/gfx/animation/tween.cc
+++ b/ui/gfx/animation/tween.cc
@@ -118,6 +118,9 @@
 
     case ACCEL_0_100_DECEL_80:
       return gfx::CubicBezier(0, 1, 0.2, 1).Solve(state);
+
+    case ACCEL_5_70_DECEL_90:
+      return gfx::CubicBezier(0.05, 0.7, 0.1, 1).Solve(state);
   }
 
   NOTREACHED();
@@ -164,8 +167,8 @@
       BlendColorComponents(SkColorGetB(start), SkColorGetB(target), start_a,
                            target_a, blended_a, value);
 
-  return SkColorSetARGB(
-      FloatToColorByte(blended_a), blended_r, blended_g, blended_b);
+  return SkColorSetARGB(FloatToColorByte(blended_a), blended_r, blended_g,
+                        blended_b);
 }
 
 // static
diff --git a/ui/gfx/animation/tween.h b/ui/gfx/animation/tween.h
index 45d86cd..f9c8a13 100644
--- a/ui/gfx/animation/tween.h
+++ b/ui/gfx/animation/tween.h
@@ -94,6 +94,9 @@
 
     ACCEL_0_100_DECEL_80,  // Variant of ACCEL_0_80_DECEL_80 which drops in
                            // value even faster.
+
+    ACCEL_5_70_DECEL_90,  // Start at peak velocity and very soft
+                          // deceleration.
   };
 
   Tween(const Tween&) = delete;
diff --git a/ui/gfx/switches.cc b/ui/gfx/switches.cc
index 67a798b..0e8044c 100644
--- a/ui/gfx/switches.cc
+++ b/ui/gfx/switches.cc
@@ -47,6 +47,12 @@
 };
 
 const base::Feature kOddWidthMultiPlanarBuffers{
-    "OddWidthMultiPlanarBuffers", base::FEATURE_DISABLED_BY_DEFAULT};
+  "OddWidthMultiPlanarBuffers",
+#if BUILDFLAG(IS_MAC)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
 
 }  // namespace features
diff --git a/ui/views/controls/tabbed_pane/tabbed_pane.cc b/ui/views/controls/tabbed_pane/tabbed_pane.cc
index 85c6cab9..eb25f02 100644
--- a/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -138,6 +138,7 @@
 void Tab::GetAccessibleNodeData(ui::AXNodeData* data) {
   data->role = ax::mojom::Role::kTab;
   data->SetName(title_->GetText());
+  data->SetNameFrom(ax::mojom::NameFrom::kContents);
   data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected());
 }
 
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index a7ee067..168f63d 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -960,6 +960,8 @@
   node_data->role = ax::mojom::Role::kTextField;
 
   node_data->SetName(accessible_name_);
+  node_data->SetNameFrom(ax::mojom::NameFrom::kContents);
+
   // Editable state indicates support of editable interface, and is always set
   // for a textfield, even if disabled or readonly.
   node_data->AddState(ax::mojom::State::kEditable);
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
index 5795a38..a316ed8 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
@@ -7,6 +7,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.support.test.InstrumentationRegistry;
+import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -423,4 +424,43 @@
                 ScrollNotificationType.DIRECTION_CHANGED_UP, (int) notificationTypes[0]);
         Assert.assertTrue(scrollRatio[0] < 0.5);
     }
+
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(101)
+    public void testOnVerticalOverscroll() throws TimeoutException {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
+
+        float overscrollY[] = new float[1];
+        CallbackHelper callbackHelper = new CallbackHelper();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onVerticalOverscroll(float accumulatedOverscrollY) {
+                    overscrollY[0] = accumulatedOverscrollY;
+                    callbackHelper.notifyCalled();
+                    tab.unregisterTabCallback(this);
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        View decorView[] = new View[1];
+        int dimension[] = new int[2];
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            decorView[0] = activity.getWindow().getDecorView();
+            dimension[0] = decorView[0].getWidth();
+            dimension[1] = decorView[0].getHeight();
+        });
+
+        int x = dimension[0] / 2;
+        int fromY = dimension[1] / 3;
+        int toY = dimension[1] / 3 * 2;
+
+        TestTouchUtils.dragCompleteView(InstrumentationRegistry.getInstrumentation(), decorView[0],
+                /*fromX=*/x, /*toX=*/x, fromY, toY, /*stepCount=*/10);
+        callbackHelper.waitForFirst();
+        Assert.assertThat(overscrollY[0], Matchers.lessThan(0f));
+    }
 }
diff --git a/weblayer/browser/java/BUILD.gn b/weblayer/browser/java/BUILD.gn
index eb8a818..d738792 100644
--- a/weblayer/browser/java/BUILD.gn
+++ b/weblayer/browser/java/BUILD.gn
@@ -44,7 +44,7 @@
     "//components/browser_ui/settings/android:java_resources",
     "//components/browser_ui/site_settings/android:java_resources",
     "//components/browser_ui/strings/android:browser_ui_strings_grd",
-    "//components/browser_ui/styles/android:java_resources",
+    "//components/browser_ui/theme/android:java_resources",
     "//components/infobars/android:java_resources",
     "//components/omnibox/browser:java_resources",
     "//components/page_info/android:java_resources",
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
index 7aad242..c49a5862 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java
@@ -50,6 +50,7 @@
 import org.chromium.components.webapps.AddToHomescreenCoordinator;
 import org.chromium.components.webapps.AppBannerManager;
 import org.chromium.content_public.browser.GestureListenerManager;
+import org.chromium.content_public.browser.GestureStateListener;
 import org.chromium.content_public.browser.GestureStateListenerWithScroll;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationHandle;
@@ -314,6 +315,20 @@
 
         mMediaSessionHelper = new MediaSessionHelper(
                 mWebContents, MediaSessionManager.createMediaSessionHelperDelegate(this));
+
+        GestureListenerManager.fromWebContents(mWebContents)
+                .addListener(new GestureStateListener() {
+                    @Override
+                    public void didOverscroll(
+                            float accumulatedOverscrollX, float accumulatedOverscrollY) {
+                        if (WebLayerFactoryImpl.getClientMajorVersion() < 101) return;
+                        try {
+                            mClient.onVerticalOverscroll(accumulatedOverscrollY);
+                        } catch (RemoteException e) {
+                            throw new APICallException(e);
+                        }
+                    }
+                });
     }
 
     private void doInitAfterSettingContainerView() {
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
index 21bf2d7..c09baf4e 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
@@ -47,4 +47,7 @@
       in IObjectWrapper linkText, in IObjectWrapper titleOrAltText,
       in IObjectWrapper srcUrl, in boolean isImage, in boolean isVideo, in boolean canDownload,
       in IContextMenuParams contextMenuParams) = 13;
+
+  // Added in M101.
+  void onVerticalOverscroll(float accumulatedOverscrollY) = 14;
 }
diff --git a/weblayer/public/java/org/chromium/weblayer/Tab.java b/weblayer/public/java/org/chromium/weblayer/Tab.java
index c503d97..c3d37fd 100644
--- a/weblayer/public/java/org/chromium/weblayer/Tab.java
+++ b/weblayer/public/java/org/chromium/weblayer/Tab.java
@@ -972,6 +972,14 @@
                 mActionModeCallback.onActionItemClicked(actionModeItemType, selectedString);
             }
         }
+
+        @Override
+        public void onVerticalOverscroll(float accumulatedOverscrollY) {
+            StrictModeWorkaround.apply();
+            for (TabCallback callback : mCallbacks) {
+                callback.onVerticalOverscroll(accumulatedOverscrollY);
+            }
+        }
     }
 
     private static final class ErrorPageCallbackClientImpl extends IErrorPageCallbackClient.Stub {
diff --git a/weblayer/public/java/org/chromium/weblayer/TabCallback.java b/weblayer/public/java/org/chromium/weblayer/TabCallback.java
index 724d289..58756765 100644
--- a/weblayer/public/java/org/chromium/weblayer/TabCallback.java
+++ b/weblayer/public/java/org/chromium/weblayer/TabCallback.java
@@ -72,4 +72,21 @@
      */
     public void onScrollNotification(
             @ScrollNotificationType int notificationType, float currentScrollRatio) {}
+
+    /**
+     * Notification for vertical overscroll. This happens when user tries to touch scroll beyond
+     * the scroll bounds, or when a fling animation hits scroll bounds.
+     * A few caveats when using this callback:
+     * * This should be considered independent and unordered with respect to other scroll callbacks
+     *   such as `onScrollNotification` or `ScrollOffsetCallback.onVerticalScrollOffsetChanged`.
+     *   Client should not assume a certain order between this and other scroll notifications.
+     * * The value is accumulated scroll, so the magnitude of the value only goes up for a single
+     *   overscroll gesture. However this is not enough to distinguish between two overscroll
+     *   gestures and client must listen to touch events to make such distinction. Similarly there
+     *   is no "end overscroll" event, and client is expected to listen to touch events as well.
+     *   Added in M101.
+     * @param accumulatedOverscrollY negative for when trying to scroll beyond offset 0, positive
+     *                               for when trying to scroll beyond bottom scroll bounds.
+     */
+    public void onVerticalOverscroll(float accumulatedOverscrollY) {}
 }