diff --git a/DEPS b/DEPS
index 3388b1b..3defa7f 100644
--- a/DEPS
+++ b/DEPS
@@ -245,11 +245,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': 'd88df695a5bf615b38d6b888842d299fbf0826ad',
+  'skia_revision': '0e597a5a4ced48fbf93879cc25c1a53f2e955a41',
   # 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': '39826aa60d5a16cff8798db290a52dbb0169f979',
+  'v8_revision': '11559b4461ac0c3328354229e03c2a989b104ff9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -292,7 +292,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '4eb6cb8818057a022f97176b53738ee3098c8eb6',
+  'freetype_revision': '2b672e7210a6e989aca4787fb81f4b2542bad9c1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -320,7 +320,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': 'c7d57e818a1fcedbf069f71d3e6f8d5d53b41e4c',
+  'devtools_frontend_revision': 'c91f9d88817d21922a3f9c752d96992f91ef9c62',
   # 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.
@@ -360,7 +360,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': 'ce44c8f400baeef357008bcc7a076e7986d97486',
+  'dawn_revision': '5070b65bc1cf80c5fc1a212c304a158d28ea61f8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -722,7 +722,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': 'wp4ZrF-Nyce8J3UDc5VWrAStdcQ51Idt6ZmcpdGKRxQC',
+          'version': 'gwW_nuIBbaSJs-T9SqZ8p-n9n8PeBeGCIpIT4sa1Yk4C',
         },
       ],
       'dep_type': 'cipd',
@@ -733,7 +733,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': '_Jh6gyFng6YXxpvIEKSC6Xgmz0J0sH-dH38Ri7igE4wC',
+          'version': 'lI01ErixADKVyf--6jyS6PIJOFVfe1tgu5b6DQMwTW4C',
         },
       ],
       'dep_type': 'cipd',
@@ -744,7 +744,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': '9nWfGFtN6UnLAn6z6i-9iTT6_7MrPgTg1z627s2D7NYC',
+          'version': 'AlGjJ-CejY4CeQg-uhxG0Gput00l4TBgsuXn5IhegKQC',
         },
       ],
       'dep_type': 'cipd',
@@ -1594,7 +1594,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/turbine',
-              'version': 'tJ3VP1_iZhYyYREs6I6YHVZeA5CN6drLhywrF_DFVh0C',
+              'version': 'ttEtncMGe74t-cysVW-3cc6loq-end5oDsc-Exn8WDsC',
           },
       ],
       'condition': 'checkout_android',
@@ -1648,7 +1648,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'fca7b339442bd70c5dc49bb33ee7f9466b560a97',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'a32e4ca33dc81da6db255ba59aa8d18ccc6d2a81',
+    Var('webrtc_git') + '/src.git' + '@' + 'ecf92afbf080c2af7fe21d9e6003446f510cd1eb',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1730,7 +1730,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@475deda10cc763d48548bea13bc1938545ca0df3',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@91602a9bc9aae5d6264be35f1e9d6a987f7416cb',
     'condition': 'checkout_src_internal',
   },
 
@@ -1760,7 +1760,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'SaAp5D1gG5lIC4ZE7xnLyMxeYzm6jRVP0G39LFQe7dAC',
+        'version': 'whGWspg-klNzSrHZ6Btb4NI-dWrEwtbx1ByAN3vyl50C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1771,7 +1771,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'DyuU3gkGWfadGk_00UO2sVlWXd1N32y3FqB6G_dqJVYC',
+        'version': 'b1RegiT9Iom0RtBYkCtI4xhjs3VuH137mKAHIzLaRjkC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1782,7 +1782,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'tKAFdQIr4EK_llsszeHtY38qGNZA-2tR4V5CxlPECDkC',
+        'version': '0apBJfzJkpDurPSAH4Ov522ZP09ROz15tKsFCGouaMYC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/strings/ash_strings_fil.xtb b/ash/strings/ash_strings_fil.xtb
index 19fa1f77..f49ed94 100644
--- a/ash/strings/ash_strings_fil.xtb
+++ b/ash/strings/ash_strings_fil.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">Naka-on ang Instant Caption.</translation>
 <translation id="1720011244392820496">I-on ang Wi-Fi Sync</translation>
+<translation id="1736898441010944794">Nakikita ng mga Bluetooth device ang "<ph name="NAME" />."</translation>
 <translation id="1743570585616704562">Hindi nakilala</translation>
 <translation id="1746730358044914197">Isinasaayos ng iyong administrator ang mga pamamaraan sa pag-input.</translation>
 <translation id="1747827819627189109">Naka-enable ang on-screen na keyboard</translation>
@@ -831,6 +832,7 @@
 <translation id="6578407462441924264">Walang Pangalan</translation>
 <translation id="6585808820553845416">Matatapos ang session pagkalipas ng <ph name="SESSION_TIME_REMAINING" />.</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">Lumipat ang iyong koneksyon sa mas secure na network</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, Lakas ng Signal <ph name="SIGNAL_STRENGTH" />, Pinapamahalaan ng iyong Administrator</translation>
 <translation id="6612802754306526077">Pinili ang screen recording mode</translation>
 <translation id="6614169507485700968">Naka-on ang privacy screen</translation>
diff --git a/ash/strings/ash_strings_hy.xtb b/ash/strings/ash_strings_hy.xtb
index cd2962b..ba4f6299 100644
--- a/ash/strings/ash_strings_hy.xtb
+++ b/ash/strings/ash_strings_hy.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">Կենդանի ենթագրերը միացված են։</translation>
 <translation id="1720011244392820496">Միացրեք Wi-Fi համաժամացումը</translation>
+<translation id="1736898441010944794">«<ph name="NAME" />» ադապտերը տեսանելի է Bluetooth սարքերին։</translation>
 <translation id="1743570585616704562">Չճանաչվեց</translation>
 <translation id="1746730358044914197">Ներածման եղանակները կարգավորվում են ձեր ադմինիստրատորի կողմից:</translation>
 <translation id="1747827819627189109">Էկրանի ստեղնաշարը միացված է</translation>
@@ -831,6 +832,7 @@
 <translation id="6578407462441924264">Անանուն</translation>
 <translation id="6585808820553845416">Աշխատաշրջանն ավարտվում է <ph name="SESSION_TIME_REMAINING" />-ից:</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">Դուք միացաք ավելի ապահով ցանցի</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, ազդանշանի ուժգնությունը՝ <ph name="SIGNAL_STRENGTH" />, կառավարվում է ձեր ադմինիստրատորի կողմից</translation>
 <translation id="6612802754306526077">Ընտրված է էկրանի տեսագրման ռեժիմը</translation>
 <translation id="6614169507485700968">Գաղտնիության էկրանը միացված է</translation>
diff --git a/ash/strings/ash_strings_ja.xtb b/ash/strings/ash_strings_ja.xtb
index b53b2a1..ddc87e7f 100644
--- a/ash/strings/ash_strings_ja.xtb
+++ b/ash/strings/ash_strings_ja.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">自動字幕起こしはオンになっています。</translation>
 <translation id="1720011244392820496">Wi-Fi 同期をオンにする</translation>
+<translation id="1736898441010944794">Bluetooth デバイスに「<ph name="NAME" />」が表示されます。</translation>
 <translation id="1743570585616704562">認識されませんでした</translation>
 <translation id="1746730358044914197">入力方法は管理者によって設定されています。</translation>
 <translation id="1747827819627189109">画面キーボードが有効です</translation>
@@ -831,6 +832,7 @@
 <translation id="6578407462441924264">名前なし</translation>
 <translation id="6585808820553845416">セッション終了まであと <ph name="SESSION_TIME_REMAINING" />です。</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />、<ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">より安全なネットワークに接続が切り替えられました</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />、信号強度 <ph name="SIGNAL_STRENGTH" />、管理者によって管理</translation>
 <translation id="6612802754306526077">画面の録画モードを選択しました</translation>
 <translation id="6614169507485700968">プライバシー スクリーンはオンになっています</translation>
diff --git a/ash/strings/ash_strings_kn.xtb b/ash/strings/ash_strings_kn.xtb
index c536ea8..5524713 100644
--- a/ash/strings/ash_strings_kn.xtb
+++ b/ash/strings/ash_strings_kn.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">ಲೈವ್ ಕ್ಯಾಪ್ಶನ್ ಆನ್ ಆಗಿದೆ.</translation>
 <translation id="1720011244392820496">ವೈ-ಫೈ ಸಿಂಕ್ ಆನ್ ಮಾಡಿ</translation>
+<translation id="1736898441010944794">ಬ್ಲೂಟೂತ್ ಸಾಧನಗಳಲ್ಲಿ "<ph name="NAME" />" ಗೋಚರಿಸುತ್ತದೆ.</translation>
 <translation id="1743570585616704562">ಗುರುತಿಸಲಾಗಿಲ್ಲ</translation>
 <translation id="1746730358044914197">ನಿಮ್ಮ ಇನ್‌ಪುಟ್‌ ವಿಧಾನಗಳನ್ನು ನಿರ್ವಾಹಕರ ಸಹಾಯದಿಂದ ಕಾನ್ಫಿಗರ್‌ ಮಾಡಲಾಗಿದೆ.</translation>
 <translation id="1747827819627189109">ಆನ್ ಸ್ಕ್ರೀನ್ ಕೀಬೋರ್ಡ್ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ</translation>
@@ -832,6 +833,7 @@
 <translation id="6578407462441924264">ಹೆಸರಿಸದಿರುವುದು</translation>
 <translation id="6585808820553845416">ಸೆಷನ್ <ph name="SESSION_TIME_REMAINING" /> ರಲ್ಲಿ ಕೊನೆಗೊಳ್ಳಲಿದೆ.</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">ನಿಮ್ಮ ಕನೆಕ್ಷನ್ ಅನ್ನು ಇನ್ನಷ್ಟು ಸುರಕ್ಷಿತವಾದ ನೆಟ್‍ವರ್ಕ್‌ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, ಸಿಗ್ನಲ್ ಸಾಮರ್ಥ್ಯ <ph name="SIGNAL_STRENGTH" />, ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ನಿರ್ವಹಿಸಿದ್ದಾರೆ</translation>
 <translation id="6612802754306526077">ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಮೋಡ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ</translation>
 <translation id="6614169507485700968">ಗೌಪ್ಯತೆ ಸ್ಕ್ರೀನ್ ಆನ್ ಆಗಿದೆ</translation>
diff --git a/ash/strings/ash_strings_lo.xtb b/ash/strings/ash_strings_lo.xtb
index 7ae4c70..3573036 100644
--- a/ash/strings/ash_strings_lo.xtb
+++ b/ash/strings/ash_strings_lo.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">ຄຳບັນຍາຍສົດເປີດຢູ່.</translation>
 <translation id="1720011244392820496">ເປີດ Wi-Fi Sync</translation>
+<translation id="1736898441010944794">"<ph name="NAME" />" ຈະສະແດງໃຫ້ອຸປະກອນ Bluetooth ເຫັນ.</translation>
 <translation id="1743570585616704562">ບໍ່ຮັບຮູ້</translation>
 <translation id="1746730358044914197">ວິທີການປ້ອນຂໍ້ມູນຖືກກຳນົດຄ່າໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ.</translation>
 <translation id="1747827819627189109">ແປ້ນພິມ​ເທິງ​ໜ້າ​ຈໍ​ເປີດໃຊ້​ງານ​ແລ້ວ</translation>
@@ -831,6 +832,7 @@
 <translation id="6578407462441924264">ບໍ່ມີຊື່</translation>
 <translation id="6585808820553845416">ຊ່ວງເວລາເຂົ້າໃຊ້ສິ້ນສຸດໃນ <ph name="SESSION_TIME_REMAINING" /> .</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">ສະຫຼັບການເຊື່ອມຕໍ່ຂອງທ່ານເປັນເຄືອຂ່າຍທີ່ປອດໄພຍິ່ງຂຶ້ນແລ້ວ</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, ຄວາມແຮງສັນຍານ <ph name="SIGNAL_STRENGTH" />, ຈັດການໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ</translation>
 <translation id="6612802754306526077">ເລືອກໂໝດການບັນທຶກໜ້າຈໍແລ້ວ</translation>
 <translation id="6614169507485700968">ໜ້າຈໍຄວາມເປັນສ່ວນຕົວເປີດຢູ່</translation>
diff --git a/ash/strings/ash_strings_ms.xtb b/ash/strings/ash_strings_ms.xtb
index e7608f685..594517d 100644
--- a/ash/strings/ash_strings_ms.xtb
+++ b/ash/strings/ash_strings_ms.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">Sari Kata Langsung dihidupkan.</translation>
 <translation id="1720011244392820496">Hidupkan Penyegerakan Wi-Fi</translation>
+<translation id="1736898441010944794">"<ph name="NAME" />" kelihatan kepada peranti Bluetooth.</translation>
 <translation id="1743570585616704562">Tidak dikenali</translation>
 <translation id="1746730358044914197">Kaedah masukan dikonfigurasi oleh pentadbir anda.</translation>
 <translation id="1747827819627189109">Papan kekunci pada skrin didayakan</translation>
@@ -832,6 +833,7 @@
 <translation id="6578407462441924264">Tidak bernama</translation>
 <translation id="6585808820553845416">Sesi berakhir dalam <ph name="SESSION_TIME_REMAINING" />.</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">Sambungan anda dialihkan kepada rangkaian yang lebih selamat</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, Kekuatan Isyarat <ph name="SIGNAL_STRENGTH" />, Diurus oleh Pentadbir anda</translation>
 <translation id="6612802754306526077">Mod rakaman skrin dipilih</translation>
 <translation id="6614169507485700968">Skrin privasi dihidupkan</translation>
diff --git a/ash/strings/ash_strings_ne.xtb b/ash/strings/ash_strings_ne.xtb
index 5c59822..13744129 100644
--- a/ash/strings/ash_strings_ne.xtb
+++ b/ash/strings/ash_strings_ne.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">लाइभ क्याप्सन अन छ।</translation>
 <translation id="1720011244392820496">Wi-Fi सिंक गर्ने सुविधा अन गर्नुहोस्</translation>
+<translation id="1736898441010944794">ब्लुटुथ डिभाइसहरूले "<ph name="NAME" />" देख्न सक्छन्।</translation>
 <translation id="1743570585616704562">पहिचान भएन</translation>
 <translation id="1746730358044914197">तपाईंको प्रशासकले इनपुट विधिहरूको कन्फिगर गर्नुभएको हो।</translation>
 <translation id="1747827819627189109">अन-स्क्रिन किबोर्ड सक्षम गरियो</translation>
@@ -831,6 +832,7 @@
 <translation id="6578407462441924264">नामविहीन</translation>
 <translation id="6585808820553845416">सत्र <ph name="SESSION_TIME_REMAINING" /> मा समाप्त हुन्छ।</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">तपाईंको कनेक्सन अझ सुरक्षित नेटवर्कमा लगिएको छ</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, सिग्नलको क्षमता <ph name="SIGNAL_STRENGTH" />, तपाईंका प्रशासकले व्यवस्थित गर्नुभएको छ</translation>
 <translation id="6612802754306526077">स्क्रिन रेकर्डिङ मोड चयन गरियो</translation>
 <translation id="6614169507485700968">गोपनीयताको स्क्रिन सक्रिय छ</translation>
diff --git a/ash/strings/ash_strings_nl.xtb b/ash/strings/ash_strings_nl.xtb
index 5ed18dfc..c8ecd9d 100644
--- a/ash/strings/ash_strings_nl.xtb
+++ b/ash/strings/ash_strings_nl.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">Live ondertiteling staat aan.</translation>
 <translation id="1720011244392820496">Wifi-synchronisatie aanzetten</translation>
+<translation id="1736898441010944794"><ph name="NAME" /> is zichtbaar voor bluetooth-apparaten.</translation>
 <translation id="1743570585616704562">Niet herkend</translation>
 <translation id="1746730358044914197">Invoermethoden worden ingesteld door je beheerder.</translation>
 <translation id="1747827819627189109">Schermtoetsenbord staat aan</translation>
@@ -831,6 +832,7 @@
 <translation id="6578407462441924264">Naamloos</translation>
 <translation id="6585808820553845416">Sessie loopt af over <ph name="SESSION_TIME_REMAINING" />.</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">Je verbinding is overgeschakeld op een beter beveiligd netwerk</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, signaalsterkte <ph name="SIGNAL_STRENGTH" />, beheerd door je beheerder</translation>
 <translation id="6612802754306526077">Schermopnamemodus geselecteerd</translation>
 <translation id="6614169507485700968">Privacyscherm staat aan</translation>
diff --git a/ash/strings/ash_strings_pl.xtb b/ash/strings/ash_strings_pl.xtb
index 1517873..abc7054 100644
--- a/ash/strings/ash_strings_pl.xtb
+++ b/ash/strings/ash_strings_pl.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">Napisy na żywo są włączone.</translation>
 <translation id="1720011244392820496">Włącz synchronizację Wi-Fi</translation>
+<translation id="1736898441010944794">„<ph name="NAME" />” jest widoczny dla urządzeń Bluetooth.</translation>
 <translation id="1743570585616704562">Nie rozpoznano</translation>
 <translation id="1746730358044914197">Metody wprowadzania zostały skonfigurowane przez administratora.</translation>
 <translation id="1747827819627189109">Klawiatura ekranowa włączona</translation>
@@ -830,6 +831,7 @@
 <translation id="6578407462441924264">Bez nazwy</translation>
 <translation id="6585808820553845416">Sesja kończy się za <ph name="SESSION_TIME_REMAINING" />.</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">Połączenie zostało przełączone na bezpieczniejszą sieć</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, siła sygnału: <ph name="SIGNAL_STRENGTH" />, zarządzana przez administratora</translation>
 <translation id="6612802754306526077">Wybrano tryb nagrywania ekranu</translation>
 <translation id="6614169507485700968">Ekran chroniący prywatność jest włączony</translation>
diff --git a/ash/strings/ash_strings_ro.xtb b/ash/strings/ash_strings_ro.xtb
index 7db3f554..273cf37 100644
--- a/ash/strings/ash_strings_ro.xtb
+++ b/ash/strings/ash_strings_ro.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">Funcția Subtitrări live este activată.</translation>
 <translation id="1720011244392820496">Activează Sincronizarea Wi-Fi</translation>
+<translation id="1736898441010944794">„<ph name="NAME" />” vizibil pentru dispozitivele Bluetooth.</translation>
 <translation id="1743570585616704562">Nu este recunoscută</translation>
 <translation id="1746730358044914197">Metodele de introducere a textului sunt configurate de administratorul tău.</translation>
 <translation id="1747827819627189109">Tastatură pe ecran activată</translation>
@@ -831,6 +832,7 @@
 <translation id="6578407462441924264">Nedenumit</translation>
 <translation id="6585808820553845416">Sesiunea se încheie peste <ph name="SESSION_TIME_REMAINING" />.</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" /> <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">Conexiunea a trecut la o rețea mai sigură</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, puterea semnalului: <ph name="SIGNAL_STRENGTH" />, gestionată de administratorul tău</translation>
 <translation id="6612802754306526077">Modul de înregistrare a ecranului selectat</translation>
 <translation id="6614169507485700968">Ecranul de confidențialitate este activat</translation>
diff --git a/ash/strings/ash_strings_te.xtb b/ash/strings/ash_strings_te.xtb
index f42fb05..394bf8b 100644
--- a/ash/strings/ash_strings_te.xtb
+++ b/ash/strings/ash_strings_te.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">లైవ్ క్యాప్షన్ ఆన్ చేయబడింది.</translation>
 <translation id="1720011244392820496">Wi-Fi సింక్‌ను ఆన్ చేయండి</translation>
+<translation id="1736898441010944794">"<ph name="NAME" />" బ్లూటూత్ పరికరాలకు కనిపిస్తుంది.</translation>
 <translation id="1743570585616704562">గుర్తించలేదు</translation>
 <translation id="1746730358044914197">ఇన్‌పుట్ పద్ధతులు మీ నిర్వాహకుల ద్వారా కాన్ఫిగర్ చేయబడ్డాయి.</translation>
 <translation id="1747827819627189109">స్క్రీన్‌పై కనిపించే కీబోర్డ్ ప్రారంభించబడింది</translation>
@@ -832,6 +833,7 @@
 <translation id="6578407462441924264">పేరు లేదు</translation>
 <translation id="6585808820553845416">సెషన్ <ph name="SESSION_TIME_REMAINING" />లో ముగుస్తుంది.</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">మీ కనెక్షన్ మరింత సురక్షితమైన నెట్‌వర్క్‌కు మార్చబడింది</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, సిగ్నల్ సామర్థ్యం <ph name="SIGNAL_STRENGTH" />, మీ అడ్మినిస్ట్రేటర్ ద్వారా నిర్వహించబడుతోంది</translation>
 <translation id="6612802754306526077">స్క్రీన్ రికార్డింగ్ మోడ్ ఎంచుకోబడింది</translation>
 <translation id="6614169507485700968">గోప్యతా స్క్రీన్ ఆన్‌లో ఉంది</translation>
diff --git a/ash/strings/ash_strings_th.xtb b/ash/strings/ash_strings_th.xtb
index 24bbc61..38c6472 100644
--- a/ash/strings/ash_strings_th.xtb
+++ b/ash/strings/ash_strings_th.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">คำบรรยายสดเปิดอยู่</translation>
 <translation id="1720011244392820496">เปิดการซิงค์ Wi-Fi</translation>
+<translation id="1736898441010944794">อุปกรณ์บลูทูธจะมองเห็น "<ph name="NAME" />" ได้</translation>
 <translation id="1743570585616704562">ไม่รู้จัก</translation>
 <translation id="1746730358044914197">ผู้ดูแลระบบเป็นคนกำหนดค่าวิธีการป้อนข้อมูล</translation>
 <translation id="1747827819627189109">เปิดใช้แป้นพิมพ์บนหน้าจออยู่</translation>
@@ -831,6 +832,7 @@
 <translation id="6578407462441924264">ไม่ได้ตั้งชื่อ</translation>
 <translation id="6585808820553845416">เซสชันจะสิ้นสุดใน <ph name="SESSION_TIME_REMAINING" /></translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />, <ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">ระบบเปลี่ยนไปเชื่อมต่อกับเครือข่ายที่ปลอดภัยกว่าเดิมแล้ว</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />, ความแรงสัญญาณ <ph name="SIGNAL_STRENGTH" />, จัดการโดยผู้ดูแลระบบ</translation>
 <translation id="6612802754306526077">เลือกโหมดบันทึกหน้าจอแล้ว</translation>
 <translation id="6614169507485700968">หน้าจอส่วนตัวเปิดอยู่</translation>
diff --git a/ash/strings/ash_strings_zh-HK.xtb b/ash/strings/ash_strings_zh-HK.xtb
index 8cbcdb4..203573d 100644
--- a/ash/strings/ash_strings_zh-HK.xtb
+++ b/ash/strings/ash_strings_zh-HK.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">「即時字幕」功能已開啟。</translation>
 <translation id="1720011244392820496">開啟 Wi-Fi Sync</translation>
+<translation id="1736898441010944794">「<ph name="NAME" />」可供藍牙裝置搜尋。</translation>
 <translation id="1743570585616704562">無法識別</translation>
 <translation id="1746730358044914197">您的管理員已設定輸入方法。</translation>
 <translation id="1747827819627189109">屏幕鍵盤已啟用</translation>
@@ -829,6 +830,7 @@
 <translation id="6578407462441924264">未命名</translation>
 <translation id="6585808820553845416">這個工作階段將在 <ph name="SESSION_TIME_REMAINING" />後結束。</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />,<ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">已切換至更安全的網路連線</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />,訊號強度係 <ph name="SIGNAL_STRENGTH" />,由管理員管理</translation>
 <translation id="6612802754306526077">揀咗螢幕錄影模式</translation>
 <translation id="6614169507485700968">已啟用私隱保護畫面</translation>
diff --git a/ash/strings/ash_strings_zh-TW.xtb b/ash/strings/ash_strings_zh-TW.xtb
index d52bf65..6ada002 100644
--- a/ash/strings/ash_strings_zh-TW.xtb
+++ b/ash/strings/ash_strings_zh-TW.xtb
@@ -122,6 +122,7 @@
 <translation id="1715874602234207">F</translation>
 <translation id="1719094688023114093">即時字幕功能已開啟。</translation>
 <translation id="1720011244392820496">開啟 Wi-Fi 同步功能</translation>
+<translation id="1736898441010944794">「<ph name="NAME" />」可供藍牙裝置搜尋。</translation>
 <translation id="1743570585616704562">無法識別</translation>
 <translation id="1746730358044914197">輸入法是由你的管理員所設定。</translation>
 <translation id="1747827819627189109">螢幕小鍵盤已啟用</translation>
@@ -830,6 +831,7 @@
 <translation id="6578407462441924264">未命名</translation>
 <translation id="6585808820553845416">這個工作階段將在 <ph name="SESSION_TIME_REMAINING" />後結束。</translation>
 <translation id="6593850935013518327"><ph name="PRIMARY_TEXT" />,<ph name="SECONDARY_TEXT" /></translation>
+<translation id="6602800870584047641">已切換至更安全的網路連線</translation>
 <translation id="661203523074512333"><ph name="SECURITY_STATUS" />,訊號強度 <ph name="SIGNAL_STRENGTH" />,由系統管理員管理</translation>
 <translation id="6612802754306526077">已選取螢幕畫面錄製模式</translation>
 <translation id="6614169507485700968">已啟用隱私保護畫面</translation>
diff --git a/ash/system/message_center/hps_notify_notification_blocker_unittest.cc b/ash/system/message_center/hps_notify_notification_blocker_unittest.cc
index 074bfa3..2636655d 100644
--- a/ash/system/message_center/hps_notify_notification_blocker_unittest.cc
+++ b/ash/system/message_center/hps_notify_notification_blocker_unittest.cc
@@ -148,7 +148,7 @@
   EXPECT_EQ(VisibleNotificationCount(), 3u);
 }
 
-TEST_F(HpsNotifyNotificationBlockerTest, Pref) {
+TEST_F(HpsNotifyNotificationBlockerTest, DISABLED_Pref) {
   SetBlockerPref(false);
 
   // Start with one notification that shouldn't be hidden.
diff --git a/ash/webui/camera_app_ui/resources/js/h264.js b/ash/webui/camera_app_ui/resources/js/h264.js
deleted file mode 100644
index c0d1913..0000000
--- a/ash/webui/camera_app_ui/resources/js/h264.js
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview
- * H264 related utility functions referenced from
- * media/video/h264_level_limits.cc.
- */
-
-import {assert, assertNotReached, assertString} from './assert.js';
-import {Resolution} from './type.js';  // eslint-disable-line no-unused-vars
-
-/**
- * @enum {number}
- */
-export const Profile = {
-  BASELINE: 66,
-  MAIN: 77,
-  HIGH: 100,
-};
-
-/**
- * @type {!Set<number>}
- */
-const profileValues = new Set(Object.values(Profile));
-
-/**
- * @param {number} v
- * @return {!Profile}
- */
-export function assertProfile(v) {
-  assert(profileValues.has(v));
-  return /** @type {!Profile} */ (v);
-}
-
-/**
- * @type {!Map<!Profile, (string|undefined)>}
- */
-const profileNames = new Map([
-  [Profile.BASELINE, 'baseline'],
-  [Profile.MAIN, 'main'],
-  [Profile.HIGH, 'high'],
-]);
-
-/**
- * @param {!Profile} profile
- * @return {string}
- */
-export function getProfileName(profile) {
-  return assertString(profileNames.get(profile));
-}
-
-/**
- * @enum {number}
- */
-export const Level = {
-  // Ignore unsupported lower levels.
-  LV30: 30,
-  LV31: 31,
-  LV32: 32,
-  LV40: 40,
-  LV41: 41,
-  LV42: 42,
-  LV50: 50,
-  LV51: 51,
-  LV52: 52,
-  LV60: 60,
-  LV61: 61,
-  LV62: 62,
-};
-
-/**
- * @type {!Array<!Level>}
- */
-export const Levels = Object.values(Level).sort((a, b) => a - b);
-
-/**
- * @typedef {{
- *   profile: !Profile,
- *   level: !Level,
- *   bitrate: number,
- * }}
- */
-export let EncoderParameters;
-
-/**
- * @typedef {{
- *   processRate: number,
- *   frameSize: number,
- *   mainBitrate: number,
- * }}
- */
-let LevelLimit;  // eslint-disable-line no-unused-vars
-
-/**
- * @type {!Map<!Level, !LevelLimit>}
- */
-const levelLimits = (() => {
-  const limit = (processRate, frameSize, mainBitrate) =>
-      ({processRate, frameSize, mainBitrate});
-  return new Map([
-    [Level.LV30, limit(40500, 1620, 10000)],
-    [Level.LV31, limit(108000, 3600, 14000)],
-    [Level.LV32, limit(216000, 5120, 20000)],
-    [Level.LV40, limit(245760, 8192, 20000)],
-    [Level.LV41, limit(245760, 8192, 50000)],
-    [Level.LV42, limit(522240, 8704, 50000)],
-    [Level.LV50, limit(589824, 22080, 135000)],
-    [Level.LV51, limit(983040, 36864, 240000)],
-    [Level.LV52, limit(2073600, 36864, 240000)],
-    [Level.LV60, limit(4177920, 139264, 240000)],
-    [Level.LV61, limit(8355840, 139264, 480000)],
-    [Level.LV62, limit(16711680, 139264, 800000)],
-  ]);
-})();
-
-/**
- * @param {!Profile} profile
- * @param {!Level} level
- * @return {number} Returns the maximal available bitrate supported by target
- *     profile and level.
- */
-export function getMaxBitrate(profile, level) {
-  const {mainBitrate} = levelLimits.get(level);
-  const kbs = (() => {
-    switch (profile) {
-      case Profile.BASELINE:
-      case Profile.MAIN:
-        return mainBitrate;
-      case Profile.HIGH:
-        return Math.floor(mainBitrate * 5 / 4);
-    }
-    assertNotReached();
-  })();
-  return kbs * 1000;
-}
-
-/**
- * H264 macroblock size in pixels.
- */
-const MACROBLOCK_SIZE = 16;
-
-/**
- * @param {!Level} level
- * @param {number} fps
- * @param {!Resolution} resolution
- * @return {boolean} Returns whether the recording |fps| and |resolution| fit in
- *     the h264 level limitation.
- */
-export function checkLevelLimits(level, fps, {width, height}) {
-  const frameSize =
-      Math.ceil(width / MACROBLOCK_SIZE) * Math.ceil(height / MACROBLOCK_SIZE);
-  const processRate = frameSize * fps;
-  const limit = levelLimits.get(level);
-  return frameSize <= limit.frameSize && processRate <= limit.processRate;
-}
-
-/**
- * Gets minimal available level with respect to given profile, bitrate,
- * resolution, fps.
- * @param {!Profile} profile
- * @param {number} bitrate
- * @param {number} fps
- * @param {!Resolution} resolution
- * @return {?Level}
- */
-export function getMinimalLevel(profile, bitrate, fps, resolution) {
-  for (const level of Levels) {
-    if (checkLevelLimits(level, fps, resolution) &&
-        getMaxBitrate(profile, level) >= bitrate) {
-      return level;
-    }
-  }
-  return null;
-}
diff --git a/ash/webui/camera_app_ui/resources/js/h264.ts b/ash/webui/camera_app_ui/resources/js/h264.ts
new file mode 100644
index 0000000..c8b15f7
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/h264.ts
@@ -0,0 +1,134 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * H264 related utility functions referenced from
+ * media/video/h264_level_limits.cc.
+ */
+
+import {assert, assertNotReached} from './assert.js';
+import {Resolution} from './type.js';
+
+export enum Profile {
+  BASELINE = 66,
+  MAIN = 77,
+  HIGH = 100,
+}
+
+export const profileValues = new Set(
+    Object.values(Profile).filter((x): x is Profile => typeof x === 'number'));
+
+export function assertProfile(v: number): Profile {
+  assert(profileValues.has(v));
+  return v;
+}
+
+const profileNames: Record<Profile, string> = {
+  [Profile.BASELINE]: 'baseline',
+  [Profile.MAIN]: 'main',
+  [Profile.HIGH]: 'high',
+};
+
+export function getProfileName(profile: Profile): string {
+  return profileNames[profile];
+}
+
+export enum Level {
+  // Ignore unsupported lower levels.
+  LV30 = 30,
+  LV31 = 31,
+  LV32 = 32,
+  LV40 = 40,
+  LV41 = 41,
+  LV42 = 42,
+  LV50 = 50,
+  LV51 = 51,
+  LV52 = 52,
+  LV60 = 60,
+  LV61 = 61,
+  LV62 = 62,
+}
+
+export const Levels = Object.values(Level)
+                          .filter((x): x is Level => typeof x === 'number')
+                          .sort((a, b) => a - b);
+
+export interface EncoderParameters {
+  profile: Profile;
+  level: Level;
+  bitrate: number;
+}
+
+const levelLimits = (() => {
+  const limit = (processRate: number, frameSize: number, mainBitrate: number) =>
+      ({processRate, frameSize, mainBitrate});
+  return {
+    [Level.LV30]: limit(40500, 1620, 10000),
+    [Level.LV31]: limit(108000, 3600, 14000),
+    [Level.LV32]: limit(216000, 5120, 20000),
+    [Level.LV40]: limit(245760, 8192, 20000),
+    [Level.LV41]: limit(245760, 8192, 50000),
+    [Level.LV42]: limit(522240, 8704, 50000),
+    [Level.LV50]: limit(589824, 22080, 135000),
+    [Level.LV51]: limit(983040, 36864, 240000),
+    [Level.LV52]: limit(2073600, 36864, 240000),
+    [Level.LV60]: limit(4177920, 139264, 240000),
+    [Level.LV61]: limit(8355840, 139264, 480000),
+    [Level.LV62]: limit(16711680, 139264, 800000),
+  };
+})();
+
+/**
+ * @return Returns the maximal available bitrate supported by target
+ *     profile and level.
+ */
+export function getMaxBitrate(profile: Profile, level: Level): number {
+  const {mainBitrate} = levelLimits[level];
+  const kbs = (() => {
+    switch (profile) {
+      case Profile.BASELINE:
+      case Profile.MAIN:
+        return mainBitrate;
+      case Profile.HIGH:
+        return Math.floor(mainBitrate * 5 / 4);
+    }
+    assertNotReached();
+  })();
+  return kbs * 1000;
+}
+
+/**
+ * H264 macroblock size in pixels.
+ */
+const MACROBLOCK_SIZE = 16;
+
+/**
+ * @return Returns whether the recording |fps| and |resolution| fit in the h264
+ *     level limitation.
+ */
+export function checkLevelLimits(
+    level: Level, fps: number, {width, height}: Resolution): boolean {
+  const frameSize =
+      Math.ceil(width / MACROBLOCK_SIZE) * Math.ceil(height / MACROBLOCK_SIZE);
+  const processRate = frameSize * fps;
+  const limit = levelLimits[level];
+  return frameSize <= limit.frameSize && processRate <= limit.processRate;
+}
+
+/**
+ * Gets minimal available level with respect to given profile, bitrate,
+ * resolution, fps.
+ */
+export function getMinimalLevel(
+    profile: Profile, bitrate: number, fps: number,
+    resolution: Resolution): Level|null {
+  for (const level of Levels) {
+    if (checkLevelLimits(level, fps, resolution) &&
+        getMaxBitrate(profile, level) >= bitrate) {
+      return level;
+    }
+  }
+  return null;
+}
diff --git a/ash/webui/camera_app_ui/resources/js/intent.js b/ash/webui/camera_app_ui/resources/js/intent.js
deleted file mode 100644
index c037e41..0000000
--- a/ash/webui/camera_app_ui/resources/js/intent.js
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import * as metrics from './metrics.js';
-import {ChromeHelper} from './mojo/chrome_helper.js';
-// eslint-disable-next-line no-unused-vars
-import {Mode} from './type.js';
-
-/**
- * Thrown when fails to parse intent url.
- */
-export class ParseError extends Error {
-  /**
-   * @param {!URL} url Intent url.
-   */
-  constructor(url) {
-    super(`Failed to parse intent url ${url}`);
-    /**
-     * @const {!URL} url
-     * @private
-     */
-    this.url_ = url;
-
-    this.name = this.constructor.name;
-  }
-}
-
-/**
- * Intent from ARC++.
- */
-export class Intent {
-  /**
-   * @param {!URL} url
-   * @param {number} intentId
-   * @param {!Mode} mode
-   * @param {boolean} shouldHandleResult
-   * @param {boolean} shouldDownScale
-   * @param {boolean} isSecure
-   * @private
-   */
-  constructor(
-      url, intentId, mode, shouldHandleResult, shouldDownScale, isSecure) {
-    /**
-     * @const {!URL}
-     */
-    this.url = url;
-
-    /**
-     * @const {number}
-     */
-    this.intentId = intentId;
-
-    /**
-     * Capture mode of intent.
-     * @const {!Mode}
-     */
-    this.mode = mode;
-
-    /**
-     * Whether the intent should return with the captured result.
-     * @const {boolean}
-     */
-    this.shouldHandleResult = shouldHandleResult;
-
-    /**
-     * Whether the captured image should be down-scaled.
-     * @const {boolean}
-     */
-    this.shouldDownScale = shouldDownScale;
-
-    /**
-     * If the intent is launched when the device is under secure mode.
-     * @const {boolean}
-     */
-    this.isSecure = isSecure;
-
-    /**
-     * Flag for avoiding intent being resolved by foreground and background
-     * twice.
-     * @type {boolean}
-     * @private
-     */
-    this.done_ = false;
-  }
-
-  /**
-   * @return {!ChromeHelper}
-   * @private
-   */
-  get chromeHelper_() {
-    return ChromeHelper.getInstance();
-  }
-
-  /**
-   * Whether intent has been finished or canceled.
-   * @return {boolean}
-   */
-  get done() {
-    return this.done_;
-  }
-
-  /**
-   * Notifies ARC++ to finish the intent.
-   * @return {!Promise}
-   */
-  async finish() {
-    if (this.done) {
-      return;
-    }
-    this.done_ = true;
-    await this.chromeHelper_.finish(this.intentId);
-    this.logResult(metrics.IntentResultType.CONFIRMED);
-  }
-
-  /**
-   * Notifies ARC++ to append data to the intent result.
-   * @param {!Uint8Array} data The data to be appended to intent result.
-   * @return {!Promise}
-   */
-  async appendData(data) {
-    if (this.done) {
-      return;
-    }
-    await this.chromeHelper_.appendData(this.intentId, data);
-  }
-
-  /**
-   * Notifies ARC++ to clear appended intent result data.
-   * @return {!Promise}
-   */
-  async clearData() {
-    if (this.done) {
-      return;
-    }
-    await this.chromeHelper_.clearData(this.intentId);
-  }
-
-  /**
-   * Logs the intent result to metrics.
-   * @param {!metrics.IntentResultType} result
-   */
-  logResult(result) {
-    metrics.sendIntentEvent({
-      intent: this,
-      result,
-    });
-  }
-
-  /**
-   * @param {!URL} url Url passed along with app launch event.
-   * @param {!Mode} mode Mode for the intent
-   * @return {!Intent} Created intent object. Returns null if input is not a
-   *     valid intent url.
-   * @throws {!ParseError}
-   */
-  static create(url, mode) {
-    const params = url.searchParams;
-    const getBool = (key) => params.get(key) === '1';
-    const param = params.get('intentId');
-    if (param === null) {
-      throw new ParseError(url);
-    }
-    const intentId = parseInt(param, 10);
-
-    return new Intent(
-        url, intentId, mode, getBool('shouldHandleResult'),
-        getBool('shouldDownScale'), getBool('isSecure'));
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/intent.ts b/ash/webui/camera_app_ui/resources/js/intent.ts
new file mode 100644
index 0000000..ca0064f
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/intent.ts
@@ -0,0 +1,121 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import * as metrics from './metrics.js';
+import {ChromeHelper} from './mojo/chrome_helper.js';
+import {Mode} from './type.js';
+
+/**
+ * Thrown when fails to parse intent url.
+ */
+export class ParseError extends Error {
+  /**
+   * @param url Intent url.
+   */
+  constructor(url: URL) {
+    super(`Failed to parse intent url ${url}`);
+
+    this.name = this.constructor.name;
+  }
+}
+
+/**
+ * Intent from ARC++.
+ */
+export class Intent {
+  /**
+   * Flag for avoiding intent being resolved by foreground and background
+   * twice.
+   */
+  private doneInternal = false;
+
+  /**
+   * @param mode Capture mode of intent.
+   * @param shouldHandleResult Whether the intent should return with the
+   *     captured result.
+   * @param shouldDownScale Whether the captured image should be down-scaled.
+   * @param isSecure If the intent is launched when the device is under secure
+   *     mode.
+   */
+  private constructor(
+      readonly url: URL,
+      readonly intentId: number,
+      readonly mode: Mode,
+      readonly shouldHandleResult: boolean,
+      readonly shouldDownScale: boolean,
+      readonly isSecure: boolean,
+  ) {}
+
+  /**
+   * Whether intent has been finished or canceled.
+   */
+  get done(): boolean {
+    return this.doneInternal;
+  }
+
+  /**
+   * Notifies ARC++ to finish the intent.
+   */
+  async finish(): Promise<void> {
+    if (this.doneInternal) {
+      return;
+    }
+    this.doneInternal = true;
+    await ChromeHelper.getInstance().finish(this.intentId);
+    this.logResult(metrics.IntentResultType.CONFIRMED);
+  }
+
+  /**
+   * Notifies ARC++ to append data to the intent result.
+   * @param data The data to be appended to intent result.
+   */
+  async appendData(data: Uint8Array): Promise<void> {
+    if (this.doneInternal) {
+      return;
+    }
+    await ChromeHelper.getInstance().appendData(this.intentId, data);
+  }
+
+  /**
+   * Notifies ARC++ to clear appended intent result data.
+   */
+  async clearData(): Promise<void> {
+    if (this.doneInternal) {
+      return;
+    }
+    await ChromeHelper.getInstance().clearData(this.intentId);
+  }
+
+  /**
+   * Logs the intent result to metrics.
+   */
+  logResult(result: metrics.IntentResultType): void {
+    metrics.sendIntentEvent({
+      intent: this,
+      result,
+    });
+  }
+
+  /**
+   * @param url Url passed along with app launch event.
+   * @param mode Mode for the intent
+   * @return Created intent object.
+   */
+  static create(url: URL, mode: Mode): Intent {
+    const params = url.searchParams;
+    const getBool = (key) => params.get(key) === '1';
+    const param = params.get('intentId');
+    if (param === null) {
+      throw new ParseError(url);
+    }
+    const intentId = Number(param);
+    if (!Number.isInteger(intentId)) {
+      throw new ParseError(url);
+    }
+
+    return new Intent(
+        url, intentId, mode, getBool('shouldHandleResult'),
+        getBool('shouldDownScale'), getBool('isSecure'));
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/js.gni b/ash/webui/camera_app_ui/resources/js/js.gni
index ff21391..b041343 100644
--- a/ash/webui/camera_app_ui/resources/js/js.gni
+++ b/ash/webui/camera_app_ui/resources/js/js.gni
@@ -22,14 +22,14 @@
   "focus_ring.ts",
   "gallerybutton.ts",
   "geometry.ts",
-  "h264.js",
+  "h264.ts",
   "i18n_string.ts",
   "init.ts",
-  "intent.js",
+  "intent.ts",
   "lib/comlink.ts",
   "lib/comlink_protocol.ts",
   "lib/ffmpeg.js",
-  "main.js",
+  "main.ts",
   "metrics.ts",
   "models/async_interval.ts",
   "models/async_writer.ts",
@@ -43,29 +43,28 @@
   "models/idb.ts",
   "models/lazy_directory_entry.ts",
   "models/load_time_data.ts",
-  "models/local_storage.js",
+  "models/local_storage.ts",
   "models/result_saver.ts",
   "models/video_saver.ts",
   "mojo/chrome_helper.ts",
   "mojo/device_operator.js",
-  "mojo/image_capture.js",
+  "mojo/image_capture.ts",
   "mojo/type.ts",
   "mojo/util.ts",
-  "nav.js",
+  "nav.ts",
   "new_feature_toast.js",
   "perf.ts",
   "snackbar.ts",
   "sound.ts",
   "state.ts",
   "test_bridge.ts",
-  "thumbnailer.js",
+  "thumbnailer.ts",
   "timer.ts",
   "toast.ts",
   "tooltip.ts",
   "type.ts",
   "unload.ts",
-  "untrusted_ga_helper.js",
-  "untrusted_helper_interfaces.js",
+  "untrusted_ga_helper.ts",
   "untrusted_script_loader.ts",
   "untrusted_video_processor_helper.ts",
   "util.ts",
@@ -95,7 +94,7 @@
   "views/view.ts",
   "views/warning.js",
   "waitable_event.ts",
-  "window_controller.js",
+  "window_controller.ts",
 ]
 
 no_compile_js_files = [ "lib/analytics.js" ]
diff --git a/ash/webui/camera_app_ui/resources/js/main.js b/ash/webui/camera_app_ui/resources/js/main.ts
similarity index 80%
rename from ash/webui/camera_app_ui/resources/js/main.js
rename to ash/webui/camera_app_ui/resources/js/main.ts
index 0e1b3c5..223054e1 100644
--- a/ash/webui/camera_app_ui/resources/js/main.js
+++ b/ash/webui/camera_app_ui/resources/js/main.ts
@@ -57,75 +57,49 @@
  * Creates the Camera App main object.
  */
 export class App {
-  /**
-   * @param {{
-   *     perfLogger: !PerfLogger,
-   *     intent: ?Intent,
-   *     facing: ?Facing,
-   *     mode: ?Mode,
-   * }} params
-   * @public
-   */
-  constructor({perfLogger, intent, facing, mode: defaultMode}) {
-    /**
-     * @type {!PerfLogger}
-     * @private
-     */
-    this.perfLogger_ = perfLogger;
+  private perfLogger: PerfLogger;
+  private intent: Intent|null;
+  private photoPreferrer: PhotoConstraintsPreferrer;
+  private videoPreferrer: VideoConstraintsPreferrer;
+  private infoUpdater: DeviceInfoUpdater;
+  private galleryButton = new GalleryButton();
+  private cameraView: Camera;
 
-    /**
-     * @type {?Intent}
-     * @private
-     */
-    this.intent_ = intent;
+  constructor({perfLogger, intent, facing, mode: defaultMode}: {
+    perfLogger: PerfLogger,
+    intent: Intent|null,
+    facing: Facing|null,
+    mode: Mode|null,
+  }) {
+    this.perfLogger = perfLogger;
 
-    /**
-     * @type {!PhotoConstraintsPreferrer}
-     * @private
-     */
-    this.photoPreferrer_ =
-        new PhotoConstraintsPreferrer(() => this.cameraView_.start());
+    this.intent = intent;
 
-    /**
-     * @type {!VideoConstraintsPreferrer}
-     * @private
-     */
-    this.videoPreferrer_ =
-        new VideoConstraintsPreferrer(() => this.cameraView_.start());
+    this.photoPreferrer =
+        new PhotoConstraintsPreferrer(() => this.cameraView.start());
 
-    /**
-     * @type {!DeviceInfoUpdater}
-     * @private
-     */
-    this.infoUpdater_ =
-        new DeviceInfoUpdater(this.photoPreferrer_, this.videoPreferrer_);
+    this.videoPreferrer =
+        new VideoConstraintsPreferrer(() => this.cameraView.start());
 
-    /**
-     * @type {!GalleryButton}
-     * @private
-     */
-    this.galleryButton_ = new GalleryButton();
+    this.infoUpdater =
+        new DeviceInfoUpdater(this.photoPreferrer, this.videoPreferrer);
 
-    /**
-     * @type {!Camera}
-     * @private
-     */
-    this.cameraView_ = (() => {
+    this.cameraView = (() => {
       const mode = defaultMode ?? Mode.PHOTO;
-      if (this.intent_ !== null && this.intent_.shouldHandleResult) {
+      if (this.intent !== null && this.intent.shouldHandleResult) {
         state.set(state.State.SHOULD_HANDLE_INTENT_RESULT, true);
         return new CameraIntent(
-            this.intent_, this.infoUpdater_, this.photoPreferrer_,
-            this.videoPreferrer_, mode, this.perfLogger_);
+            this.intent, this.infoUpdater, this.photoPreferrer,
+            this.videoPreferrer, mode, this.perfLogger);
       } else {
         return new Camera(
-            this.galleryButton_, this.infoUpdater_, this.photoPreferrer_,
-            this.videoPreferrer_, mode, this.perfLogger_, facing);
+            this.galleryButton, this.infoUpdater, this.photoPreferrer,
+            this.videoPreferrer, mode, this.perfLogger, facing);
       }
     })();
 
     document.body.addEventListener(
-        'keydown', (event) => this.onKeyPressed_(event));
+        'keydown', (event) => this.onKeyPressed(event));
 
     // Disable the zoom in-out gesture which is triggered by wheel and pinch on
     // trackpad.
@@ -136,14 +110,13 @@
     }, {passive: false, capture: true});
 
     util.setupI18nElements(document.body);
-    this.setupToggles_();
-    this.setupEffect_();
+    this.setupToggles();
+    this.setupEffect();
     focusRing.initialize();
 
-
     // Set up views navigation by their DOM z-order.
     nav.setup([
-      this.cameraView_,
+      this.cameraView,
       new Warning(),
       new Dialog(ViewName.MESSAGE_DIALOG),
       new View(ViewName.SPLASH),
@@ -154,9 +127,8 @@
 
   /**
    * Sets up toggles (checkbox and radio) by data attributes.
-   * @private
    */
-  setupToggles_() {
+  private setupToggles() {
     dom.getAll('input', HTMLInputElement).forEach((element) => {
       element.addEventListener('keypress', (event) => {
         const e = assertInstanceof(event, KeyboardEvent);
@@ -208,9 +180,8 @@
 
   /**
    * Sets up visual effect for all applicable elements.
-   * @private
    */
-  setupEffect_() {
+  private setupEffect() {
     dom.getAll('.inkdrop', HTMLElement)
         .forEach((el) => util.setInkdropEffect(el));
 
@@ -238,10 +209,8 @@
 
   /**
    * Starts the app by loading the model and opening the camera-view.
-   * @param {!metrics.LaunchType} launchType
-   * @return {!Promise}
    */
-  async start(launchType) {
+  async start(launchType: metrics.LaunchType): Promise<void> {
     document.documentElement.dir = loadTimeData.getTextDirection();
     try {
       await filesystem.initialize();
@@ -256,8 +225,8 @@
       // 3. Other intents
       //      (intent !== null && shouldHandleResult === true)
       // Only (1) and (2) will show gallery button on the UI.
-      if (this.intent_ === null || !this.intent_.shouldHandleResult) {
-        this.galleryButton_.initialize(cameraDir);
+      if (this.intent === null || !this.intent.shouldHandleResult) {
+        this.galleryButton.initialize(cameraDir);
       }
     } catch (error) {
       reportError(ErrorType.FILE_SYSTEM_FAILURE, ErrorLevel.ERROR, error);
@@ -267,8 +236,8 @@
     const showWindow = (async () => {
       // For intent only requiring open camera with specific mode without
       // returning the capture result, finish it directly.
-      if (this.intent_ !== null && !this.intent_.shouldHandleResult) {
-        this.intent_.finish();
+      if (this.intent !== null && !this.intent.shouldHandleResult) {
+        this.intent.finish();
       }
     })();
 
@@ -279,7 +248,7 @@
       } else {
         // CCA must get camera usage for completing its initialization when
         // first launched.
-        await this.cameraView_.initialize();
+        await this.cameraView.initialize();
         notifyCameraResourceReady();
         cameraResourceInitialized.signal();
       }
@@ -293,10 +262,10 @@
 
     const startCamera = (async () => {
       await cameraResourceInitialized.wait();
-      const isSuccess = await this.cameraView_.start();
+      const isSuccess = await this.cameraView.start();
 
       if (isSuccess) {
-        const aspectRatio = this.cameraView_.getPreviewAspectRatio();
+        const aspectRatio = this.cameraView.getPreviewAspectRatio();
         const {width, height} = getDefaultWindowSize(aspectRatio);
         window.resizeTo(width, height);
       }
@@ -305,9 +274,9 @@
       nav.open(ViewName.CAMERA);
 
       const windowCreationTime = window['windowCreationTime'];
-      this.perfLogger_.start(
+      this.perfLogger.start(
           PerfEvent.LAUNCHING_FROM_WINDOW_CREATION, windowCreationTime);
-      this.perfLogger_.stop(
+      this.perfLogger.stop(
           PerfEvent.LAUNCHING_FROM_WINDOW_CREATION, {hasError: !isSuccess});
       if (appWindow !== null) {
         appWindow.onAppLaunched();
@@ -315,7 +284,7 @@
     })();
 
     const preloadImages = (async () => {
-      const loadImage = (url) => new Promise((resolve, reject) => {
+      const loadImage = (url) => new Promise<void>((resolve, reject) => {
         const link = document.createElement('link');
         link.rel = 'preload';
         link.as = 'image';
@@ -338,60 +307,56 @@
     })();
 
     metrics.sendLaunchEvent({launchType});
-    return Promise.all([showWindow, startCamera, preloadImages]);
+    await Promise.all([showWindow, startCamera, preloadImages]);
   }
 
   /**
    * Handles pressed keys.
-   * @param {!Event} event Key press event.
-   * @private
+   * @param event Key press event.
    */
-  onKeyPressed_(event) {
+  private onKeyPressed(event: Event) {
     tooltip.hide();  // Hide shown tooltip on any keypress.
     nav.onKeyPressed(assertInstanceof(event, KeyboardEvent));
   }
 
   /**
    * Suspends app and hides app window.
-   * @return {!Promise}
    */
-  async suspend() {
+  async suspend(): Promise<void> {
     state.set(state.State.SUSPEND, true);
-    await this.cameraView_.start();
+    await this.cameraView.start();
     nav.open(ViewName.WARNING, WarningType.CAMERA_PAUSED);
   }
 
   /**
    * Resumes app from suspension and shows app window.
    */
-  resume() {
+  resume(): void {
     state.set(state.State.SUSPEND, false);
     nav.close(ViewName.WARNING, WarningType.CAMERA_PAUSED);
   }
 
   /**
    * Begins to take photo or recording with the current options, e.g. timer.
-   * @param {!metrics.ShutterType} shutterType The shutter is triggered by which
-   *     shutter type.
-   * @return {?Promise<void>} Promise resolved when take action completes.
+   * @param shutterType The shutter is triggered by which shutter type.
+   * @return Promise resolved when take action completes.
    *     Returns null if CCA can't start take action.
    */
-  beginTake(shutterType) {
-    return this.cameraView_.beginTake(shutterType);
+  beginTake(shutterType: metrics.ShutterType): Promise<void>|null {
+    return this.cameraView.beginTake(shutterType);
   }
 }
 
 /**
  * Parse search params in URL.
- * @return {{
- *     intent: ?Intent,
- *     facing: ?Facing,
- *     mode: ?Mode,
- *     openFrom: ?string,
- *     autoTake: boolean,
- * }}
  */
-function parseSearchParams() {
+function parseSearchParams(): {
+  intent: Intent|null,
+  facing: Facing|null,
+  mode: Mode|null,
+  openFrom: string|null,
+  autoTake: boolean
+} {
   const url = new URL(window.location.href);
   const params = url.searchParams;
 
@@ -399,7 +364,6 @@
 
   const mode = checkEnumVariant(Mode, params.get('mode'));
 
-  /** @type {?Intent} */
   const intent = (() => {
     if (params.get('intentId') === null) {
       return null;
@@ -416,9 +380,8 @@
 
 /**
  * Singleton of the App object.
- * @type {?App}
  */
-let instance = null;
+let instance: App|null = null;
 
 /**
  * Creates the App object and starts camera stream.
diff --git a/ash/webui/camera_app_ui/resources/js/metrics.ts b/ash/webui/camera_app_ui/resources/js/metrics.ts
index ca4bb342..22f6c1ba 100644
--- a/ash/webui/camera_app_ui/resources/js/metrics.ts
+++ b/ash/webui/camera_app_ui/resources/js/metrics.ts
@@ -16,7 +16,7 @@
   PerfInformation,
   Resolution,
 } from './type.js';
-import {GAHelperInterface} from './untrusted_helper_interfaces.js';
+import {GAHelperInterface} from './untrusted_ga_helper.js';
 import * as util from './util.js';
 import {WaitableEvent} from './waitable_event.js';
 
diff --git a/ash/webui/camera_app_ui/resources/js/models/local_storage.js b/ash/webui/camera_app_ui/resources/js/models/local_storage.js
deleted file mode 100644
index f02772a..0000000
--- a/ash/webui/camera_app_ui/resources/js/models/local_storage.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assertBoolean, assertInstanceof, assertString} from '../assert.js';
-
-/**
- * @param {string} key
- * @param {*} defaultValue
- * @return {*} The value in storage or defaultValue if not found.
- */
-function getHelper(key, defaultValue) {
-  const rawValue = window.localStorage.getItem(key);
-  if (rawValue === null) {
-    return defaultValue;
-  }
-  return JSON.parse(rawValue);
-}
-
-/**
- * @param {string} key
- * @param {!Object=} defaultValue
- * @return {!Object} The object in storage or defaultValue if not found.
- */
-export function getObject(key, defaultValue = {}) {
-  return assertInstanceof(getHelper(key, defaultValue), Object);
-}
-
-/**
- * @param {string} key
- * @param {string=} defaultValue
- * @return {string} The string in storage or defaultValue if not found.
- */
-export function getString(key, defaultValue = '') {
-  return assertString(getHelper(key, defaultValue));
-}
-/**
- * @param {string} key
- * @param {boolean=} defaultValue
- * @return {boolean} The boolean in storage or defaultValue if not found.
- */
-export function getBool(key, defaultValue = false) {
-  return assertBoolean(getHelper(key, defaultValue));
-}
-
-/**
- * @param {string} key
- * @param {*} value
- */
-export function set(key, value) {
-  window.localStorage.setItem(key, JSON.stringify(value));
-}
-
-/**
- * @param {...string} keys
- */
-export function remove(...keys) {
-  for (const key of keys) {
-    window.localStorage.removeItem(key);
-  }
-}
-
-/**
- * Clears all the items in the local storage.
- */
-export function clear() {
-  window.localStorage.clear();
-}
diff --git a/ash/webui/camera_app_ui/resources/js/models/local_storage.ts b/ash/webui/camera_app_ui/resources/js/models/local_storage.ts
new file mode 100644
index 0000000..ef090df
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/models/local_storage.ts
@@ -0,0 +1,56 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assertBoolean, assertInstanceof, assertString} from '../assert.js';
+
+/**
+ * @return The value in storage or defaultValue if not found.
+ */
+function getHelper(key: string, defaultValue: unknown): unknown {
+  const rawValue = window.localStorage.getItem(key);
+  if (rawValue === null) {
+    return defaultValue;
+  }
+  return JSON.parse(rawValue);
+}
+
+/**
+ * @return The object in storage or defaultValue if not found.
+ */
+export function getObject<T>(
+    key: string, defaultValue: Record<string, T> = {}): Record<string, T> {
+  // TODO(pihsun): actually verify the type at runtime here?
+  return assertInstanceof(getHelper(key, defaultValue), Object) as
+      Record<string, T>;
+}
+
+/**
+ * @return The string in storage or defaultValue if not found.
+ */
+export function getString(key: string, defaultValue = ''): string {
+  return assertString(getHelper(key, defaultValue));
+}
+/**
+ * @return The boolean in storage or defaultValue if not found.
+ */
+export function getBool(key: string, defaultValue = false): boolean {
+  return assertBoolean(getHelper(key, defaultValue));
+}
+
+export function set(key: string, value: unknown): void {
+  window.localStorage.setItem(key, JSON.stringify(value));
+}
+
+export function remove(...keys: string[]): void {
+  for (const key of keys) {
+    window.localStorage.removeItem(key);
+  }
+}
+
+/**
+ * Clears all the items in the local storage.
+ */
+export function clear(): void {
+  window.localStorage.clear();
+}
diff --git a/ash/webui/camera_app_ui/resources/js/models/video_saver.ts b/ash/webui/camera_app_ui/resources/js/models/video_saver.ts
index 0110b57c..b3e406d2 100644
--- a/ash/webui/camera_app_ui/resources/js/models/video_saver.ts
+++ b/ash/webui/camera_app_ui/resources/js/models/video_saver.ts
@@ -8,7 +8,9 @@
   MimeType,
   Resolution,
 } from '../type.js';
-import {VideoProcessorHelperInterface} from '../untrusted_helper_interfaces.js';
+import {
+  VideoProcessorHelperInterface,
+} from '../untrusted_video_processor_helper.js';
 import * as util from '../util.js';
 
 import {AsyncWriter} from './async_writer.js';
diff --git a/ash/webui/camera_app_ui/resources/js/mojo/image_capture.js b/ash/webui/camera_app_ui/resources/js/mojo/image_capture.js
deleted file mode 100644
index 1da0af0..0000000
--- a/ash/webui/camera_app_ui/resources/js/mojo/image_capture.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {bitmapToJpegBlob} from '../util.js';
-import {WaitableEvent} from '../waitable_event.js';
-
-import {DeviceOperator} from './device_operator.js';
-// eslint-disable-next-line no-unused-vars
-import {Effect} from './type.js';
-import {closeEndpoint} from './util.js';
-
-/**
- * Creates the wrapper of JS image-capture and Mojo image-capture.
- */
-export class CrosImageCapture {
-  /**
-   * @param {!MediaStreamTrack} videoTrack A video track whose still images will
-   *     be taken.
-   */
-  constructor(videoTrack) {
-    /**
-     * The id of target media device.
-     * @type {string}
-     * @private
-     */
-    this.deviceId_ = videoTrack.getSettings().deviceId;
-
-    /**
-     * The standard ImageCapture object.
-     * @type {!ImageCapture}
-     * @private
-     */
-    this.capture_ = new ImageCapture(videoTrack);
-  }
-
-  /**
-   * Gets the photo capabilities with the available options/effects.
-   * @return {!Promise<!PhotoCapabilities>} Promise
-   *     for the result.
-   */
-  async getPhotoCapabilities() {
-    return this.capture_.getPhotoCapabilities();
-  }
-
-  /**
-   * Takes single or multiple photo(s) with the specified settings and effects.
-   * The amount of result photo(s) depends on the specified settings and
-   * effects, and the first promise in the returned array will always resolve
-   * with the unreprocessed photo. The returned array will be resolved once it
-   * received the shutter event.
-   * @param {!PhotoSettings} photoSettings Photo settings for ImageCapture's
-   *     takePhoto().
-   * @param {!Array<!Effect>=} photoEffects Photo effects to be
-   *     applied.
-   * @return {!Promise<!Array<!Promise<!Blob>>>} A promise of the array
-   *     containing promise of each blob result.
-   */
-  async takePhoto(photoSettings, photoEffects = []) {
-    const deviceOperator = await DeviceOperator.getInstance();
-    if (deviceOperator === null && photoEffects.length > 0) {
-      throw new Error('Applying effects is not supported on this device');
-    }
-
-    const takes =
-        await deviceOperator.setReprocessOptions(this.deviceId_, photoEffects);
-    if (deviceOperator !== null) {
-      const onShutterDone = new WaitableEvent();
-      const shutterObserver =
-          await deviceOperator.addShutterObserver(this.deviceId_, () => {
-            onShutterDone.signal();
-          });
-      takes.unshift(this.capture_.takePhoto(photoSettings));
-      await onShutterDone.wait();
-      closeEndpoint(shutterObserver);
-      return takes;
-    } else {
-      takes.unshift(this.capture_.takePhoto(photoSettings));
-      return takes;
-    }
-  }
-
-  /**
-   * @return {!Promise<!ImageBitmap>}
-   */
-  grabFrame() {
-    return this.capture_.grabFrame();
-  }
-
-  /**
-   * @return {!Promise<!Blob>} Returns jpeg blob of the grabbed frame.
-   */
-  async grabJpegFrame() {
-    const bitmap = await this.capture_.grabFrame();
-    return bitmapToJpegBlob(bitmap);
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/mojo/image_capture.ts b/ash/webui/camera_app_ui/resources/js/mojo/image_capture.ts
new file mode 100644
index 0000000..5aee0f9
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/mojo/image_capture.ts
@@ -0,0 +1,88 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {bitmapToJpegBlob} from '../util.js';
+import {WaitableEvent} from '../waitable_event.js';
+
+import {DeviceOperator} from './device_operator.js';
+import {Effect} from './type.js';
+import {closeEndpoint} from './util.js';
+
+/**
+ * Creates the wrapper of JS image-capture and Mojo image-capture.
+ */
+export class CrosImageCapture {
+  /**
+   * The id of target media device.
+   */
+  private readonly deviceId: string;
+
+  /**
+   * The standard ImageCapture object.
+   */
+  private readonly capture: ImageCapture;
+
+  /**
+   * @param videoTrack A video track whose still images will be taken.
+   */
+  constructor(videoTrack: MediaStreamTrack) {
+    this.deviceId = videoTrack.getSettings().deviceId;
+    this.capture = new ImageCapture(videoTrack);
+  }
+
+  /**
+   * Gets the photo capabilities with the available options/effects.
+   * @return Promise for the result.
+   */
+  async getPhotoCapabilities(): Promise<PhotoCapabilities> {
+    return this.capture.getPhotoCapabilities();
+  }
+
+  /**
+   * Takes single or multiple photo(s) with the specified settings and effects.
+   * The amount of result photo(s) depends on the specified settings and
+   * effects, and the first promise in the returned array will always resolve
+   * with the unreprocessed photo. The returned array will be resolved once it
+   * received the shutter event.
+   * @param photoSettings Photo settings for ImageCapture's takePhoto().
+   * @param photoEffects Photo effects to be applied.
+   * @return A promise of the array containing promise of each blob result.
+   */
+  async takePhoto(photoSettings: PhotoSettings, photoEffects: Effect[] = []):
+      Promise<Array<Promise<Blob>>> {
+    const deviceOperator = await DeviceOperator.getInstance();
+    if (deviceOperator === null && photoEffects.length > 0) {
+      throw new Error('Applying effects is not supported on this device');
+    }
+
+    const takes =
+        await deviceOperator.setReprocessOptions(this.deviceId, photoEffects);
+    if (deviceOperator !== null) {
+      const onShutterDone = new WaitableEvent();
+      const shutterObserver =
+          await deviceOperator.addShutterObserver(this.deviceId, () => {
+            onShutterDone.signal();
+          });
+      takes.unshift(this.capture.takePhoto(photoSettings));
+      await onShutterDone.wait();
+      closeEndpoint(shutterObserver);
+      return takes;
+    } else {
+      takes.unshift(this.capture.takePhoto(photoSettings));
+      return takes;
+    }
+  }
+
+  grabFrame(): Promise<ImageBitmap> {
+    return this.capture.grabFrame();
+  }
+
+  /**
+   * @return Returns jpeg blob of the grabbed frame.
+   */
+  async grabJpegFrame(): Promise<Blob> {
+    const bitmap = await this.capture.grabFrame();
+    return bitmapToJpegBlob(bitmap);
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/nav.js b/ash/webui/camera_app_ui/resources/js/nav.ts
similarity index 72%
rename from ash/webui/camera_app_ui/resources/js/nav.js
rename to ash/webui/camera_app_ui/resources/js/nav.ts
index 32d543c..faf167a 100644
--- a/ash/webui/camera_app_ui/resources/js/nav.js
+++ b/ash/webui/camera_app_ui/resources/js/nav.ts
@@ -7,50 +7,37 @@
 import {toggleExpertMode} from './expert.js';
 import * as state from './state.js';
 import * as toast from './toast.js';
-// eslint-disable-next-line no-unused-vars
 import {ViewName} from './type.js';
 import * as util from './util.js';
-// eslint-disable-next-line no-unused-vars
-import {View} from './views/view.js';
+import {EnterOptions, View} from './views/view.js';
 import {windowController} from './window_controller.js';
 
 /**
  * All views stacked in ascending z-order (DOM order) for navigation, and only
  * the topmost visible view is active (clickable/focusable).
- * @type {!Array<!View>}
  */
-let allViews = [];
+let allViews: View[] = [];
 
 /**
  * Index of the current topmost visible view in the stacked views.
- * @type {number}
  */
 let topmostIndex = -1;
 
-// Disable checking jsdoc generator annotation which is incompatible with
-// closure compiler: JSdoc use @generator and @yields while closure compiler use
-// @return {Generator}.
-/* eslint-disable valid-jsdoc */
-
 /**
  * Gets view and all recursive subviews.
- * @param {!View} view
- * @return {!Generator<!View>}
  */
-function* getRecursiveViews(view) {
+function* getRecursiveViews(view: View): Generator<View> {
   yield view;
   for (const subview of view.getSubViews()) {
     yield* getRecursiveViews(subview);
   }
 }
 
-/* eslint-enable valid-jsdoc */
-
 /**
  * Sets up navigation for all views, e.g. camera-view, dialog-view, etc.
- * @param {!Array<!View>} views All views in ascending z-order.
+ * @param views All views in ascending z-order.
  */
-export function setup(views) {
+export function setup(views: View[]): void {
   allViews = views.flatMap((v) => [...getRecursiveViews(v)]);
   // Manage all tabindex usages in for navigation.
   document.body.addEventListener('keydown', (event) => {
@@ -65,9 +52,9 @@
 
 /**
  * Activates the view to be focusable.
- * @param {number} index Index of the view.
+ * @param index Index of the view.
  */
-function activate(index) {
+function activate(index: number) {
   // Restore the view's child elements' tabindex and then focus the view.
   const view = allViews[index];
   view.root.setAttribute('aria-hidden', 'false');
@@ -84,9 +71,9 @@
 
 /**
  * Deactivates the view to be unfocusable.
- * @param {number} index Index of the view.
+ * @param index Index of the view.
  */
-function deactivate(index) {
+function deactivate(index: number) {
   const view = allViews[index];
   view.root.setAttribute('aria-hidden', 'true');
   dom.getAllFrom(view.root, '[tabindex]', HTMLElement).forEach((element) => {
@@ -101,20 +88,20 @@
 
 /**
  * Checks if the view is already shown.
- * @param {number} index Index of the view.
- * @return {boolean} Whether the view is shown or not.
+ * @param index Index of the view.
+ * @return Whether the view is shown or not.
  */
-function isShown(index) {
+function isShown(index: number): boolean {
   return state.get(allViews[index].name);
 }
 
 /**
  * Shows the view indexed in the stacked views and activates the view only if
  * it becomes the topmost visible view.
- * @param {number} index Index of the view.
- * @return {!View} View shown.
+ * @param index Index of the view.
+ * @return View shown.
  */
-function show(index) {
+function show(index: number): View {
   const view = allViews[index];
   if (!isShown(index)) {
     state.set(view.name, true);
@@ -132,9 +119,9 @@
 
 /**
  * Finds the next topmost visible view in the stacked views.
- * @return {number} Index of the view found; otherwise, -1.
+ * @return Index of the view found; otherwise, -1.
  */
-function findNextTopmostIndex() {
+function findNextTopmostIndex(): number {
   for (let i = topmostIndex - 1; i >= 0; i--) {
     if (isShown(i)) {
       return i;
@@ -146,9 +133,9 @@
 /**
  * Hides the view indexed in the stacked views and deactivate the view if it was
  * the topmost visible view.
- * @param {number} index Index of the view.
+ * @param index Index of the view.
  */
-function hide(index) {
+function hide(index: number) {
   if (index === topmostIndex) {
     deactivate(index);
     const next = findNextTopmostIndex();
@@ -162,43 +149,43 @@
 
 /**
  * Finds the view by its name in the stacked views.
- * @param {!ViewName} name View name.
- * @return {number} Index of the view found; otherwise, -1.
+ * @param name View name.
+ * @return Index of the view found; otherwise, -1.
  */
-function findIndex(name) {
+function findIndex(name: ViewName): number {
   return allViews.findIndex((view) => view.name === name);
 }
 
 /**
  * Opens a navigation session of the view; shows the view before entering it and
  * hides the view after leaving it for the ended session.
- * @param {!ViewName} name View name.
- * @param {...*} args Optional rest parameters for entering the view.
- * @return {!Promise<*>} Promise for the operation or result.
+ * @param name View name.
+ * @param args Optional rest parameters for entering the view.
+ * @return Promise for the operation or result.
  */
-export function open(name, ...args) {
+export function open(name: ViewName, options?: EnterOptions): Promise<unknown> {
   const index = findIndex(name);
-  return show(index).enter(...args).finally(() => {
+  return show(index).enter(options).finally(() => {
     hide(index);
   });
 }
 
 /**
  * Closes the current navigation session of the view by leaving it.
- * @param {!ViewName} name View name.
- * @param {*=} condition Optional condition for leaving the view.
- * @return {boolean} Whether successfully leaving the view or not.
+ * @param name View name.
+ * @param condition Optional condition for leaving the view.
+ * @return Whether successfully leaving the view or not.
  */
-export function close(name, condition) {
+export function close(name: ViewName, condition?: unknown): boolean {
   const index = findIndex(name);
   return allViews[index].leave(condition);
 }
 
 /**
  * Handles key pressed event.
- * @param {!KeyboardEvent} event Key press event.
+ * @param event Key press event.
  */
-export function onKeyPressed(event) {
+export function onKeyPressed(event: KeyboardEvent): void {
   const key = util.getShortcutIdentifier(event);
   switch (key) {
     case 'BrowserBack':
@@ -235,7 +222,7 @@
 /**
  * Handles when the window state or size changed.
  */
-export function onWindowStatusChanged() {
+export function onWindowStatusChanged(): void {
   // All visible views need being relayout after window is resized or state
   // changed.
   for (let i = allViews.length - 1; i >= 0; i--) {
@@ -247,9 +234,8 @@
 
 /**
  * Returns whether the view is the top view above all shown view.
- * @param {!ViewName} name Name of the view
- * @return {boolean}
+ * @param name Name of the view
  */
-export function isTopMostView(name) {
+export function isTopMostView(name: ViewName): boolean {
   return topmostIndex === findIndex(name);
 }
diff --git a/ash/webui/camera_app_ui/resources/js/thumbnailer.js b/ash/webui/camera_app_ui/resources/js/thumbnailer.ts
similarity index 71%
rename from ash/webui/camera_app_ui/resources/js/thumbnailer.js
rename to ash/webui/camera_app_ui/resources/js/thumbnailer.ts
index 6c759b0..8de414e 100644
--- a/ash/webui/camera_app_ui/resources/js/thumbnailer.js
+++ b/ash/webui/camera_app_ui/resources/js/thumbnailer.ts
@@ -14,20 +14,19 @@
 
 /**
  * Converts the element to a jpeg blob by drawing it on a canvas.
- * @param {!CanvasImageSource} element Source element.
- * @param {number} width Canvas width.
- * @param {number} height Canvas height.
- * @return {!Promise<!Blob>} Converted jpeg blob.
- * @throws {!EmptyThumbnailError} Thrown when the data to generate thumbnail is
+ * @param element Source element.
+ * @param width Canvas width.
+ * @param height Canvas height.
+ * @return Converted jpeg blob.
+ * @throws {EmptyThumbnailError} Thrown when the data to generate thumbnail is
  *     empty.
  */
-async function elementToJpegBlob(element, width, height) {
+async function elementToJpegBlob(
+    element: CanvasImageSource, width: number, height: number): Promise<Blob> {
   const {canvas, ctx} = newDrawingCanvas({width, height});
   ctx.drawImage(element, 0, 0, width, height);
 
-  /**
-   * @type {!Uint8ClampedArray} A one-dimensional pixels array in RGBA order.
-   */
+  /** A one-dimensional pixels array in RGBA order. */
   const data = ctx.getImageData(0, 0, width, height).data;
   if (data.every((byte) => byte === 0)) {
     throw new EmptyThumbnailError();
@@ -40,15 +39,12 @@
 
 /**
  * Loads the blob into a <video> element.
- * @param {!Blob} blob
- * @return {!Promise<!HTMLVideoElement>}
- * @throws {!Error} Thrown when it fails to load video.
+ * @throws Thrown when it fails to load video.
  */
-async function loadVideoBlob(blob) {
+async function loadVideoBlob(blob: Blob): Promise<HTMLVideoElement> {
   const el = document.createElement('video');
   try {
-    /** @type {WaitableEvent<boolean>} */
-    const hasLoaded = new WaitableEvent();
+    const hasLoaded = new WaitableEvent<boolean>();
     el.addEventListener('error', () => {
       hasLoaded.signal(false);
     });
@@ -87,13 +83,11 @@
 
 /**
  * Loads the blob into an <img> element.
- * @param {!Blob} blob
- * @return {!Promise<!HTMLImageElement>}
  */
-async function loadImageBlob(blob) {
+async function loadImageBlob(blob: Blob): Promise<HTMLImageElement> {
   const el = new Image();
   try {
-    await new Promise((resolve, reject) => {
+    await new Promise<void>((resolve, reject) => {
       el.addEventListener('error', () => {
         reject(new Error('Failed to load image'));
       });
@@ -110,12 +104,13 @@
 
 /**
  * Creates a thumbnail of video by scaling the first frame to the target size.
- * @param {!Blob} blob Blob of video to be scaled.
- * @param {number} width Target width.
- * @param {number=} height Target height. Preserve the aspect ratio if not set.
- * @return {!Promise<!Blob>} Promise of the thumbnail as a jpeg blob.
+ * @param blob Blob of video to be scaled.
+ * @param width Target width.
+ * @param height Target height. Preserve the aspect ratio if not set.
+ * @return Promise of the thumbnail as a jpeg blob.
  */
-async function scaleVideo(blob, width, height = undefined) {
+async function scaleVideo(
+    blob: Blob, width: number, height?: number): Promise<Blob> {
   const el = await loadVideoBlob(blob);
   if (height === undefined) {
     height = Math.round(width * el.videoHeight / el.videoWidth);
@@ -125,12 +120,13 @@
 
 /**
  * Creates a thumbnail of image by scaling it to the target size.
- * @param {!Blob} blob Blob of image to be scaled.
- * @param {number} width Target width.
- * @param {number=} height Target height. Preserve the aspect ratio if not set.
- * @return {!Promise<!Blob>} Promise of the thumbnail as a jpeg blob.
+ * @param blob Blob of image to be scaled.
+ * @param width Target width.
+ * @param height Target height. Preserve the aspect ratio if not set.
+ * @return Promise of the thumbnail as a jpeg blob.
  */
-export async function scaleImage(blob, width, height = undefined) {
+export async function scaleImage(
+    blob: Blob, width: number, height?: number): Promise<Blob> {
   const el = await loadImageBlob(blob);
   if (height === undefined) {
     height = Math.round(width * el.naturalHeight / el.naturalWidth);
@@ -142,10 +138,6 @@
  * Failed to find image in pdf error.
  */
 class NoImageInPdfError extends Error {
-  /**
-   * @param {string=} message
-   * @public
-   */
   constructor(message = 'Failed to find image in pdf') {
     super(message);
     this.name = this.constructor.name;
@@ -154,21 +146,20 @@
 
 /**
  * Gets image embedded in a PDF.
- * @param {!Blob} blob Blob of PDF.
- * @return {!Promise<!Blob>} Promise resolved to image blob inside PDF.
+ * @param blob Blob of PDF.
+ * @return Promise resolved to image blob inside PDF.
  */
-async function getImageFromPdf(blob) {
+async function getImageFromPdf(blob: Blob): Promise<Blob> {
   const buf = await blob.arrayBuffer();
   const view = new Uint8Array(buf);
   let i = 0;
   /**
    * Finds |patterns| in view starting from |i| and moves |i| to end of found
    * pattern index.
-   * @param {...number} patterns
-   * @return {number} Returns begin of found pattern index or -1 for no further
-   *     pattern is found.
+   * @return Returns begin of found pattern index or -1 for no further pattern
+   *     is found.
    */
-  const findPattern = (...patterns) => {
+  const findPattern = (...patterns: number[]): number => {
     for (; i + patterns.length < view.length; i++) {
       if (patterns.every((b, index) => b === view[i + index])) {
         const ret = i;
@@ -218,11 +209,7 @@
  * Throws when the input blob type is not supported by thumbnailer.
  */
 class InvalidBlobTypeError extends Error {
-  /**
-   * @param {string} type
-   * @public
-   */
-  constructor(type) {
+  constructor(type: string) {
     super(`Invalid thumbnailer blob input type: ${type}`);
     this.name = this.constructor.name;
   }
@@ -231,16 +218,14 @@
 /**
  * For non-video type cover, keeps the original size as possible to support drag
  * drop share. Scales video type which don't support drag drop share.
- * @type {number}
  */
 const VIDEO_COVER_WIDTH = 240;
 
 /**
  * Extracts image blob from an arbitrary type of blob.
- * @param {!Blob} blob
- * @return {!Promise<!Blob>} Resolved to the image blob.
+ * @return Resolved to the image blob.
  */
-export async function extractImageFromBlob(blob) {
+export async function extractImageFromBlob(blob: Blob): Promise<Blob> {
   switch (blob.type) {
     case MimeType.GIF:
     case MimeType.JPEG:
diff --git a/ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.js b/ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.ts
similarity index 67%
rename from ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.js
rename to ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.ts
index 44323a5..f3497d1 100644
--- a/ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.js
+++ b/ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.ts
@@ -2,12 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// eslint-disable-next-line no-unused-vars
-import {GAHelperInterface} from './untrusted_helper_interfaces.js';
-
 /**
  * The GA library URL in trusted type.
- * @type {!TrustedScriptURL}
  */
 const gaLibraryURL = (() => {
   const staticUrlPolicy = trustedTypes.createPolicy(
@@ -17,13 +13,13 @@
 
 /**
  * Initializes GA for sending metrics.
- * @param {string} id The GA tracker ID to send metrics.
- * @param {string} clientId The GA client ID representing the current client.
- * @param {function(string): void} setClientIdCallback Callback to store
- *     client id.
- * @return {!Promise}
+ * @param id The GA tracker ID to send metrics.
+ * @param clientId The GA client ID representing the current client.
+ * @param setClientIdCallback Callback to store client id.
  */
-async function initGA(id, clientId, setClientIdCallback) {
+function initGA(
+    id: string, clientId: string,
+    setClientIdCallback: (clientId: string) => void): void {
   // GA initialization function which is mostly copied from
   // https://developers.google.com/analytics/devguides/collection/analyticsjs.
   (function(i, s, o, g, r) {
@@ -57,21 +53,24 @@
 
 /**
  * Sends event to GA.
- * @param {!UniversalAnalytics.FieldsObject} event Event to send.
- * @return {!Promise}
+ * @param event Event to send.
  */
-async function sendGAEvent(event) {
+function sendGAEvent(event: UniversalAnalytics.FieldsObject): void {
   window.ga('send', 'event', event);
 }
 
 /**
  * Sets if GA can send metrics.
- * @param {string} id The GA tracker ID.
- * @param {boolean} enabled True if the metrics is enabled.
- * @return {!Promise}
+ * @param id The GA tracker ID.
+ * @param enabled True if the metrics is enabled.
  */
-async function setMetricsEnabled(id, enabled) {
+function setMetricsEnabled(id: string, enabled: boolean): void {
   window[`ga-disable-${id}`] = !enabled;
 }
 
-export /** !GAHelperInterface */ {initGA, sendGAEvent, setMetricsEnabled};
+export interface GAHelperInterface {
+  initGA: typeof initGA;
+  sendGAEvent: typeof sendGAEvent;
+  setMetricsEnabled: typeof setMetricsEnabled;
+}
+export {initGA, sendGAEvent, setMetricsEnabled};
diff --git a/ash/webui/camera_app_ui/resources/js/untrusted_helper_interfaces.js b/ash/webui/camera_app_ui/resources/js/untrusted_helper_interfaces.js
deleted file mode 100644
index dfbf299..0000000
--- a/ash/webui/camera_app_ui/resources/js/untrusted_helper_interfaces.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{
- *   initGA: function(string, string, function(string): void): !Promise,
- *   sendGAEvent: function(!UniversalAnalytics.FieldsObject): !Promise,
- *   setMetricsEnabled: function(string, boolean): !Promise,
- * }}
- */
-export let GAHelperInterface;
-
-/**
- * @typedef {{
- *   connectToWorker: function(!MessagePort): !Promise,
- * }}
- */
-export let VideoProcessorHelperInterface;
diff --git a/ash/webui/camera_app_ui/resources/js/untrusted_video_processor_helper.ts b/ash/webui/camera_app_ui/resources/js/untrusted_video_processor_helper.ts
index 9d0254f..5968a54 100644
--- a/ash/webui/camera_app_ui/resources/js/untrusted_video_processor_helper.ts
+++ b/ash/webui/camera_app_ui/resources/js/untrusted_video_processor_helper.ts
@@ -32,4 +32,7 @@
   await worker.exposeVideoProcessor(Comlink.transfer(port, [port]));
 }
 
+export interface VideoProcessorHelperInterface {
+  connectToWorker: typeof connectToWorker;
+}
 export {connectToWorker};
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.js b/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.js
index df9ca17..cdcb678 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.js
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.js
@@ -198,7 +198,7 @@
    */
   initVideoProfile_() {
     // TODO(b/151047420): Remove options and use the largest supported profile.
-    for (const profile of Object.values(h264.Profile)) {
+    for (const profile of h264.profileValues) {
       const tpl = util.instantiateTemplate('#video-profile-option-template');
       const option = dom.getFrom(tpl, 'option', HTMLOptionElement);
       option.value = profile.toString();
diff --git a/ash/webui/camera_app_ui/resources/js/views/view.ts b/ash/webui/camera_app_ui/resources/js/views/view.ts
index eb01fd4..75cf32b 100644
--- a/ash/webui/camera_app_ui/resources/js/views/view.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/view.ts
@@ -40,7 +40,8 @@
   }
 }
 
-type EnterOptions = DialogEnterOptions|WarningEnterOptions|PTZPanelOptions;
+export type EnterOptions =
+    DialogEnterOptions|WarningEnterOptions|PTZPanelOptions;
 
 interface ViewOptions {
   /** enables dismissible by Esc-key. */
diff --git a/ash/webui/camera_app_ui/resources/js/window_controller.js b/ash/webui/camera_app_ui/resources/js/window_controller.js
deleted file mode 100644
index 0388282..0000000
--- a/ash/webui/camera_app_ui/resources/js/window_controller.js
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assertInstanceof} from './assert.js';
-import {
-  WindowStateControllerRemote,
-  WindowStateMonitorCallbackRouter,
-  WindowStateType,
-} from './mojo/type.js';
-import {wrapEndpoint} from './mojo/util.js';
-
-/**
- * @typedef {function(!Array<!WindowStateType>): void}
- */
-let WindowStateChangedEventListener;  // eslint-disable-line no-unused-vars
-
-/**
- * Controller to get/set/listener for window state.
- */
-export class WindowController {
-  /**
-   * @public
-   */
-  constructor() {
-    /**
-     * The remote controller from Mojo interface.
-     * @type {?WindowStateControllerRemote}
-     */
-    this.windowStateController_ = null;
-
-    /**
-     * Current window states.
-     * @type {!Array<!WindowStateType>}
-     */
-    this.windowStates_ = [];
-
-    /**
-     * Set of the listeners for window state changed events.
-     * @type {!Set<!WindowStateChangedEventListener>}
-     */
-    this.listeners_ = new Set();
-  }
-
-  /**
-   * Binds the controller remote from Mojo interface.
-   * @param {!WindowStateControllerRemote} remoteController
-   * @return {!Promise}
-   */
-  async bind(remoteController) {
-    this.windowStateController_ = remoteController;
-
-    const windowMonitorCallbackRouter =
-        wrapEndpoint(new WindowStateMonitorCallbackRouter());
-    windowMonitorCallbackRouter.onWindowStateChanged.addListener((states) => {
-      this.windowStates_ = states;
-      this.listeners_.forEach((listener) => listener(states));
-    });
-    const {states} = await this.windowStateController_.addMonitor(
-        windowMonitorCallbackRouter.$.bindNewPipeAndPassRemote());
-    this.windowStates_ = states;
-  }
-
-  /**
-   * Minimizes the window.
-   * @return {!Promise}
-   */
-  async minimize() {
-    return assertInstanceof(
-               this.windowStateController_, WindowStateControllerRemote)
-        .minimize();
-  }
-
-  /**
-   * Maximizes the window.
-   * @return {!Promise}
-   */
-  async maximize() {
-    return assertInstanceof(
-               this.windowStateController_, WindowStateControllerRemote)
-        .maximize();
-  }
-
-  /**
-   * Restores the window and leaves maximized/minimized/fullscreen state.
-   * @return {!Promise}
-   */
-  async restore() {
-    return assertInstanceof(
-               this.windowStateController_, WindowStateControllerRemote)
-        .restore();
-  }
-
-  /**
-   * Makes the window fullscreen.
-   * @return {!Promise}
-   */
-  async fullscreen() {
-    return assertInstanceof(
-               this.windowStateController_, WindowStateControllerRemote)
-        .fullscreen();
-  }
-
-  /**
-   * Focuses the window.
-   * @return {!Promise}
-   */
-  async focus() {
-    return assertInstanceof(
-               this.windowStateController_, WindowStateControllerRemote)
-        .focus();
-  }
-
-  /**
-   * Returns true if the window is currently minimized.
-   * @return {boolean}
-   */
-  isMinimized() {
-    return this.windowStates_.includes(WindowStateType.MINIMIZED);
-  }
-
-  /**
-   * Returns true if the window is currently fullscreen or maximized.
-   * @return {boolean}
-   */
-  isFullscreenOrMaximized() {
-    return this.windowStates_.includes(WindowStateType.FULLSCREEN) ||
-        this.windowStates_.includes(WindowStateType.MAXIMIZED);
-  }
-
-  /**
-   * Adds listener for the window state (including window size) changed events.
-   * @param {!WindowStateChangedEventListener} listener
-   */
-  addListener(listener) {
-    this.listeners_.add(listener);
-  }
-}
-
-export const windowController = new WindowController();
diff --git a/ash/webui/camera_app_ui/resources/js/window_controller.ts b/ash/webui/camera_app_ui/resources/js/window_controller.ts
new file mode 100644
index 0000000..3e0f9932
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/window_controller.ts
@@ -0,0 +1,119 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assertInstanceof} from './assert.js';
+import {
+  WindowStateControllerRemote,
+  WindowStateMonitorCallbackRouter,
+  WindowStateType,
+} from './mojo/type.js';
+import {wrapEndpoint} from './mojo/util.js';
+
+type WindowStateChangedEventListener = (states: WindowStateType[]) => void;
+
+/**
+ * Controller to get/set/listener for window state.
+ */
+export class WindowController {
+  /**
+   * The remote controller from Mojo interface.
+   */
+  private windowStateController: WindowStateControllerRemote|null = null;
+
+  /**
+   * Current window states.
+   */
+  private windowStates: WindowStateType[] = [];
+
+  /**
+   * Set of the listeners for window state changed events.
+   */
+  private readonly listeners = new Set<WindowStateChangedEventListener>();
+
+  /**
+   * Binds the controller remote from Mojo interface.
+   */
+  async bind(remoteController: WindowStateControllerRemote): Promise<void> {
+    this.windowStateController = remoteController;
+
+    const windowMonitorCallbackRouter =
+        wrapEndpoint(new WindowStateMonitorCallbackRouter());
+    windowMonitorCallbackRouter.onWindowStateChanged.addListener((states) => {
+      this.windowStates = states;
+      this.listeners.forEach((listener) => listener(states));
+    });
+    const {states} = await this.windowStateController.addMonitor(
+        windowMonitorCallbackRouter.$.bindNewPipeAndPassRemote());
+    this.windowStates = states;
+  }
+
+  /**
+   * Minimizes the window.
+   */
+  minimize(): Promise<void> {
+    return assertInstanceof(
+               this.windowStateController, WindowStateControllerRemote)
+        .minimize();
+  }
+
+  /**
+   * Maximizes the window.
+   */
+  maximize(): Promise<void> {
+    return assertInstanceof(
+               this.windowStateController, WindowStateControllerRemote)
+        .maximize();
+  }
+
+  /**
+   * Restores the window and leaves maximized/minimized/fullscreen state.
+   */
+  restore(): Promise<void> {
+    return assertInstanceof(
+               this.windowStateController, WindowStateControllerRemote)
+        .restore();
+  }
+
+  /**
+   * Makes the window fullscreen.
+   */
+  fullscreen(): Promise<void> {
+    return assertInstanceof(
+               this.windowStateController, WindowStateControllerRemote)
+        .fullscreen();
+  }
+
+  /**
+   * Focuses the window.
+   */
+  focus(): Promise<void> {
+    return assertInstanceof(
+               this.windowStateController, WindowStateControllerRemote)
+        .focus();
+  }
+
+  /**
+   * Returns true if the window is currently minimized.
+   */
+  isMinimized(): boolean {
+    return this.windowStates.includes(WindowStateType.MINIMIZED);
+  }
+
+  /**
+   * Returns true if the window is currently fullscreen or maximized.
+   */
+  isFullscreenOrMaximized(): boolean {
+    return this.windowStates.includes(WindowStateType.FULLSCREEN) ||
+        this.windowStates.includes(WindowStateType.MAXIMIZED);
+  }
+
+  /**
+   * Adds listener for the window state (including window size) changed events.
+   */
+  addListener(listener: WindowStateChangedEventListener): void {
+    this.listeners.add(listener);
+  }
+}
+
+export const windowController = new WindowController();
diff --git a/ash/webui/camera_app_ui/resources/utils/cca.py b/ash/webui/camera_app_ui/resources/utils/cca.py
index a9f60b4..a4979aa 100755
--- a/ash/webui/camera_app_ui/resources/utils/cca.py
+++ b/ash/webui/camera_app_ui/resources/utils/cca.py
@@ -194,11 +194,11 @@
 TS_ENTRY_FILES = [
     "js/externs/types.d.ts",
     "js/init.ts",
-    "js/main.js",
+    "js/main.ts",
     "js/models/barcode_worker.ts",
     "js/models/ffmpeg/video_processor.ts",
     "js/test_bridge.ts",
-    "js/untrusted_ga_helper.js",
+    "js/untrusted_ga_helper.ts",
     "js/untrusted_script_loader.ts",
     "js/untrusted_video_processor_helper.ts",
 ]
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc
index 476f860..beb32f7f 100644
--- a/base/allocator/partition_allocator/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -54,10 +54,12 @@
 #include <base/mac/mac_util.h>
 #endif
 
-#define EXPECT_PEQ(ptr1, ptr2) \
+// In the MTE world, the upper bits of a pointer can be decorated with a tag,
+// thus allowing many versions of the same pointer to exist. These macros take
+// that into account when comparing.
+#define PA_EXPECT_PTR_EQ(ptr1, ptr2) \
   { EXPECT_EQ(memory::UnmaskPtr(ptr1), memory::UnmaskPtr(ptr2)); }
-
-#define EXPECT_PNE(ptr1, ptr2) \
+#define PA_EXPECT_PTR_NE(ptr1, ptr2) \
   { EXPECT_NE(memory::UnmaskPtr(ptr1), memory::UnmaskPtr(ptr2)); }
 
 #if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
@@ -636,7 +638,7 @@
   // slot_span2 and find the recently freed slot.
   char* new_ptr = reinterpret_cast<char*>(
       allocator.root()->Alloc(kTestAllocSize, type_name));
-  EXPECT_PEQ(ptr, new_ptr);
+  PA_EXPECT_PTR_EQ(ptr, new_ptr);
   EXPECT_EQ(slot_span2, bucket->active_slot_spans_head);
   EXPECT_EQ(slot_span3, slot_span2->next_slot_span);
 
@@ -649,7 +651,7 @@
   // This allocation should be satisfied by slot_span1.
   new_ptr = reinterpret_cast<char*>(
       allocator.root()->Alloc(kTestAllocSize, type_name));
-  EXPECT_PEQ(ptr, new_ptr);
+  PA_EXPECT_PTR_EQ(ptr, new_ptr);
   EXPECT_EQ(slot_span1, bucket->active_slot_spans_head);
   EXPECT_EQ(slot_span2, slot_span1->next_slot_span);
 
@@ -769,17 +771,17 @@
 
   // Change the size of the realloc, remaining inside the same bucket.
   void* new_ptr = allocator.root()->Realloc(ptr, base_size + 2, type_name);
-  EXPECT_PEQ(ptr, new_ptr);
+  PA_EXPECT_PTR_EQ(ptr, new_ptr);
   new_ptr = allocator.root()->Realloc(ptr, base_size + 1, type_name);
-  EXPECT_PEQ(ptr, new_ptr);
+  PA_EXPECT_PTR_EQ(ptr, new_ptr);
   new_ptr =
       allocator.root()->Realloc(ptr, base_size + kSmallestBucket, type_name);
-  EXPECT_PEQ(ptr, new_ptr);
+  PA_EXPECT_PTR_EQ(ptr, new_ptr);
 
   // Change the size of the realloc, switching buckets.
   new_ptr = allocator.root()->Realloc(ptr, base_size + kSmallestBucket + 1,
                                       type_name);
-  EXPECT_PNE(new_ptr, ptr);
+  PA_EXPECT_PTR_NE(new_ptr, ptr);
   // Check that the realloc copied correctly.
   char* new_char_ptr = static_cast<char*>(new_ptr);
   EXPECT_EQ(*new_char_ptr, 'A');
@@ -795,13 +797,13 @@
   // do an alloc of the old allocation size and check that the old allocation
   // address is at the head of the freelist and reused.
   void* reused_ptr = allocator.root()->Alloc(base_size + 1, type_name);
-  EXPECT_PEQ(reused_ptr, orig_ptr);
+  PA_EXPECT_PTR_EQ(reused_ptr, orig_ptr);
   allocator.root()->Free(reused_ptr);
 
   // Downsize the realloc.
   ptr = new_ptr;
   new_ptr = allocator.root()->Realloc(ptr, base_size + 1, type_name);
-  EXPECT_PEQ(new_ptr, orig_ptr);
+  PA_EXPECT_PTR_EQ(new_ptr, orig_ptr);
   new_char_ptr = static_cast<char*>(new_ptr);
   EXPECT_EQ(*new_char_ptr, 'B');
   *new_char_ptr = 'C';
@@ -809,7 +811,7 @@
   // Upsize the realloc to outside the partition.
   ptr = new_ptr;
   new_ptr = allocator.root()->Realloc(ptr, kMaxBucketed + 1, type_name);
-  EXPECT_PNE(new_ptr, ptr);
+  PA_EXPECT_PTR_NE(new_ptr, ptr);
   new_char_ptr = static_cast<char*>(new_ptr);
   EXPECT_EQ(*new_char_ptr, 'C');
   *new_char_ptr = 'D';
@@ -829,8 +831,8 @@
   // Downsize the realloc to inside the partition.
   ptr = new_ptr;
   new_ptr = allocator.root()->Realloc(ptr, base_size + 1, type_name);
-  EXPECT_PNE(new_ptr, ptr);
-  EXPECT_PEQ(new_ptr, orig_ptr);
+  PA_EXPECT_PTR_NE(new_ptr, ptr);
+  PA_EXPECT_PTR_EQ(new_ptr, orig_ptr);
   new_char_ptr = static_cast<char*>(new_ptr);
   EXPECT_EQ(*new_char_ptr, 'F');
 
@@ -893,9 +895,9 @@
     EXPECT_EQ(0, slot_span->num_allocated_slots);
     EXPECT_EQ(0, slot_span->num_unprovisioned_slots);
     void* new_ptr_1 = allocator.root()->Alloc(size, type_name);
-    EXPECT_PEQ(ptr2, new_ptr_1);
+    PA_EXPECT_PTR_EQ(ptr2, new_ptr_1);
     void* new_ptr_2 = allocator.root()->Alloc(size, type_name);
-    EXPECT_PEQ(ptr3, new_ptr_2);
+    PA_EXPECT_PTR_EQ(ptr3, new_ptr_2);
 
     allocator.root()->Free(new_ptr_1);
     allocator.root()->Free(new_ptr_2);
@@ -956,9 +958,9 @@
   EXPECT_LT(requested_size, actual_capacity);
 #if BUILDFLAG(USE_BACKUP_REF_PTR)
   for (size_t offset = 0; offset < requested_size; ++offset) {
-    EXPECT_PEQ(PartitionAllocGetSlotStartInBRPPool(
-                   reinterpret_cast<uintptr_t>(ptr) + offset),
-               allocator.root()->AdjustPointerForExtrasSubtract(ptr));
+    PA_EXPECT_PTR_EQ(PartitionAllocGetSlotStartInBRPPool(
+                         reinterpret_cast<uintptr_t>(ptr) + offset),
+                     allocator.root()->AdjustPointerForExtrasSubtract(ptr));
   }
 #endif  // BUILDFLAG(USE_BACKUP_REF_PTR)
   allocator.root()->Free(ptr);
@@ -975,9 +977,9 @@
   EXPECT_EQ(requested_size, actual_capacity);
 #if BUILDFLAG(USE_BACKUP_REF_PTR)
   for (size_t offset = 0; offset < requested_size; offset += 877) {
-    EXPECT_PEQ(PartitionAllocGetSlotStartInBRPPool(
-                   reinterpret_cast<uintptr_t>(ptr) + offset),
-               allocator.root()->AdjustPointerForExtrasSubtract(ptr));
+    PA_EXPECT_PTR_EQ(PartitionAllocGetSlotStartInBRPPool(
+                         reinterpret_cast<uintptr_t>(ptr) + offset),
+                     allocator.root()->AdjustPointerForExtrasSubtract(ptr));
   }
 #endif  // BUILDFLAG(USE_BACKUP_REF_PTR)
   allocator.root()->Free(ptr);
@@ -999,9 +1001,9 @@
   EXPECT_EQ(requested_size + SystemPageSize(), actual_capacity);
 #if BUILDFLAG(USE_BACKUP_REF_PTR)
   for (size_t offset = 0; offset < requested_size; offset += 4999) {
-    EXPECT_PEQ(PartitionAllocGetSlotStartInBRPPool(
-                   reinterpret_cast<uintptr_t>(ptr) + offset),
-               allocator.root()->AdjustPointerForExtrasSubtract(ptr));
+    PA_EXPECT_PTR_EQ(PartitionAllocGetSlotStartInBRPPool(
+                         reinterpret_cast<uintptr_t>(ptr) + offset),
+                     allocator.root()->AdjustPointerForExtrasSubtract(ptr));
   }
 #endif  // BUILDFLAG(USE_BACKUP_REF_PTR)
 
@@ -1016,9 +1018,9 @@
   EXPECT_EQ(requested_size, actual_capacity);
 #if BUILDFLAG(USE_BACKUP_REF_PTR)
   for (size_t offset = 0; offset < requested_size; offset += 4999) {
-    EXPECT_PEQ(PartitionAllocGetSlotStartInBRPPool(
-                   reinterpret_cast<uintptr_t>(ptr) + offset),
-               allocator.root()->AdjustPointerForExtrasSubtract(ptr));
+    PA_EXPECT_PTR_EQ(PartitionAllocGetSlotStartInBRPPool(
+                         reinterpret_cast<uintptr_t>(ptr) + offset),
+                     allocator.root()->AdjustPointerForExtrasSubtract(ptr));
   }
 #endif  // BUILDFLAG(USE_BACKUP_REF_PTR)
 
@@ -1039,9 +1041,9 @@
     EXPECT_LT(requested_size, actual_capacity);
 #if BUILDFLAG(USE_BACKUP_REF_PTR)
     for (size_t offset = 0; offset < requested_size; offset += 16111) {
-      EXPECT_PEQ(PartitionAllocGetSlotStartInBRPPool(
-                     reinterpret_cast<uintptr_t>(ptr) + offset),
-                 allocator.root()->AdjustPointerForExtrasSubtract(ptr));
+      PA_EXPECT_PTR_EQ(PartitionAllocGetSlotStartInBRPPool(
+                           reinterpret_cast<uintptr_t>(ptr) + offset),
+                       allocator.root()->AdjustPointerForExtrasSubtract(ptr));
     }
 #endif  // BUILDFLAG(USE_BACKUP_REF_PTR)
     allocator.root()->Free(ptr);
@@ -1155,9 +1157,9 @@
     char* ptr = static_cast<char*>(ptrs[i]);
     EXPECT_EQ(allocator.root()->AllocationCapacityFromPtr(ptr), requested_size);
     for (size_t offset = 0; offset < requested_size; offset += 13) {
-      EXPECT_PEQ(PartitionAllocGetSlotStartInBRPPool(
-                     reinterpret_cast<uintptr_t>(ptr) + offset),
-                 allocator.root()->AdjustPointerForExtrasSubtract(ptr));
+      PA_EXPECT_PTR_EQ(PartitionAllocGetSlotStartInBRPPool(
+                           reinterpret_cast<uintptr_t>(ptr) + offset),
+                       allocator.root()->AdjustPointerForExtrasSubtract(ptr));
     }
     allocator.root()->Free(ptr);
   }
@@ -1174,8 +1176,8 @@
   // realloc(ptr, 0) should be equivalent to free().
   void* ptr2 = allocator.root()->Realloc(ptr, 0, type_name);
   EXPECT_EQ(nullptr, ptr2);
-  EXPECT_PEQ(allocator.root()->AdjustPointerForExtrasSubtract(ptr),
-             slot_span->get_freelist_head());
+  PA_EXPECT_PTR_EQ(allocator.root()->AdjustPointerForExtrasSubtract(ptr),
+                   slot_span->get_freelist_head());
 
   // Test that growing an allocation with realloc() copies everything from the
   // old allocation.
@@ -1185,7 +1187,7 @@
   ptr = allocator.root()->Alloc(size, type_name);
   memset(ptr, 'A', size);
   ptr2 = allocator.root()->Realloc(ptr, size + 1, type_name);
-  EXPECT_PNE(ptr, ptr2);
+  PA_EXPECT_PTR_NE(ptr, ptr2);
   char* char_ptr2 = static_cast<char*>(ptr2);
   EXPECT_EQ('A', char_ptr2[0]);
   EXPECT_EQ('A', char_ptr2[size - 1]);
@@ -1197,7 +1199,7 @@
   // from the old allocation. Use |size - 1| to test what happens to the extra
   // space before the cookie.
   ptr = allocator.root()->Realloc(ptr2, size - 1, type_name);
-  EXPECT_PNE(ptr2, ptr);
+  PA_EXPECT_PTR_NE(ptr2, ptr);
   char* char_ptr = static_cast<char*>(ptr);
   EXPECT_EQ('A', char_ptr[0]);
   EXPECT_EQ('A', char_ptr[size - 2]);
@@ -1217,7 +1219,7 @@
   ptr = allocator.root()->Alloc(size, type_name);
   memset(ptr, 'A', size);
   ptr2 = allocator.root()->Realloc(ptr, size * 2, type_name);
-  EXPECT_PNE(ptr, ptr2);
+  PA_EXPECT_PTR_NE(ptr, ptr2);
   char_ptr2 = static_cast<char*>(ptr2);
   EXPECT_EQ('A', char_ptr2[0]);
   EXPECT_EQ('A', char_ptr2[size - 1]);
@@ -1233,7 +1235,7 @@
   ptr = allocator.root()->Alloc(size, type_name);
   memset(ptr, 'A', size);
   ptr2 = allocator.root()->Realloc(ptr2, size / 2, type_name);
-  EXPECT_PNE(ptr, ptr2);
+  PA_EXPECT_PTR_NE(ptr, ptr2);
   char_ptr2 = static_cast<char*>(ptr2);
   EXPECT_EQ('A', char_ptr2[0]);
   EXPECT_EQ('A', char_ptr2[size / 2 - 1]);
@@ -1265,7 +1267,7 @@
   // Test that a previously in-place shrunk direct mapped allocation can be
   // expanded up again up to its original size.
   ptr = allocator.root()->Realloc(ptr3, size, type_name);
-  EXPECT_PEQ(ptr3, ptr);
+  PA_EXPECT_PTR_EQ(ptr3, ptr);
   EXPECT_EQ(actual_capacity, allocator.root()->AllocationCapacityFromPtr(ptr));
 
   // Test that the allocation can be expanded in place up to its capacity.
@@ -1326,7 +1328,7 @@
     // Test that a direct mapped allocation is performed not in-place when the
     // new size is small enough.
     ptr3 = allocator.root()->Realloc(ptr2, SystemPageSize(), type_name);
-    EXPECT_PNE(ptr2, ptr3);
+    PA_EXPECT_PTR_NE(ptr2, ptr3);
 
     allocator.root()->Free(ptr3);
   }
@@ -1342,7 +1344,7 @@
   // Reallocating with the same size will actually relocate, because without a
   // need for alignment we can downsize the reservation significantly.
   void* ptr2 = allocator.root()->Realloc(ptr, size, type_name);
-  EXPECT_PNE(ptr, ptr2);
+  PA_EXPECT_PTR_NE(ptr, ptr2);
   allocator.root()->Free(ptr2);
 
   // Again pick size such that the alignment will put it cross the super page
@@ -2503,7 +2505,7 @@
   // A good choice here is to re-fill the third slot span since the first two
   // are empty. We used to fail that.
   void* ptr7 = allocator.root()->Alloc(size, type_name);
-  EXPECT_PEQ(ptr6, ptr7);
+  PA_EXPECT_PTR_EQ(ptr6, ptr7);
   EXPECT_EQ(slot_span3, bucket->active_slot_spans_head);
 
   allocator.root()->Free(ptr5);
@@ -2725,10 +2727,10 @@
   // Let's check we didn't brick the freelist.
   void* ptr1b =
       allocator.root()->Alloc(SystemPageSize() - kExtraAllocSize, type_name);
-  EXPECT_PEQ(ptr1, ptr1b);
+  PA_EXPECT_PTR_EQ(ptr1, ptr1b);
   void* ptr2b =
       allocator.root()->Alloc(SystemPageSize() - kExtraAllocSize, type_name);
-  EXPECT_PEQ(ptr2, ptr2b);
+  PA_EXPECT_PTR_EQ(ptr2, ptr2b);
   EXPECT_FALSE(slot_span->get_freelist_head());
 
   allocator.root()->Free(ptr1);
@@ -3575,7 +3577,7 @@
   void* ptr2 = new_root->ReallocFlags(PartitionAllocReturnNull, ptr,
                                       test_size + 1024, nullptr);
   EXPECT_TRUE(ptr2);
-  EXPECT_PNE(ptr, ptr2);
+  PA_EXPECT_PTR_NE(ptr, ptr2);
 }
 
 TEST_F(PartitionAllocTest, FastPathOrReturnNull) {
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc
index 64e9c0d..34dac71 100644
--- a/base/allocator/partition_allocator/partition_bucket.cc
+++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -872,7 +872,7 @@
     // last call. This may be a good signal to shrink it if possible (if an
     // entire OS page is free, we can decommit it).
     //
-    // Besides saving CPU, this also avoid touching memory of fully idle slot
+    // Besides saving CPU, this also avoids touching memory of fully idle slot
     // spans, which may required paging.
     if (slot_span->num_allocated_slots > 0 && !slot_span->freelist_is_sorted)
       slot_span->SortFreelist();
diff --git a/base/allocator/partition_allocator/partition_page.cc b/base/allocator/partition_allocator/partition_page.cc
index 0282fb43..19d8251 100644
--- a/base/allocator/partition_allocator/partition_page.cc
+++ b/base/allocator/partition_allocator/partition_page.cc
@@ -260,9 +260,11 @@
       bucket->get_slots_per_span() - num_unprovisioned_slots;
   PA_CHECK(num_unprovisioned_slots <= kMaxSlotsPerSlotSpan);
 
+  size_t num_free_slots = 0;
   size_t slot_size = bucket->slot_size;
   for (PartitionFreelistEntry* head = freelist_head; head;
        head = head->GetNext(slot_size)) {
+    ++num_free_slots;
     size_t offset_in_slot_span =
         reinterpret_cast<char*>(memory::UnmaskPtr(head)) - slot_span_base;
     size_t slot_number = bucket->GetSlotNumber(offset_in_slot_span);
@@ -270,25 +272,29 @@
     free_slots[slot_number] = true;
   }
 
-  PartitionFreelistEntry* back = nullptr;
-  PartitionFreelistEntry* head = nullptr;
+  // Empty or single-element list is always sorted.
+  if (num_free_slots > 1) {
+    PartitionFreelistEntry* back = nullptr;
+    PartitionFreelistEntry* head = nullptr;
 
-  for (size_t slot_number = 0; slot_number < num_provisioned_slots;
-       slot_number++) {
-    if (free_slots[slot_number]) {
-      char* slot_address =
-          memory::RemaskPtr(slot_span_base + (slot_size * slot_number));
-      auto* entry = new (slot_address) PartitionFreelistEntry();
+    for (size_t slot_number = 0; slot_number < num_provisioned_slots;
+         slot_number++) {
+      if (free_slots[slot_number]) {
+        char* slot_address =
+            memory::RemaskPtr(slot_span_base + (slot_size * slot_number));
+        auto* entry = new (slot_address) PartitionFreelistEntry();
 
-      if (!head)
-        head = entry;
-      else
-        back->SetNext(entry);
+        if (!head)
+          head = entry;
+        else
+          back->SetNext(entry);
 
-      back = entry;
+        back = entry;
+      }
     }
+    SetFreelistHead(head);
   }
-  SetFreelistHead(head);
+
   freelist_is_sorted = true;
 }
 
diff --git a/base/allocator/partition_allocator/starscan/pcscan_unittest.cc b/base/allocator/partition_allocator/starscan/pcscan_unittest.cc
index ee75dea3..67e04eb 100644
--- a/base/allocator/partition_allocator/starscan/pcscan_unittest.cc
+++ b/base/allocator/partition_allocator/starscan/pcscan_unittest.cc
@@ -20,10 +20,12 @@
 
 namespace base {
 
-#define EXPECT_PEQ(ptr1, ptr2) \
+// In the MTE world, the upper bits of a pointer can be decorated with a tag,
+// thus allowing many versions of the same pointer to exist. These macros take
+// that into account when comparing.
+#define PA_EXPECT_PTR_EQ(ptr1, ptr2) \
   { EXPECT_EQ(memory::UnmaskPtr(ptr1), memory::UnmaskPtr(ptr2)); }
-
-#define EXPECT_PNE(ptr1, ptr2) \
+#define PA_EXPECT_PTR_NE(ptr1, ptr2) \
   { EXPECT_NE(memory::UnmaskPtr(ptr1), memory::UnmaskPtr(ptr2)); }
 
 namespace internal {
@@ -149,8 +151,8 @@
   EXPECT_EQ(SlotSpan::FromSlotStartPtr(first),
             SlotSpan::FromSlotStartPtr(last));
   if (bucket.num_system_pages_per_slot_span == NumSystemPagesPerPartitionPage())
-    EXPECT_PEQ(reinterpret_cast<size_t>(first) & PartitionPageBaseMask(),
-               reinterpret_cast<size_t>(last) & PartitionPageBaseMask());
+    PA_EXPECT_PTR_EQ(reinterpret_cast<size_t>(first) & PartitionPageBaseMask(),
+                     reinterpret_cast<size_t>(last) & PartitionPageBaseMask());
   EXPECT_EQ(num_slots, static_cast<size_t>(
                            bucket.active_slot_spans_head->num_allocated_slots));
   EXPECT_EQ(nullptr, bucket.active_slot_spans_head->get_freelist_head());
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 25901e5a0..9e89284 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-7.20220101.3.1
+7.20220103.0.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 25901e5a0..9e89284 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-7.20220101.3.1
+7.20220103.0.1
diff --git a/cc/trees/draw_property_utils.cc b/cc/trees/draw_property_utils.cc
index e8a6155a..2b8ad609 100644
--- a/cc/trees/draw_property_utils.cc
+++ b/cc/trees/draw_property_utils.cc
@@ -769,10 +769,8 @@
   auto result =
       std::make_pair(node->mask_filter_info, node->is_fast_rounded_corner);
 
-  if (!result.first.Transform(to_target)) {
-    DCHECK(result.first.IsEmpty());
+  if (!result.first.Transform(to_target))
     return kEmptyMaskFilterInfoPair;
-  }
 
   return result;
 }
diff --git a/chrome/VERSION b/chrome/VERSION
index 9c81a79..155bf274 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=99
 MINOR=0
-BUILD=4803
+BUILD=4805
 PATCH=0
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
index b23d7287..85dd28a 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
@@ -537,10 +537,8 @@
      */
     @Test
     @MediumTest
-    @DisableIf.Build(sdk_is_less_than = Build.VERSION_CODES.M,
-            message = "Failing on Lollipop Phone Tester (https://crbug.com/1247243)")
-    public void
-    testCreateAndEnterCard() throws Exception {
+    @DisabledTest(message = "https://crbug.com/1247243, https://crbug.com/1249164#c2")
+    public void testCreateAndEnterCard() throws Exception {
         // Add a profile for easier address selection.
         mHelper.addDummyProfile("Adam West", "adamwest@google.com");
 
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 a161c544..66cd1ac 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
@@ -39,19 +39,10 @@
 import org.chromium.components.infobars.InfoBar;
 import org.chromium.components.infobars.InfoBarAnimationListener;
 import org.chromium.components.infobars.InfoBarUiItem;
-import org.chromium.components.messages.DismissReason;
-import org.chromium.components.messages.MessageBannerProperties;
-import org.chromium.components.messages.MessageDispatcher;
-import org.chromium.components.messages.MessageDispatcherProvider;
-import org.chromium.components.messages.MessageIdentifier;
-import org.chromium.components.messages.MessageStateHandler;
-import org.chromium.components.messages.MessagesTestHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
-import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.test.util.UiRestriction;
 
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
 /** Integration test for {@link ChromeSurveyController} and {@link SurveyInfoBar}. */
@@ -66,7 +57,6 @@
 public class ChromeSurveyControllerIntegrationTest {
     // clang-format on
     static final String TEST_TRIGGER_ID = "test_trigger_id";
-    static final long MESSAGE_AUTO_DISMISS_DURATION_MS = 10000L;
 
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@@ -77,10 +67,9 @@
     private InfoBarAnimationListener mInfoBarAnimationListener;
     private CallbackHelper mAllAnimationsFinishedCallback;
     private String mPrefKey;
-    private MessageDispatcher mMessageDispatcher;
 
     @Before
-    public void setUp() throws InterruptedException, TimeoutException, ExecutionException {
+    public void setUp() throws InterruptedException, TimeoutException {
         mAllAnimationsFinishedCallback = new CallbackHelper();
         mInfoBarAnimationListener = new InfoBarAnimationListener() {
             @Override
@@ -104,14 +93,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mActivityTestRule.getInfoBarContainer().addAnimationListener(mInfoBarAnimationListener);
         });
-
-        mMessageDispatcher = TestThreadUtils.runOnUiThreadBlocking(
-                ()
-                        -> MessageDispatcherProvider.from(
-                                mActivityTestRule.getActivity().getWindowAndroid()));
-
-        waitUntilSurveyPromptPresented(
-                !ChromeFeatureList.isEnabled(ChromeFeatureList.MESSAGES_FOR_ANDROID_CHROME_SURVEY));
+        waitUntilInfoBarPresented();
     }
 
     @After
@@ -144,24 +126,6 @@
 
     @Test
     @MediumTest
-    public void testMessagePrimaryButtonClicked() throws TimeoutException, ExecutionException {
-        PropertyModel message = getSurveyMessage();
-        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);
-        // Simulate message dismissal on primary button click.
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> mMessageDispatcher.dismissMessage(message, DismissReason.PRIMARY_ACTION));
-
-        Assert.assertEquals("#showSurveyIfAvailable should be attempted.", 1,
-                mTestSurveyController.showSurveyCallbackHelper.getCallCount());
-        assertInfoBarClosingStateRecorded(InfoBarClosingState.ACCEPTED_SURVEY);
-    }
-
-    @Test
-    @MediumTest
     @Features.DisableFeatures(ChromeFeatureList.MESSAGES_FOR_ANDROID_CHROME_SURVEY)
     public void testInfoBarClose() throws TimeoutException {
         InfoBar surveyInfoBar = getSurveyInfoBar();
@@ -176,59 +140,30 @@
 
     @Test
     @MediumTest
-    public void testMessageDismissed() throws TimeoutException, ExecutionException {
-        PropertyModel message = getSurveyMessage();
-        Assert.assertNotNull("Message should not be null.", message);
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> mMessageDispatcher.dismissMessage(message, DismissReason.GESTURE));
-        assertInfoBarClosingStateRecorded(InfoBarClosingState.CLOSE_BUTTON);
-    }
-
-    @Test
-    @MediumTest
     @Features.DisableFeatures(ChromeFeatureList.MESSAGES_FOR_ANDROID_CHROME_SURVEY)
     public void testNoInfoBarInNewTab() throws InterruptedException {
-        waitUntilSurveyPromptStateRecorded(
-                ChromeSurveyController.getRequiredVisibilityDurationMs());
+        waitUntilInfoBarStateRecorded();
 
         // Info bar should not displayed in another tab.
-        Tab tabTwo = mActivityTestRule.loadUrlInNewTab("about:blank", false);
+        mActivityTestRule.loadUrlInNewTab("about:blank", false);
         assertInfoBarClosingStateRecorded(InfoBarClosingState.VISIBLE_INDIRECT);
 
+        Tab tabTwo = mActivityTestRule.getActivity().getActivityTab();
         waitUntilTabIsReady(tabTwo);
         Assert.assertNull("Tab two should not have the infobar", getSurveyInfoBar());
     }
 
-    @Test
-    @MediumTest
-    public void testNoMessageInNewTab() throws InterruptedException, ExecutionException {
-        // Simulate message visibility for the auto-dismiss duration length of time.
-        waitUntilSurveyPromptStateRecorded(MESSAGE_AUTO_DISMISS_DURATION_MS);
-
-        // Survey prompt should not be displayed in another tab.
-        Tab tabTwo = mActivityTestRule.loadUrlInNewTab("about:blank", false);
-        assertInfoBarClosingStateRecorded(InfoBarClosingState.VISIBLE_INDIRECT);
-
-        waitUntilTabIsReady(tabTwo);
-        Assert.assertNull("Tab two should not have the message.", getSurveyMessage());
-    }
-
-    private void waitUntilSurveyPromptPresented(boolean infobar) throws TimeoutException {
+    private void waitUntilInfoBarPresented() throws TimeoutException {
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
         waitUntilTabIsReady(tab);
         mTestSurveyController.downloadCallbackHelper.waitForFirst();
-        if (infobar) {
-            Assert.assertNotNull("Tab should have an info bar.", getSurveyInfoBar());
-            Assert.assertEquals("Info Bars are not visible.", View.VISIBLE,
-                    mActivityTestRule.getInfoBarContainer().getVisibility());
-        } else {
-            Assert.assertNotNull("Tab should have a message.", getSurveyMessage());
-        }
+        Assert.assertNotNull("Tab should have an info bar", getSurveyInfoBar());
+        Assert.assertEquals("Info Bars are not visible.", View.VISIBLE,
+                mActivityTestRule.getInfoBarContainer().getVisibility());
     }
 
-    private void waitUntilSurveyPromptStateRecorded(long visibilityDuration)
-            throws InterruptedException {
-        Thread.sleep(visibilityDuration);
+    private void waitUntilInfoBarStateRecorded() throws InterruptedException {
+        Thread.sleep(ChromeSurveyController.getRequiredVisibilityDurationMs());
         CriteriaHelper.pollUiThread(
                 () -> SharedPreferencesManager.getInstance().contains(mPrefKey));
     }
@@ -248,12 +183,6 @@
         return null;
     }
 
-    private PropertyModel getSurveyMessage() {
-        List<MessageStateHandler> messages = MessagesTestHelper.getEnqueuedMessages(
-                mMessageDispatcher, MessageIdentifier.CHROME_SURVEY);
-        return messages.size() == 0 ? null : MessagesTestHelper.getCurrentMessage(messages.get(0));
-    }
-
     private void assertInfoBarClosingStateRecorded(@InfoBarClosingState int state) {
         int count = RecordHistogram.getHistogramValueCountForTesting(
                 "Android.Survey.InfoBarClosingState", state);
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index cfeb5d9a..95f05ab 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-99.0.4799.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-99.0.4801.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/resources/generated_resources_fil.xtb b/chrome/app/resources/generated_resources_fil.xtb
index 573dff0a..3047ab8 100644
--- a/chrome/app/resources/generated_resources_fil.xtb
+++ b/chrome/app/resources/generated_resources_fil.xtb
@@ -2902,6 +2902,7 @@
 <translation id="3916445069167113093">Maaaring makapinsala sa iyong computer ang uri ng file na ito. Gusto mo pa rin bang panatilihin ang <ph name="FILE_NAME" />?</translation>
 <translation id="3918972485393593704">Iulat ang mga detalye sa Google</translation>
 <translation id="3919145445993746351">Para makuha ang iyong mga extension sa lahat ng computer mo, i-on ang pag-sync</translation>
+<translation id="3919229493046408863">I-off ang notification kapag nasa malapit ang mga device</translation>
 <translation id="3919798653937160644">Hindi lalabas sa iyong history ng browser ang mga page na tiningnan mo sa window na ito at hindi mag-iiwan ang mga ito ng iba pang trace, tulad ng cookies, sa computer pagkatapos mong isara ang lahat ng nakabukas na window ng Bisita. Gayunpaman, papanatilihin ang anumang file na na-download mo.</translation>
 <translation id="3920504717067627103">Mga Patakaran ng Certificate</translation>
 <translation id="392089482157167418">I-enable ang ChromeVox (pasalitang feedback)</translation>
@@ -5936,6 +5937,7 @@
 <translation id="7167486101654761064">&amp;Laging buksan ang uri ng mga file na ito</translation>
 <translation id="716810439572026343">Dina-download ang <ph name="FILE_NAME" /></translation>
 <translation id="7168109975831002660">Minimum na laki ng font</translation>
+<translation id="7169122689956315694">I-on ang notification kapag nasa malapit ang mga device</translation>
 <translation id="7170236477717446850">Larawan sa profile</translation>
 <translation id="7171000599584840888">Magdagdag ng Profile...</translation>
 <translation id="7171259390164035663">Huwag i-enroll</translation>
diff --git a/chrome/app/resources/generated_resources_hy.xtb b/chrome/app/resources/generated_resources_hy.xtb
index 6e2e5e8..4979bace 100644
--- a/chrome/app/resources/generated_resources_hy.xtb
+++ b/chrome/app/resources/generated_resources_hy.xtb
@@ -2887,6 +2887,7 @@
 <translation id="3916445069167113093">Այս տեսակի ֆայլը կարող է վնասել ձեր համակարգիչը: Ներբեռնե՞լ <ph name="FILE_NAME" /> ֆայլը:</translation>
 <translation id="3918972485393593704">Ուղարկել մանրամասները Google-ին</translation>
 <translation id="3919145445993746351">Ձեր ընդլայնումները բոլոր համակարգիչներում օգտագործելու համար միացրեք համաժամացումը</translation>
+<translation id="3919229493046408863">Անջատել ծանուցումը, երբ մոտակա սարքեր գտնվեն</translation>
 <translation id="3919798653937160644">Այս պատուհանով բացված էջերը չեն ցուցադրվի այցելությունների պատմության մեջ, և հյուրի բոլոր պատուհանները փակելուց հետո այլ հետագծեր (օրինակ՝ քուքիները) չեն մնա համակարգչում։ Ամեն դեպքում, ձեր ներբեռնած ֆայլերը կպահպանվեն։</translation>
 <translation id="3920504717067627103">Վկայագրի քաղաքականություններ</translation>
 <translation id="392089482157167418">Միացնել ChromeVox-ը (ձայնային ուղեկցում)</translation>
@@ -5923,6 +5924,7 @@
 <translation id="7167486101654761064">&amp;Միշտ բացել այս տեսակի ֆայլերը</translation>
 <translation id="716810439572026343"><ph name="FILE_NAME" /> ֆայլի ներբեռնում</translation>
 <translation id="7168109975831002660">Նվազագույն տառաչափը</translation>
+<translation id="7169122689956315694">Միացնել ծանուցումը, երբ մոտակա սարքեր գտնվեն</translation>
 <translation id="7170236477717446850">Պրոֆիլի նկար</translation>
 <translation id="7171000599584840888">Ավելացնել պրոֆիլ...</translation>
 <translation id="7171259390164035663">Հրաժարվել</translation>
diff --git a/chrome/app/resources/generated_resources_ja.xtb b/chrome/app/resources/generated_resources_ja.xtb
index c623b34..a45c583 100644
--- a/chrome/app/resources/generated_resources_ja.xtb
+++ b/chrome/app/resources/generated_resources_ja.xtb
@@ -2883,6 +2883,7 @@
 <translation id="3916445069167113093">この種類のファイルはコンピュータに損害を与える可能性があります。<ph name="FILE_NAME" /> のダウンロードを続けますか?</translation>
 <translation id="3918972485393593704">Google に詳細なレポートを送信する</translation>
 <translation id="3919145445993746351">お使いのどのパソコンでも同じ拡張機能を使用するには、同期を有効にします</translation>
+<translation id="3919229493046408863">周辺にデバイスがあるときに通知をオフにする</translation>
 <translation id="3919798653937160644">このウィンドウで開いたページはブラウザの履歴に記録されません。また、開いているゲスト ウィンドウをすべて閉じると、Cookie などのデータはパソコンから消去されます。ただし、ダウンロードしたファイルは保持されます。</translation>
 <translation id="3920504717067627103">証明書ポリシー</translation>
 <translation id="392089482157167418">ChromeVox(音声フィードバック)を有効にする</translation>
@@ -5899,6 +5900,7 @@
 <translation id="7167486101654761064">この種類のファイルは常に開く(&amp;A)</translation>
 <translation id="716810439572026343"><ph name="FILE_NAME" /> をダウンロードしています</translation>
 <translation id="7168109975831002660">最小フォント サイズ</translation>
+<translation id="7169122689956315694">周辺にデバイスがあるときに通知をオンにする</translation>
 <translation id="7170236477717446850">プロフィール写真</translation>
 <translation id="7171000599584840888">プロファイルを追加...</translation>
 <translation id="7171259390164035663">登録しない</translation>
diff --git a/chrome/app/resources/generated_resources_kn.xtb b/chrome/app/resources/generated_resources_kn.xtb
index ddacde0..3e762c0d 100644
--- a/chrome/app/resources/generated_resources_kn.xtb
+++ b/chrome/app/resources/generated_resources_kn.xtb
@@ -2894,6 +2894,7 @@
 <translation id="3916445069167113093">ಈ ಫೈಲ್‌ನ ಪ್ರಕಾರವು ನಿಮ್ಮ ಕಂಪ್ಯೂಟರ್‌ಗೆ ಹಾನಿಯನ್ನುಂಟು ಮಾಡಬಹುದು. ನೀವು <ph name="FILE_NAME" /> ಅನ್ನು ಹೇಗಿದ್ದರೂ ಇರಿಸಲು ಬಯಸುವಿರಾ?</translation>
 <translation id="3918972485393593704">Google ಗೆ ವಿವರಗಳ ವರದಿ ನೀಡಿ</translation>
 <translation id="3919145445993746351">ನಿಮ್ಮ ಎಲ್ಲಾ ಕಂಪ್ಯೂಟರ್‌ಗಳಲ್ಲೂ ನಿಮ್ಮ ವಿಸ್ತರಣೆಗಳನ್ನು ಪಡೆಯಲು, ಸಿಂಕ್ ಅನ್ನು ಆನ್ ಮಾಡಿ</translation>
+<translation id="3919229493046408863">ಸಾಧನಗಳು ಸಮೀಪದಲ್ಲಿರುವಾಗ ಅಧಿಸೂಚನೆಯನ್ನು ಆಫ್ ಮಾಡಿ</translation>
 <translation id="3919798653937160644">ನಿಮ್ಮ ಕಂಪ್ಯೂಟರ್‌ನಲ್ಲಿ ತೆರೆದಿರುವ ಎಲ್ಲ ಅತಿಥಿ ವಿಂಡೊಗಳನ್ನು ಮುಚ್ಚಿದ ನಂತರ ಈ ವಿಂಡೊದಲ್ಲಿ ನೀವು ವೀಕ್ಷಿಸುವ ಪುಟಗಳು ಬ್ರೌಸರ್ ಇತಿಹಾಸದಲ್ಲಿ ಗೋಚರಿಸುವುದಿಲ್ಲ ಮತ್ತು ಅವುಗಳು ಕುಕೀಗಳಂತಹ ಇತರ ಗುರುತುಗಳನ್ನು ಕಂಪ್ಯೂಟರ್‌ನಲ್ಲಿ ಉಳಿಯಲು ಬಿಡುವುದಿಲ್ಲ. ಆದರೂ, ನೀವು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿದ ಯಾವುದೇ ಫೈಲ್‌ಗಳನ್ನು ರಕ್ಷಣೆ ಮಾಡಿ ಇಟ್ಟುಕೊಳ್ಳಲಾಗುತ್ತದೆ.</translation>
 <translation id="3920504717067627103">ಪ್ರಮಾಣಪತ್ರ ನೀತಿಗಳು</translation>
 <translation id="392089482157167418">ChromeVox ಸಕ್ರಿಯಗೊಳಿಸಿ (ಮಾತಿನ ಪ್ರತಿಕ್ರಿಯೆ)</translation>
@@ -5930,6 +5931,7 @@
 <translation id="7167486101654761064">&amp;ಯಾವಾಗಲೂ ಈ ಪ್ರಕಾರದ ಫೈಲ್ ಅನ್ನು ತೆರೆಯಿರಿ</translation>
 <translation id="716810439572026343"><ph name="FILE_NAME" /> ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ</translation>
 <translation id="7168109975831002660">ಕನಿಷ್ಠ ಫಾಂಟ್ ಗಾತ್ರ</translation>
+<translation id="7169122689956315694">ಸಾಧನಗಳು ಸಮೀಪದಲ್ಲಿರುವಾಗ ಅಧಿಸೂಚನೆಯನ್ನು ಆನ್ ಮಾಡಿ</translation>
 <translation id="7170236477717446850">ಪ್ರೊಫೈಲ್‌ ಚಿತ್ರ</translation>
 <translation id="7171000599584840888">ಪ್ರೊಫೈಲ್ ಸೇರಿಸಿ...</translation>
 <translation id="7171259390164035663">ನೋಂದಾಯಿಸಿಕೊಳ್ಳಬೇಡಿ</translation>
diff --git a/chrome/app/resources/generated_resources_lo.xtb b/chrome/app/resources/generated_resources_lo.xtb
index 3bbc9196..22cbddd 100644
--- a/chrome/app/resources/generated_resources_lo.xtb
+++ b/chrome/app/resources/generated_resources_lo.xtb
@@ -2898,6 +2898,7 @@
 <translation id="3916445069167113093">ໄຟລ໌ປະເພດນີ້ສາມາດເປັນອັນຕະລາຍຕໍ່ຄອມພິວເຕີຂອງທ່ານໄດ້. ແນວໃດທ່ານກໍ່ຕ້ອງການເກັບ <ph name="FILE_NAME" /> ໄວ້ບໍ?</translation>
 <translation id="3918972485393593704">ລາຍງານລາຍລະອຽດຫາ Google</translation>
 <translation id="3919145445993746351">ເພື່ອຮັບເອົາສ່ວນຂະຫຍາຍຂອງທ່ານຢູ່ໃນຄອມພິວເຕີທັງໝົດຂອງທ່ານ, ກະລຸນາເປີດການຊິ້ງຂໍ້ມູນ</translation>
+<translation id="3919229493046408863">ປິດການແຈ້ງເຕືອນເມື່ອມີອຸປະກອນຢູ່ໃກ້ຄຽງ</translation>
 <translation id="3919798653937160644">ໜ້າເວັບຕ່າງໆທີ່ທ່ານເບິ່ງຢູ່ໜ້າຈໍນີ້ຈະບໍ່ປາກົດໃນປະຫວັດການທ່ອງເວັບ ແລະ ພວກມັນຈະບໍ່ມີຮ່ອງຮອຍອື່ນໆ ເຊັ່ນ: ຄຸກກີ້ ຢູ່ຄອມພິວເຕີຫຼັງຈາກທີ່ທ່ານປິດໜ້າຈໍແຂກທັງໝົດ. ຢ່າງໃດກໍຕາມ, ໄຟລ໌ທີ່ທ່ານດາວໂຫຼດມາຈະຖືກເກັບໄວ້.</translation>
 <translation id="3920504717067627103">ນະໂຍບາຍວ່າດ້ວຍໃບຢັ້ງຢືນ</translation>
 <translation id="392089482157167418">ເປີດໃຊ້ງານ ChromeVox (ຄໍາ​ຄິດ​​ເຫັນທີ່​ເວົ້າມາ​)່</translation>
@@ -5931,6 +5932,7 @@
 <translation id="7167486101654761064">ເປີດໄຟລ໌ປະເພດນີ້ທຸກຄັ້ງ</translation>
 <translation id="716810439572026343">ກຳ​ລັງດາວ​ໂຫລດ <ph name="FILE_NAME" /></translation>
 <translation id="7168109975831002660">ຂະໜາດຟ້ອນຕ໌ໜັງສືຕໍ່າສຸດ</translation>
+<translation id="7169122689956315694">ເປີດການແຈ້ງເຕືອນເມື່ອມີອຸປະກອນຢູ່ໃກ້ຄຽງ</translation>
 <translation id="7170236477717446850">ຮູບໂປຣໄຟລ໌</translation>
 <translation id="7171000599584840888">ເພີ່ມໂປຣໄຟລ໌່...</translation>
 <translation id="7171259390164035663">ບໍ່ລົງທະບຽນ</translation>
diff --git a/chrome/app/resources/generated_resources_ms.xtb b/chrome/app/resources/generated_resources_ms.xtb
index adddf091..9788564 100644
--- a/chrome/app/resources/generated_resources_ms.xtb
+++ b/chrome/app/resources/generated_resources_ms.xtb
@@ -2901,6 +2901,7 @@
 <translation id="3916445069167113093">Jenis fail ini boleh membahayakan komputer anda. Adakah anda mahu terus menyimpan <ph name="FILE_NAME" />?</translation>
 <translation id="3918972485393593704">Laporkan butiran kepada Google</translation>
 <translation id="3919145445993746351">Hidupkan penyegerakan untuk mendapatkan sambungan anda pada semua komputer anda</translation>
+<translation id="3919229493046408863">Matikan pemberitahuan apabila terdapat peranti berdekatan</translation>
 <translation id="3919798653937160644">Halaman yang anda lihat dalam tetingkap ini tidak akan dipaparkan dalam sejarah penyemak imbas dan halaman ini tidak akan meninggalkan surih lain, seperti kuki, pada komputer selepas anda menutup semua tetingkap Tetamu yang terbuka. Walau bagaimanapun, sebarang fail yang anda muat turun akan dikekalkan.</translation>
 <translation id="3920504717067627103">Dasar Sijil</translation>
 <translation id="392089482157167418">Dayakan ChromeVox (maklum balas dituturkan)</translation>
@@ -5935,6 +5936,7 @@
 <translation id="7167486101654761064">&amp;Sentiasa buka fail jenis ini</translation>
 <translation id="716810439572026343">Memuat turun <ph name="FILE_NAME" /></translation>
 <translation id="7168109975831002660">Saiz fon minimum</translation>
+<translation id="7169122689956315694">Hidupkan pemberitahuan apabila terdapat peranti berdekatan</translation>
 <translation id="7170236477717446850">Gambar profil</translation>
 <translation id="7171000599584840888">Tambah Profil...</translation>
 <translation id="7171259390164035663">Jangan daftar</translation>
diff --git a/chrome/app/resources/generated_resources_ne.xtb b/chrome/app/resources/generated_resources_ne.xtb
index 1f9e5f8..cfbbe8d 100644
--- a/chrome/app/resources/generated_resources_ne.xtb
+++ b/chrome/app/resources/generated_resources_ne.xtb
@@ -2884,6 +2884,7 @@
 <translation id="3916445069167113093">यस प्रकारको फाइलले तपाइँको कम्प्युटरलाई हानी गर्न सक्छ। के तपाइँ अझै पनि <ph name="FILE_NAME" /> राख्न चाहनुहुन्छ?</translation>
 <translation id="3918972485393593704">Google लाई विवरणहरू पठाउनुहोस्</translation>
 <translation id="3919145445993746351">आफ्ना सबै कम्प्युटरमा विस्तारहरू प्राप्त गर्न सिंक गर्ने सुविधा सक्रिय गर्नुहोस्</translation>
+<translation id="3919229493046408863">डिभाइसहरू नजिकै हुँदा सूचना दिने सुविधा अफ गरियोस्</translation>
 <translation id="3919798653937160644">तपाईंले यो विन्डोमा हेर्ने पेजहरू ब्राउजर इतिहासमा देखिने छैनन् र तपाईंले सबै गेस्ट विन्डोहरू बन्द गरेपछि ती पेजहरूले कम्प्युटरमा कुकी जस्ता कुनै पनि ट्रेस छाड्ने छैनन्। तर तपाईंले डाउनलोड गर्नुभएका सबै फाइलहरू भने सुरक्षित राखिने छन्।</translation>
 <translation id="3920504717067627103">प्रमाणपत्र नीतिहरू</translation>
 <translation id="392089482157167418">ChromeVox सक्षम गर्नुहोस् (बोलिएको पृष्ठपोषण)</translation>
@@ -5915,6 +5916,7 @@
 <translation id="7167486101654761064">यस किसिमको फाइलहरू &amp;सधैं खोल्नुहोस्</translation>
 <translation id="716810439572026343"><ph name="FILE_NAME" /> डाउनलोड हुँदै</translation>
 <translation id="7168109975831002660">न्यूनतम फन्ट साइज</translation>
+<translation id="7169122689956315694">डिभाइसहरू नजिकै हुँदा सूचना दिने सुविधा अन गरियोस्</translation>
 <translation id="7170236477717446850">प्रोफाइल तस्बिर</translation>
 <translation id="7171000599584840888">प्रोफाइल हाल्नुहोस्...</translation>
 <translation id="7171259390164035663">दर्ता नगर्नुहोस्</translation>
diff --git a/chrome/app/resources/generated_resources_nl.xtb b/chrome/app/resources/generated_resources_nl.xtb
index 05a1e2fa..4c69d21 100644
--- a/chrome/app/resources/generated_resources_nl.xtb
+++ b/chrome/app/resources/generated_resources_nl.xtb
@@ -2885,6 +2885,7 @@
 <translation id="3916445069167113093">Dit type bestand kan schadelijk zijn voor je computer. Wil je <ph name="FILE_NAME" /> toch behouden?</translation>
 <translation id="3918972485393593704">Details melden aan Google</translation>
 <translation id="3919145445993746351">Zet de synchronisatie aan om op al je computers toegang te krijgen tot je extensies</translation>
+<translation id="3919229493046408863">Melding uitzetten als apparaten in de buurt zijn</translation>
 <translation id="3919798653937160644">De pagina's die je in dit venster bekijkt, zijn niet zichtbaar in de browsergeschiedenis en laten geen sporen (zoals cookies) op de computer achter nadat je alle geopende gastvensters hebt gesloten. Bestanden die je downloadt, blijven wel behouden.</translation>
 <translation id="3920504717067627103">Certificaatbeleid</translation>
 <translation id="392089482157167418">ChromeVox (gesproken feedback) aanzetten</translation>
@@ -5916,6 +5917,7 @@
 <translation id="7167486101654761064">&amp;Altijd bestanden van dit type openen</translation>
 <translation id="716810439572026343"><ph name="FILE_NAME" /> downloaden</translation>
 <translation id="7168109975831002660">Minimum lettergrootte</translation>
+<translation id="7169122689956315694">Melding aanzetten als apparaten in de buurt zijn</translation>
 <translation id="7170236477717446850">Profielfoto</translation>
 <translation id="7171000599584840888">Profiel toevoegen...</translation>
 <translation id="7171259390164035663">Niet inschrijven</translation>
diff --git a/chrome/app/resources/generated_resources_pl.xtb b/chrome/app/resources/generated_resources_pl.xtb
index 9b36196..901e7963 100644
--- a/chrome/app/resources/generated_resources_pl.xtb
+++ b/chrome/app/resources/generated_resources_pl.xtb
@@ -2883,6 +2883,7 @@
 <translation id="3916445069167113093">Pliki tego typu mogą wyrządzić szkody na komputerze. Czy chcesz mimo tego zachować plik <ph name="FILE_NAME" />?</translation>
 <translation id="3918972485393593704">Przesyłaj do Google szczegółowe informacje</translation>
 <translation id="3919145445993746351">Aby korzystać z rozszerzeń na wszystkich swoich komputerach, włącz synchronizację</translation>
+<translation id="3919229493046408863">Wyłącz powiadomienia, gdy urządzenia są w pobliżu</translation>
 <translation id="3919798653937160644">Strony wyświetlane w tym oknie nie pojawią się w historii przeglądarki. Gdy zamkniesz wszystkie okna otwarte w trybie gościa, nie zostanie po nich na komputerze żaden ślad (np. w postaci plików cookie). Pobrane pliki zostaną jednak zachowane.</translation>
 <translation id="3920504717067627103">Zasady certyfikatu</translation>
 <translation id="392089482157167418">Włącz ChromeVox (komunikaty głosowe)</translation>
@@ -5915,6 +5916,7 @@
 <translation id="7167486101654761064">&amp;Zawsze otwieraj pliki tego typu</translation>
 <translation id="716810439572026343">Pobieram plik <ph name="FILE_NAME" /></translation>
 <translation id="7168109975831002660">Minimalny rozmiar czcionki</translation>
+<translation id="7169122689956315694">Włącz powiadomienia, gdy urządzenia są w pobliżu</translation>
 <translation id="7170236477717446850">Zdjęcie profilowe</translation>
 <translation id="7171000599584840888">Dodaj profil…</translation>
 <translation id="7171259390164035663">Nie rejestruj</translation>
diff --git a/chrome/app/resources/generated_resources_ro.xtb b/chrome/app/resources/generated_resources_ro.xtb
index a64c823..98bdcd6 100644
--- a/chrome/app/resources/generated_resources_ro.xtb
+++ b/chrome/app/resources/generated_resources_ro.xtb
@@ -2887,6 +2887,7 @@
 <translation id="3916445069167113093">Acest tip de fișier poate dăuna computerului. Vrei să păstrezi <ph name="FILE_NAME" /> totuși?</translation>
 <translation id="3918972485393593704">Raportează detaliile la Google</translation>
 <translation id="3919145445993746351">Pentru a accesa extensiile pe toate computerele, activează sincronizarea</translation>
+<translation id="3919229493046408863">Dezactivează notificarea când există dispozitive în apropiere</translation>
 <translation id="3919798653937160644">Paginile pe care le accesezi în această fereastră nu vor apărea în istoricul browserului și nu vor lăsa alte urme pe computer (de exemplu, cookie-uri) după ce închizi toate ferestrele deschise în modul pentru invitați. Însă fișierele pe care le descarci vor fi păstrate.</translation>
 <translation id="3920504717067627103">Politici de certificat</translation>
 <translation id="392089482157167418">Activați ChromeVox (feedback rostit)</translation>
@@ -5920,6 +5921,7 @@
 <translation id="7167486101654761064">&amp;Deschide întotdeauna fișierele de acest tip</translation>
 <translation id="716810439572026343"><ph name="FILE_NAME" /> se descarcă</translation>
 <translation id="7168109975831002660">Dimensiunea minimă a fontului</translation>
+<translation id="7169122689956315694">Activează notificarea când există dispozitive în apropiere</translation>
 <translation id="7170236477717446850">Fotografie de profil</translation>
 <translation id="7171000599584840888">Adaugă un profil...</translation>
 <translation id="7171259390164035663">Nu doresc să mă înregistrez</translation>
diff --git a/chrome/app/resources/generated_resources_te.xtb b/chrome/app/resources/generated_resources_te.xtb
index e27873a..67f7e73 100644
--- a/chrome/app/resources/generated_resources_te.xtb
+++ b/chrome/app/resources/generated_resources_te.xtb
@@ -2900,6 +2900,7 @@
 <translation id="3916445069167113093">ఈ రకం ఫైల్‌ మీ కంప్యూటర్‌కు హాని చేయవచ్చు. అయినా సరే <ph name="FILE_NAME" />ని ఉంచాలని అనుకుంటున్నారా?</translation>
 <translation id="3918972485393593704">ఈ వివరాలను Googleకు రిపోర్ట్ చేయండి</translation>
 <translation id="3919145445993746351">మీ అన్ని కంప్యూటర్‌లలో మీ ఎక్స్‌టెన్షన్‌లను పొందడానికి, సింక్‌ను ఆన్ చేయండి</translation>
+<translation id="3919229493046408863">పరికరాలు సమీపంలో ఉన్నప్పుడు నోటిఫికేషన్‌లను ఆఫ్ చేయండి</translation>
 <translation id="3919798653937160644">మీరు ఈ విండో‌లో వీక్షించే పేజీలు బ్రౌజింగ్ హిస్టరీలో కనిపించవు, అలాగే తెరిచిన అన్ని గెస్ట్ విండోలను మీరు మూసివేసిన తర్వాత, అవి కంప్యూటర్‌లో కుక్కీల వంటి ఇతర ట్రేస్‌లను వదలవు. అయితే, మీరు డౌన్‌లోడ్ చేసే ఫైళ్లు భద్రపరచబడతాయి.</translation>
 <translation id="3920504717067627103">సర్టిఫికెట్ విధానాలు</translation>
 <translation id="392089482157167418">ChromeVox (చదవబడే అభిప్రాయం)ను ప్రారంభించు</translation>
@@ -5934,6 +5935,7 @@
 <translation id="7167486101654761064">&amp;ఎల్లప్పుడూ ఈ రకం ఫైళ్ళను తెరువు</translation>
 <translation id="716810439572026343"><ph name="FILE_NAME" />ని డౌన్‌లోడ్ చేస్తోంది</translation>
 <translation id="7168109975831002660">కనిష్ఠ ఫాంట్ పరిమాణం</translation>
+<translation id="7169122689956315694">పరికరాలు సమీపంలో ఉన్నప్పుడు నోటిఫికేషన్‌లను ఆన్ చేయండి</translation>
 <translation id="7170236477717446850">ప్రొఫైల్ ఫోటో</translation>
 <translation id="7171000599584840888">ప్రొఫైల్‌ను జోడించండి...</translation>
 <translation id="7171259390164035663">నమోదు చేసుకోవద్దు</translation>
diff --git a/chrome/app/resources/generated_resources_th.xtb b/chrome/app/resources/generated_resources_th.xtb
index 15770db..81222bce 100644
--- a/chrome/app/resources/generated_resources_th.xtb
+++ b/chrome/app/resources/generated_resources_th.xtb
@@ -2885,6 +2885,7 @@
 <translation id="3916445069167113093">ไฟล์ประเภทนี้อาจเป็นอันตรายต่อคอมพิวเตอร์ของคุณ คุณต้องการเก็บ <ph name="FILE_NAME" /> ไว้หรือไม่</translation>
 <translation id="3918972485393593704">รายงานรายละเอียดให้ Google ทราบ</translation>
 <translation id="3919145445993746351">เปิดการซิงค์เพื่อรับส่วนขยายในคอมพิวเตอร์ทุกเครื่อง</translation>
+<translation id="3919229493046408863">ปิดการแจ้งเตือนเมื่อมีอุปกรณ์อยู่ใกล้เคียง</translation>
 <translation id="3919798653937160644">หน้าเว็บที่คุณดูในหน้าต่างนี้จะไม่ปรากฏในประวัติการใช้เบราว์เซอร์และจะไม่ทิ้งร่องรอยอื่นๆ เช่น คุกกี้ ไว้ในคอมพิวเตอร์หลังจากคุณปิดหน้าต่างผู้เยี่ยมชมที่เปิดอยู่ทั้งหมด แต่จะยังมีการเก็บรักษาไฟล์ที่คุณดาวน์โหลดไว้</translation>
 <translation id="3920504717067627103">นโยบายใบรับรอง</translation>
 <translation id="392089482157167418">เปิดใช้ ChromeVox (การตอบสนองด้วยเสียง)</translation>
@@ -5918,6 +5919,7 @@
 <translation id="7167486101654761064">เปิดไฟล์ประเภทนี้เ&amp;สมอ</translation>
 <translation id="716810439572026343">กำลังดาวน์โหลด <ph name="FILE_NAME" /></translation>
 <translation id="7168109975831002660">ขนาดแบบอักษรขั้นต่ำ</translation>
+<translation id="7169122689956315694">เปิดการแจ้งเตือนเมื่อมีอุปกรณ์อยู่ใกล้เคียง</translation>
 <translation id="7170236477717446850">รูปโปรไฟล์</translation>
 <translation id="7171000599584840888">เพิ่มโปรไฟล์...</translation>
 <translation id="7171259390164035663">ไม่ต้องลงทะเบียน</translation>
diff --git a/chrome/app/resources/generated_resources_zh-HK.xtb b/chrome/app/resources/generated_resources_zh-HK.xtb
index 11e83590..17c8b6b 100644
--- a/chrome/app/resources/generated_resources_zh-HK.xtb
+++ b/chrome/app/resources/generated_resources_zh-HK.xtb
@@ -2900,6 +2900,7 @@
 <translation id="3916445069167113093">這種類型的檔案可能會損害您的電腦,您仍要保留 <ph name="FILE_NAME" /> 嗎?</translation>
 <translation id="3918972485393593704">向 Google 報告詳情</translation>
 <translation id="3919145445993746351">如要在所有電腦上取得您的擴充程式,請開啟同步功能</translation>
+<translation id="3919229493046408863">附近有其他裝置時關閉通知</translation>
 <translation id="3919798653937160644">您在這個視窗瀏覽的網頁不會顯示在瀏覽器記錄中,並且在您關閉所有開啟的訪客視窗後,電腦也不會留下 Cookie 等其他記錄。不過,系統將會保留您下載的所有檔案。</translation>
 <translation id="3920504717067627103">憑證政策</translation>
 <translation id="392089482157167418">啟用 ChromeVox (互動朗讀)</translation>
@@ -5935,6 +5936,7 @@
 <translation id="7167486101654761064">保持開啟這類檔案(&amp;A)</translation>
 <translation id="716810439572026343">正在下載 <ph name="FILE_NAME" /></translation>
 <translation id="7168109975831002660">最小字型大小</translation>
+<translation id="7169122689956315694">附近有其他裝置時開啟通知</translation>
 <translation id="7170236477717446850">個人檔案相片</translation>
 <translation id="7171000599584840888">新增設定檔…</translation>
 <translation id="7171259390164035663">不要註冊</translation>
diff --git a/chrome/app/resources/generated_resources_zh-TW.xtb b/chrome/app/resources/generated_resources_zh-TW.xtb
index ad55491..4f8cf66 100644
--- a/chrome/app/resources/generated_resources_zh-TW.xtb
+++ b/chrome/app/resources/generated_resources_zh-TW.xtb
@@ -2886,6 +2886,7 @@
 <translation id="3916445069167113093">這種類型的檔案可能會損害你的電腦,你要保留 <ph name="FILE_NAME" /> 這個檔案嗎?</translation>
 <translation id="3918972485393593704">向 Google 回報詳細資料</translation>
 <translation id="3919145445993746351">如要在所有電腦上使用你的擴充功能,請開啟同步功能</translation>
+<translation id="3919229493046408863">附近有其他裝置時關閉通知</translation>
 <translation id="3919798653937160644">你在這個視窗瀏覽的網頁不會顯示在瀏覽器記錄中,而且所有開啟的訪客視窗都關閉後,電腦上也不會留下 Cookie 等其他追蹤記錄。不過,系統將保留你下載的所有檔案。</translation>
 <translation id="3920504717067627103">憑證原則</translation>
 <translation id="392089482157167418">啟用 ChromeVox (互動朗讀)</translation>
@@ -5918,6 +5919,7 @@
 <translation id="7167486101654761064">一律開啟這類檔案(&amp;A)</translation>
 <translation id="716810439572026343">正在下載 <ph name="FILE_NAME" /></translation>
 <translation id="7168109975831002660">最小字體</translation>
+<translation id="7169122689956315694">附近有其他裝置時開啟通知</translation>
 <translation id="7170236477717446850">個人資料相片</translation>
 <translation id="7171000599584840888">新增設定檔...</translation>
 <translation id="7171259390164035663">不註冊</translation>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3c02889..0c4c7cebb 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1127,6 +1127,8 @@
     "permissions/permission_manager_factory.h",
     "permissions/prediction_based_permission_ui_selector.cc",
     "permissions/prediction_based_permission_ui_selector.h",
+    "permissions/prediction_model_handler_factory.cc",
+    "permissions/prediction_model_handler_factory.h",
     "permissions/prediction_service_factory.cc",
     "permissions/prediction_service_factory.h",
     "permissions/prediction_service_request.cc",
diff --git a/chrome/browser/android/autofill_assistant/client_android.cc b/chrome/browser/android/autofill_assistant/client_android.cc
index 13a11d1..17bb4610 100644
--- a/chrome/browser/android/autofill_assistant/client_android.cc
+++ b/chrome/browser/android/autofill_assistant/client_android.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/android/autofill_assistant/ui_controller_android_utils.h"
 #include "chrome/browser/autofill/android/personal_data_manager_android.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
-#include "chrome/browser/browser_process.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -538,7 +537,7 @@
 
 std::string ClientAndroid::GetCountryCode() const {
   variations::VariationsService* variations_service =
-      g_browser_process->variations_service();
+      dependencies_->GetVariationsService();
   // Use fallback "ZZ" if no country is available.
   if (!variations_service || variations_service->GetLatestCountry().empty())
     return "ZZ";
diff --git a/chrome/browser/android/autofill_assistant/dependencies.h b/chrome/browser/android/autofill_assistant/dependencies.h
index 13a0d7a..62b0bdb 100644
--- a/chrome/browser/android/autofill_assistant/dependencies.h
+++ b/chrome/browser/android/autofill_assistant/dependencies.h
@@ -9,6 +9,7 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/strings/string_piece.h"
 #include "chrome/browser/android/autofill_assistant/assistant_field_trial_util.h"
+#include "components/variations/service/variations_service.h"
 
 namespace autofill_assistant {
 
@@ -29,6 +30,8 @@
   virtual std::unique_ptr<AssistantFieldTrialUtil> CreateFieldTrialUtil()
       const = 0;
 
+  virtual variations::VariationsService* GetVariationsService() const = 0;
+
  protected:
   Dependencies(JNIEnv* env,
                const base::android::JavaParamRef<jobject>& java_object);
diff --git a/chrome/browser/android/autofill_assistant/dependencies_chrome.cc b/chrome/browser/android/autofill_assistant/dependencies_chrome.cc
index ac9c1fe..9b0b273f 100644
--- a/chrome/browser/android/autofill_assistant/dependencies_chrome.cc
+++ b/chrome/browser/android/autofill_assistant/dependencies_chrome.cc
@@ -8,11 +8,13 @@
 #include "base/strings/string_piece.h"
 #include "chrome/android/features/autofill_assistant/jni_headers/AssistantStaticDependenciesChrome_jni.h"
 #include "chrome/browser/android/autofill_assistant/assistant_field_trial_util.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 
 using ::base::StringPiece;
 using ::base::android::JavaParamRef;
 using ::base::android::ScopedJavaGlobalRef;
+using ::variations::VariationsService;
 
 namespace autofill_assistant {
 
@@ -40,4 +42,9 @@
   return std::make_unique<AssistantFieldTrialUtilChrome>();
 }
 
+variations::VariationsService* DependenciesChrome::GetVariationsService()
+    const {
+  return g_browser_process->variations_service();
+}
+
 }  // namespace autofill_assistant
diff --git a/chrome/browser/android/autofill_assistant/dependencies_chrome.h b/chrome/browser/android/autofill_assistant/dependencies_chrome.h
index ebb7c59..908404937 100644
--- a/chrome/browser/android/autofill_assistant/dependencies_chrome.h
+++ b/chrome/browser/android/autofill_assistant/dependencies_chrome.h
@@ -9,7 +9,9 @@
 
 #include "base/android/scoped_java_ref.h"
 #include "base/strings/string_piece.h"
+#include "chrome/browser/android/autofill_assistant/assistant_field_trial_util.h"
 #include "components/metrics/metrics_service_accessor.h"
+#include "components/variations/service/variations_service.h"
 
 namespace autofill_assistant {
 
@@ -22,6 +24,8 @@
 
   std::unique_ptr<AssistantFieldTrialUtil> CreateFieldTrialUtil()
       const override;
+
+  variations::VariationsService* GetVariationsService() const override;
 };
 
 }  // namespace autofill_assistant
diff --git a/chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.cc b/chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.cc
index 6c18237..48f35fd 100644
--- a/chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.cc
+++ b/chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.h"
 
 #include "base/syslog_logging.h"
-#include "chrome/browser/ash/app_mode/kiosk_app_launch_error.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_launcher.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/ash/app_mode/startup_app_launcher.h"
@@ -14,17 +13,24 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/install_tracker_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "extensions/browser/disable_reason.h"
 #include "extensions/browser/extension_system.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/file_util.h"
 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
+#include "extensions/common/manifest_handlers/offline_enabled_info.h"
 
 namespace ash {
 
 ChromeAppKioskAppInstaller::ChromeAppKioskAppInstaller(
     Profile* profile,
     const std::string& app_id,
-    KioskAppLauncher::Delegate* delegate)
-    : profile_(profile), app_id_(app_id), delegate_(delegate) {}
+    KioskAppLauncher::Delegate* delegate,
+    bool finalize_only)
+    : profile_(profile),
+      app_id_(app_id),
+      delegate_(delegate),
+      finalize_only_(finalize_only) {}
 
 ChromeAppKioskAppInstaller::~ChromeAppKioskAppInstaller() {}
 
@@ -35,6 +41,11 @@
 
   on_ready_callback_ = std::move(callback);
 
+  if (finalize_only_) {
+    FinalizeAppInstall();
+    return;
+  }
+
   extensions::file_util::SetUseSafeInstallation(true);
   KioskAppManager::Get()->UpdatePrimaryAppLoaderPrefs(app_id_);
   if (IsAppInstallPending(app_id_)) {
@@ -46,13 +57,15 @@
   const extensions::Extension* primary_app = GetPrimaryAppExtension();
   if (!primary_app) {
     // The extension is skipped for installation due to some error.
-    ReportInstallFailure(KioskAppLaunchError::Error::kUnableToInstall);
+    ReportInstallFailure(
+        ChromeAppKioskAppInstaller::InstallResult::kUnableToInstall);
     return;
   }
 
   if (!extensions::KioskModeInfo::IsKioskEnabled(primary_app)) {
     // The installed primary app is not kiosk enabled.
-    ReportInstallFailure(KioskAppLaunchError::Error::kNotKioskEnabled);
+    ReportInstallFailure(
+        ChromeAppKioskAppInstaller::InstallResult::kNotKioskEnabled);
     return;
   }
 
@@ -90,7 +103,8 @@
     // Check extension update before launching the primary kiosk app.
     MaybeCheckExtensionUpdate();
   } else {
-    ReportInstallFailure(KioskAppLaunchError::Error::kUnableToInstall);
+    ReportInstallFailure(
+        ChromeAppKioskAppInstaller::InstallResult::kUnableToInstall);
   }
 }
 
@@ -144,7 +158,42 @@
 void ChromeAppKioskAppInstaller::FinalizeAppInstall() {
   DCHECK(!install_complete_);
 
-  std::move(on_ready_callback_).Run(KioskAppLaunchError::Error::kNone);
+  const extensions::Extension* primary_app = GetPrimaryAppExtension();
+  // Verify that required apps are installed. While the apps should be
+  // present at this point, crash recovery flow skips app installation steps -
+  // this means that the kiosk app might not yet be downloaded. If that is
+  // the case, bail out from the app launch.
+  if (!primary_app || !AreSecondaryAppsInstalled()) {
+    ReportInstallFailure(
+        ChromeAppKioskAppInstaller::InstallResult::kUnableToLaunch);
+    return;
+  }
+
+  const bool offline_enabled =
+      extensions::OfflineEnabledInfo::IsOfflineEnabled(primary_app);
+  // If the app is not offline enabled, make sure the network is ready before
+  // launching.
+  if (!offline_enabled && !delegate_->IsNetworkReady()) {
+    ReportInstallFailure(InstallResult::kNetworkMissing);
+    return;
+  }
+
+  install_complete_ = true;
+
+  SetSecondaryAppsEnabledState(primary_app);
+  MaybeUpdateAppData();
+
+  ReportInstallSuccess();
+}
+
+void ChromeAppKioskAppInstaller::MaybeUpdateAppData() {
+  // Skip copying meta data from the current installed primary app when
+  // there is a pending update.
+  if (PrimaryAppHasPendingUpdate())
+    return;
+
+  KioskAppManager::Get()->ClearAppData(app_id_);
+  KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_, profile_, NULL);
 }
 
 void ChromeAppKioskAppInstaller::OnFinishCrxInstall(
@@ -157,7 +206,8 @@
 
   if (DidPrimaryOrSecondaryAppFailedToInstall(success, extension_id)) {
     install_observation_.Reset();
-    ReportInstallFailure(KioskAppLaunchError::Error::kUnableToInstall);
+    ReportInstallFailure(
+        ChromeAppKioskAppInstaller::InstallResult::kUnableToInstall);
     return;
   }
 
@@ -177,12 +227,14 @@
 
   const extensions::Extension* primary_app = GetPrimaryAppExtension();
   if (!primary_app) {
-    ReportInstallFailure(KioskAppLaunchError::Error::kUnableToInstall);
+    ReportInstallFailure(
+        ChromeAppKioskAppInstaller::InstallResult::kUnableToInstall);
     return;
   }
 
   if (!extensions::KioskModeInfo::IsKioskEnabled(primary_app)) {
-    ReportInstallFailure(KioskAppLaunchError::Error::kNotKioskEnabled);
+    ReportInstallFailure(
+        ChromeAppKioskAppInstaller::InstallResult::kNotKioskEnabled);
     return;
   }
 
@@ -192,10 +244,18 @@
     MaybeCheckExtensionUpdate();
 }
 
+void ChromeAppKioskAppInstaller::ReportInstallSuccess() {
+  DCHECK(install_complete_);
+  SYSLOG(INFO) << "Kiosk app is ready to launch.";
+
+  std::move(on_ready_callback_)
+      .Run(ChromeAppKioskAppInstaller::InstallResult::kSuccess);
+}
+
 void ChromeAppKioskAppInstaller::ReportInstallFailure(
-    KioskAppLaunchError::Error error) {
+    ChromeAppKioskAppInstaller::InstallResult error) {
   SYSLOG(ERROR) << "App launch failed, error: " << static_cast<int>(error);
-  DCHECK_NE(KioskAppLaunchError::Error::kNone, error);
+  DCHECK_NE(ChromeAppKioskAppInstaller::InstallResult::kSuccess, error);
 
   std::move(on_ready_callback_).Run(error);
 }
@@ -250,6 +310,12 @@
   return false;
 }
 
+bool ChromeAppKioskAppInstaller::PrimaryAppHasPendingUpdate() const {
+  return extensions::ExtensionSystem::Get(profile_)
+      ->extension_service()
+      ->GetPendingExtensionUpdate(app_id_);
+}
+
 bool ChromeAppKioskAppInstaller::DidPrimaryOrSecondaryAppFailedToInstall(
     bool success,
     const std::string& id) const {
@@ -277,4 +343,44 @@
   return false;
 }
 
-}  // namespace ash
\ No newline at end of file
+void ChromeAppKioskAppInstaller::SetSecondaryAppsEnabledState(
+    const extensions::Extension* primary_app) {
+  extensions::KioskModeInfo* info = extensions::KioskModeInfo::Get(primary_app);
+  for (const auto& app_info : info->secondary_apps) {
+    // If the enabled on launch is not specified in the manifest, the apps
+    // enabled state should be kept as is.
+    if (!app_info.enabled_on_launch.has_value())
+      continue;
+
+    SetAppEnabledState(app_info.id, app_info.enabled_on_launch.value());
+  }
+}
+
+void ChromeAppKioskAppInstaller::SetAppEnabledState(
+    const extensions::ExtensionId& id,
+    bool new_enabled_state) {
+  extensions::ExtensionService* service =
+      extensions::ExtensionSystem::Get(profile_)->extension_service();
+  extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
+
+  // If the app is already enabled, and we want it to be enabled, nothing to do.
+  if (service->IsExtensionEnabled(id) && new_enabled_state) {
+    return;
+  }
+
+  if (new_enabled_state) {
+    // Remove USER_ACTION disable reason - if no other disabled reasons are
+    // present, enable the app.
+    prefs->RemoveDisableReason(id,
+                               extensions::disable_reason::DISABLE_USER_ACTION);
+    if (prefs->GetDisableReasons(id) ==
+        extensions::disable_reason::DISABLE_NONE) {
+      service->EnableExtension(id);
+    }
+  } else {
+    service->DisableExtension(id,
+                              extensions::disable_reason::DISABLE_USER_ACTION);
+  }
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.h b/chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.h
index bb3eefb..624e9d4 100644
--- a/chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.h
+++ b/chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.h
@@ -16,14 +16,22 @@
 
 namespace ash {
 
-using InstallCallback =
-    base::OnceCallback<void(KioskAppLaunchError::Error error)>;
-
 class ChromeAppKioskAppInstaller : private extensions::InstallObserver {
  public:
+  enum class InstallResult {
+    kSuccess,
+    kUnableToInstall,
+    kUnableToLaunch,
+    kNotKioskEnabled,
+    kNetworkMissing,
+  };
+
+  using InstallCallback = base::OnceCallback<void(InstallResult result)>;
+
   ChromeAppKioskAppInstaller(Profile* profile,
                              const std::string& app_id,
-                             KioskAppLauncher::Delegate* delegate);
+                             KioskAppLauncher::Delegate* delegate,
+                             bool finalize_only);
   ChromeAppKioskAppInstaller(const ChromeAppKioskAppInstaller&) = delete;
   ChromeAppKioskAppInstaller& operator=(const ChromeAppKioskAppInstaller&) =
       delete;
@@ -31,20 +39,19 @@
 
   void BeginInstall(InstallCallback callback);
 
-  void SetInstallComplete() { install_complete_ = true; }
-  bool install_complete() const { return install_complete_; }
-
  private:
   void MaybeInstallSecondaryApps();
   void MaybeCheckExtensionUpdate();
   void OnExtensionUpdateCheckFinished(bool update_found);
   void FinalizeAppInstall();
+  void MaybeUpdateAppData();
 
   // extensions::InstallObserver overrides.
   void OnFinishCrxInstall(const std::string& extension_id,
                           bool success) override;
 
-  void ReportInstallFailure(KioskAppLaunchError::Error error);
+  void ReportInstallSuccess();
+  void ReportInstallFailure(InstallResult result);
   void RetryWhenNetworkIsAvailable(base::OnceClosure callback);
   void ObserveActiveInstallations();
 
@@ -59,14 +66,22 @@
   // Returns true if any secondary app is pending.
   bool IsAnySecondaryAppPending() const;
 
+  // Returns true if the primary app has a pending update.
+  bool PrimaryAppHasPendingUpdate() const;
+
   // Returns true if the app with |id| failed, and it is the primary or one of
   // the secondary apps.
   bool DidPrimaryOrSecondaryAppFailedToInstall(bool success,
                                                const std::string& id) const;
 
+  void SetSecondaryAppsEnabledState(const extensions::Extension* primary_app);
+  void SetAppEnabledState(const extensions::ExtensionId& id,
+                          bool should_be_enabled);
+
   Profile* const profile_;
   const std::string app_id_;
   KioskAppLauncher::Delegate* delegate_;
+  bool finalize_only_;
 
   InstallCallback on_ready_callback_;
 
diff --git a/chrome/browser/ash/app_mode/startup_app_launcher.cc b/chrome/browser/ash/app_mode/startup_app_launcher.cc
index 54f83769..7145bb3e 100644
--- a/chrome/browser/ash/app_mode/startup_app_launcher.cc
+++ b/chrome/browser/ash/app_mode/startup_app_launcher.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/app_mode/startup_app_launcher.h"
 
+#include <memory>
 #include <utility>
 #include <vector>
 
@@ -18,6 +19,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/browser_app_launcher.h"
 #include "chrome/browser/ash/app_mode/chrome_app_kiosk_app_installer.h"
+#include "chrome/browser/ash/app_mode/kiosk_app_launch_error.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/ash/app_mode/startup_app_launcher_update_checker.h"
 #include "chrome/browser/ash/net/delay_network_call.h"
@@ -52,10 +54,7 @@
 StartupAppLauncher::StartupAppLauncher(Profile* profile,
                                        const std::string& app_id,
                                        StartupAppLauncher::Delegate* delegate)
-    : KioskAppLauncher(delegate),
-      profile_(profile),
-      app_id_(app_id),
-      installer_(profile, app_id, delegate) {
+    : KioskAppLauncher(delegate), profile_(profile), app_id_(app_id) {
   DCHECK(profile_);
   DCHECK(crx_file::id_util::IdIsValid(app_id_));
   kiosk_app_manager_observation_.Observe(KioskAppManager::Get());
@@ -73,15 +72,15 @@
 void StartupAppLauncher::ContinueWithNetworkReady() {
   SYSLOG(INFO) << "ContinueWithNetworkReady"
                << ", network_ready_handled_=" << network_ready_handled_
-               << ", ready_to_launch_=" << installer_.install_complete();
+               << ", ready_to_launch_=" << ready_to_launch_;
 
-  if (installer_.install_complete() || network_ready_handled_)
+  if (ready_to_launch_ || network_ready_handled_)
     return;
 
   network_ready_handled_ = true;
 
   if (delegate_->ShouldSkipAppInstallation()) {
-    MaybeLaunchApp();
+    FinalizeAppInstall();
     return;
   }
 
@@ -96,7 +95,7 @@
   // Do not allow restarts after the launcher finishes kiosk apps installation -
   // notify the delegate that kiosk app is ready to launch, in case the launch
   // was delayed, for example by network config dialog.
-  if (installer_.install_complete()) {
+  if (ready_to_launch_) {
     delegate_->OnAppPrepared();
     return;
   }
@@ -116,7 +115,7 @@
 }
 
 void StartupAppLauncher::MaybeInitializeNetwork() {
-  DCHECK(!installer_.install_complete());
+  DCHECK(!ready_to_launch_);
 
   network_ready_handled_ = false;
 
@@ -137,7 +136,7 @@
   }
 
   if (delegate_->ShouldSkipAppInstallation()) {
-    MaybeLaunchApp();
+    FinalizeAppInstall();
     return;
   }
 
@@ -149,83 +148,6 @@
     BeginInstall();
 }
 
-void StartupAppLauncher::SetSecondaryAppsEnabledState(
-    const extensions::Extension* primary_app) {
-  extensions::KioskModeInfo* info = extensions::KioskModeInfo::Get(primary_app);
-  extensions::ExtensionService* service =
-      extensions::ExtensionSystem::Get(profile_)->extension_service();
-  extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
-  for (const auto& app_info : info->secondary_apps) {
-    // If the enabled on launch is not specified in the manifest, the apps
-    // enabled state should be kept as is.
-    if (!app_info.enabled_on_launch.has_value())
-      continue;
-
-    // If the app is already enabled, and should not be disabled, there is
-    // nothing to do for the app.
-    if (app_info.enabled_on_launch.value() &&
-        service->IsExtensionEnabled(app_info.id)) {
-      continue;
-    }
-
-    if (!app_info.enabled_on_launch.value()) {
-      service->DisableExtension(
-          app_info.id, extensions::disable_reason::DISABLE_USER_ACTION);
-    } else {
-      // Remove USER_ACTION disable reason - if the app was disabled only due to
-      // user action, enable it.
-      if (prefs->GetDisableReasons(app_info.id) ==
-          extensions::disable_reason::DISABLE_USER_ACTION) {
-        service->EnableExtension(app_info.id);
-      } else {
-        prefs->RemoveDisableReason(
-            app_info.id, extensions::disable_reason::DISABLE_USER_ACTION);
-      }
-    }
-  }
-}
-
-void StartupAppLauncher::MaybeLaunchApp() {
-  DCHECK(!installer_.install_complete());
-
-  SYSLOG(INFO) << "MaybeLaunchApp";
-  const Extension* extension = GetPrimaryAppExtension();
-  // Verify that requred apps are installed. While the apps should be
-  // present at this point, crash recovery flow skips app installation steps -
-  // this means that the kiosk app might not yet be downloaded. If that is
-  // the case, bail out from the app launch.
-  if (!extension || !AreSecondaryAppsInstalled()) {
-    OnLaunchFailure(KioskAppLaunchError::Error::kUnableToLaunch);
-    return;
-  }
-
-  const bool offline_enabled =
-      extensions::OfflineEnabledInfo::IsOfflineEnabled(extension);
-  // If the app is not offline enabled, make sure the network is ready before
-  // launching.
-  if (offline_enabled || delegate_->IsNetworkReady()) {
-    installer_.SetInstallComplete();
-    // Updates to cached primary app crx will be ignored after this point, so
-    // there is no need to observe the kiosk app manager any longer.
-    kiosk_app_manager_observation_.Reset();
-
-    SetSecondaryAppsEnabledState(extension);
-
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(&StartupAppLauncher::OnReadyToLaunch,
-                                  weak_ptr_factory_.GetWeakPtr()));
-  } else {
-    ++launch_attempt_;
-    if (launch_attempt_ < kMaxLaunchAttempt) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&StartupAppLauncher::MaybeInitializeNetwork,
-                                    weak_ptr_factory_.GetWeakPtr()));
-      return;
-    }
-    OnLaunchFailure(KioskAppLaunchError::Error::kUnableToLaunch);
-  }
-}
-
 void StartupAppLauncher::OnKioskExtensionLoadedInCache(
     const std::string& app_id) {
   OnKioskAppDataLoadStatusChanged(app_id);
@@ -238,7 +160,8 @@
 
 void StartupAppLauncher::OnKioskAppDataLoadStatusChanged(
     const std::string& app_id) {
-  DCHECK(!installer_.install_complete());
+  if (ready_to_launch_)
+    return;
 
   if (app_id != app_id_ || !wait_for_crx_update_)
     return;
@@ -250,25 +173,6 @@
     OnLaunchFailure(KioskAppLaunchError::Error::kUnableToDownload);
 }
 
-bool StartupAppLauncher::AreSecondaryAppsInstalled() const {
-  const extensions::Extension* extension = GetPrimaryAppExtension();
-  DCHECK(extension);
-  extensions::KioskModeInfo* info = extensions::KioskModeInfo::Get(extension);
-  for (const auto& app : info->secondary_apps) {
-    if (!extensions::ExtensionRegistry::Get(profile_)->GetInstalledExtension(
-            app.id)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool StartupAppLauncher::PrimaryAppHasPendingUpdate() const {
-  return !!extensions::ExtensionSystem::Get(profile_)
-               ->extension_service()
-               ->GetPendingExtensionUpdate(app_id_);
-}
-
 const extensions::Extension* StartupAppLauncher::GetPrimaryAppExtension()
     const {
   return extensions::ExtensionRegistry::Get(profile_)->GetInstalledExtension(
@@ -276,7 +180,7 @@
 }
 
 void StartupAppLauncher::LaunchApp() {
-  if (!installer_.install_complete()) {
+  if (!ready_to_launch_) {
     NOTREACHED();
     SYSLOG(ERROR) << "LaunchApp() called but launcher is not initialized.";
   }
@@ -333,34 +237,48 @@
   delegate_->OnLaunchFailed(error);
 }
 
-void StartupAppLauncher::BeginInstall() {
-  installer_.BeginInstall(base::BindOnce(&StartupAppLauncher::OnInstallComplete,
-                                         weak_ptr_factory_.GetWeakPtr()));
+void StartupAppLauncher::FinalizeAppInstall() {
+  BeginInstall(/*finalize_only=*/true);
 }
 
-void StartupAppLauncher::OnInstallComplete(KioskAppLaunchError::Error error) {
-  if (error == KioskAppLaunchError::Error::kNone) {
-    MaybeLaunchApp();
-  } else {
-    OnLaunchFailure(error);
+void StartupAppLauncher::BeginInstall(bool finalize_only) {
+  installer_ = std::make_unique<ChromeAppKioskAppInstaller>(
+      profile_, app_id_, delegate_, finalize_only);
+  installer_->BeginInstall(base::BindOnce(
+      &StartupAppLauncher::OnInstallComplete, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void StartupAppLauncher::OnInstallComplete(
+    ChromeAppKioskAppInstaller::InstallResult result) {
+  switch (result) {
+    case ChromeAppKioskAppInstaller::InstallResult::kSuccess:
+      ready_to_launch_ = true;
+      // Updates to cached primary app crx will be ignored after this point, so
+      // there is no need to observe the kiosk app manager any longer.
+      kiosk_app_manager_observation_.Reset();
+      delegate_->OnAppPrepared();
+      return;
+    case ChromeAppKioskAppInstaller::InstallResult::kUnableToInstall:
+      OnLaunchFailure(KioskAppLaunchError::Error::kUnableToInstall);
+      return;
+    case ChromeAppKioskAppInstaller::InstallResult::kNotKioskEnabled:
+      OnLaunchFailure(KioskAppLaunchError::Error::kNotKioskEnabled);
+      return;
+    case ChromeAppKioskAppInstaller::InstallResult::kUnableToLaunch:
+      OnLaunchFailure(KioskAppLaunchError::Error::kUnableToLaunch);
+      return;
+    case ChromeAppKioskAppInstaller::InstallResult::kNetworkMissing:
+      ++launch_attempt_;
+      if (launch_attempt_ < kMaxLaunchAttempt) {
+        base::ThreadTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE,
+            base::BindOnce(&StartupAppLauncher::MaybeInitializeNetwork,
+                           weak_ptr_factory_.GetWeakPtr()));
+      } else {
+        OnLaunchFailure(KioskAppLaunchError::Error::kUnableToLaunch);
+      }
+      return;
   }
 }
 
-void StartupAppLauncher::OnReadyToLaunch() {
-  DCHECK(installer_.install_complete());
-  SYSLOG(INFO) << "Kiosk app is ready to launch.";
-  MaybeUpdateAppData();
-  delegate_->OnAppPrepared();
-}
-
-void StartupAppLauncher::MaybeUpdateAppData() {
-  // Skip copying meta data from the current installed primary app when
-  // there is a pending update.
-  if (PrimaryAppHasPendingUpdate())
-    return;
-
-  KioskAppManager::Get()->ClearAppData(app_id_);
-  KioskAppManager::Get()->UpdateAppDataFromProfile(app_id_, profile_, NULL);
-}
-
 }  // namespace ash
diff --git a/chrome/browser/ash/app_mode/startup_app_launcher.h b/chrome/browser/ash/app_mode/startup_app_launcher.h
index 9daeded..3241131 100644
--- a/chrome/browser/ash/app_mode/startup_app_launcher.h
+++ b/chrome/browser/ash/app_mode/startup_app_launcher.h
@@ -54,27 +54,16 @@
   void OnLaunchSuccess();
   void OnLaunchFailure(KioskAppLaunchError::Error error);
 
-  void BeginInstall();
-  void OnInstallComplete(KioskAppLaunchError::Error error);
-
-  void OnReadyToLaunch();
-  void MaybeUpdateAppData();
+  void FinalizeAppInstall();
+  void BeginInstall(bool finalize_only = false);
+  void OnInstallComplete(ChromeAppKioskAppInstaller::InstallResult result);
 
   void MaybeInitializeNetwork();
-  void SetSecondaryAppsEnabledState(const extensions::Extension* primary_app);
-  void MaybeLaunchApp();
-
   void OnKioskAppDataLoadStatusChanged(const std::string& app_id);
 
   // AppWindowRegistry::Observer:
   void OnAppWindowAdded(extensions::AppWindow* app_window) override;
 
-  // Returns true if all secondary apps have been installed.
-  bool AreSecondaryAppsInstalled() const;
-
-  // Returns true if the primary app has a pending update.
-  bool PrimaryAppHasPendingUpdate() const;
-
   const extensions::Extension* GetPrimaryAppExtension() const;
 
   // KioskAppManagerObserver overrides.
@@ -87,8 +76,9 @@
   int launch_attempt_ = 0;
   bool wait_for_crx_update_ = false;
   bool waiting_for_window_ = false;
+  bool ready_to_launch_ = false;
 
-  ChromeAppKioskAppInstaller installer_;
+  std::unique_ptr<ChromeAppKioskAppInstaller> installer_;
 
   extensions::AppWindowRegistry* window_registry_;
 
diff --git a/chrome/browser/ash/login/enrollment/enrollment_local_policy_server_browsertest.cc b/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
similarity index 86%
rename from chrome/browser/ash/login/enrollment/enrollment_local_policy_server_browsertest.cc
rename to chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
index 58b106b..61736df1 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_local_policy_server_browsertest.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
@@ -20,17 +20,18 @@
 #include "chrome/browser/ash/login/enrollment/enrollment_screen_view.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/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/enrollment_ui_mixin.h"
 #include "chrome/browser/ash/login/test/fake_gaia_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/kiosk_apps_mixin.h"
 #include "chrome/browser/ash/login/test/kiosk_test_helpers.h"
-#include "chrome/browser/ash/login/test/local_policy_test_server_mixin.h"
 #include "chrome/browser/ash/login/test/login_or_lock_screen_visible_waiter.h"
 #include "chrome/browser/ash/login/test/network_portal_detector_mixin.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
 #include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/ash/login/test/oobe_screens_utils.h"
+#include "chrome/browser/ash/login/test/policy_test_server_constants.h"
 #include "chrome/browser/ash/login/test/test_condition_waiter.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
@@ -51,12 +52,13 @@
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
 #include "chromeos/system/fake_statistics_provider.h"
 #include "chromeos/tpm/install_attributes.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
 #include "components/policy/core/common/policy_switches.h"
 #include "components/policy/proto/device_management_backend.pb.h"
-#include "components/policy/test_support/local_policy_test_server.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_utils.h"
+#include "net/http/http_status_code.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
@@ -91,17 +93,17 @@
               ""));
 }
 
-class EnrollmentLocalPolicyServerBase : public OobeBaseTest {
+class EnrollmentEmbeddedPolicyServerBase : public OobeBaseTest {
  public:
-  EnrollmentLocalPolicyServerBase() {
+  EnrollmentEmbeddedPolicyServerBase() {
     gaia_frame_parent_ = "authView";
     authenticator_id_ = "$('enterprise-enrollment').authenticator_";
   }
 
-  EnrollmentLocalPolicyServerBase(const EnrollmentLocalPolicyServerBase&) =
-      delete;
-  EnrollmentLocalPolicyServerBase& operator=(
-      const EnrollmentLocalPolicyServerBase&) = delete;
+  EnrollmentEmbeddedPolicyServerBase(
+      const EnrollmentEmbeddedPolicyServerBase&) = delete;
+  EnrollmentEmbeddedPolicyServerBase& operator=(
+      const EnrollmentEmbeddedPolicyServerBase&) = delete;
 
   void SetUpOnMainThread() override {
     fake_gaia_.SetupFakeGaiaForLogin(FakeGaiaMixin::kFakeUserEmail,
@@ -177,26 +179,27 @@
     policy_server_.UpdateDevicePolicy(proto);
   }
 
-  LocalPolicyTestServerMixin policy_server_{&mixin_host_};
+  EmbeddedPolicyTestServerMixin policy_server_{&mixin_host_};
   test::EnrollmentUIMixin enrollment_ui_{&mixin_host_};
   FakeGaiaMixin fake_gaia_{&mixin_host_};
   DeviceStateMixin device_state_{
       &mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_UNOWNED};
 };
 
-class AutoEnrollmentLocalPolicyServer : public EnrollmentLocalPolicyServerBase {
+class AutoEnrollmentEmbeddedPolicyServer
+    : public EnrollmentEmbeddedPolicyServerBase {
  public:
-  AutoEnrollmentLocalPolicyServer() {
+  AutoEnrollmentEmbeddedPolicyServer() {
     device_state_.SetState(DeviceStateMixin::State::BEFORE_OOBE);
   }
 
-  AutoEnrollmentLocalPolicyServer(const AutoEnrollmentLocalPolicyServer&) =
-      delete;
-  AutoEnrollmentLocalPolicyServer& operator=(
-      const AutoEnrollmentLocalPolicyServer&) = delete;
+  AutoEnrollmentEmbeddedPolicyServer(
+      const AutoEnrollmentEmbeddedPolicyServer&) = delete;
+  AutoEnrollmentEmbeddedPolicyServer& operator=(
+      const AutoEnrollmentEmbeddedPolicyServer&) = delete;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    EnrollmentLocalPolicyServerBase::SetUpCommandLine(command_line);
+    EnrollmentEmbeddedPolicyServerBase::SetUpCommandLine(command_line);
 
     command_line->AppendSwitchASCII(
         switches::kEnterpriseEnableForcedReEnrollment,
@@ -217,9 +220,9 @@
   NetworkPortalDetectorMixin network_portal_detector_{&mixin_host_};
 };
 
-class AutoEnrollmentWithStatistics : public AutoEnrollmentLocalPolicyServer {
+class AutoEnrollmentWithStatistics : public AutoEnrollmentEmbeddedPolicyServer {
  public:
-  AutoEnrollmentWithStatistics() : AutoEnrollmentLocalPolicyServer() {
+  AutoEnrollmentWithStatistics() : AutoEnrollmentEmbeddedPolicyServer() {
     // AutoEnrollmentController assumes that VPD is in valid state if
     // "serial_number" or "Product_S/N" could be read from it.
     fake_statistics_provider_.SetMachineStatistic(
@@ -272,7 +275,7 @@
   }
 };
 
-class InitialEnrollmentTest : public EnrollmentLocalPolicyServerBase {
+class InitialEnrollmentTest : public EnrollmentEmbeddedPolicyServerBase {
  public:
   InitialEnrollmentTest() {
     policy_server_.ConfigureFakeStatisticsForZeroTouch(
@@ -283,7 +286,7 @@
   InitialEnrollmentTest& operator=(const InitialEnrollmentTest&) = delete;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    EnrollmentLocalPolicyServerBase::SetUpCommandLine(command_line);
+    EnrollmentEmbeddedPolicyServerBase::SetUpCommandLine(command_line);
 
     // Enable usage of fake PSM (private set membership) RLWE client.
     command_line->AppendSwitch(
@@ -353,7 +356,7 @@
 // Requesting state keys hangs forever, but that should not matter because we're
 // running on reven.
 class EnrollmentOnRevenWithNoStateKeysResponse
-    : public EnrollmentLocalPolicyServerBase {
+    : public EnrollmentEmbeddedPolicyServerBase {
  public:
   EnrollmentOnRevenWithNoStateKeysResponse() = default;
 
@@ -364,9 +367,9 @@
 
   ~EnrollmentOnRevenWithNoStateKeysResponse() override = default;
 
-  // EnrollmentLocalPolicyServerBase:
+  // EnrollmentEmbeddedPolicyServerBase:
   void SetUpInProcessBrowserTestFixture() override {
-    EnrollmentLocalPolicyServerBase::SetUpInProcessBrowserTestFixture();
+    EnrollmentEmbeddedPolicyServerBase::SetUpInProcessBrowserTestFixture();
     // Session manager client is initialized by DeviceStateMixin.
     FakeSessionManagerClient::Get()->set_state_keys_handling(
         FakeSessionManagerClient::ServerBackedStateKeysHandling::kNoResponse);
@@ -384,7 +387,7 @@
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    EnrollmentLocalPolicyServerBase::SetUpCommandLine(command_line);
+    EnrollmentEmbeddedPolicyServerBase::SetUpCommandLine(command_line);
 
     command_line->AppendSwitch(switches::kRevenBranding);
   }
@@ -394,7 +397,7 @@
 };
 
 // Simple manual enrollment.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase, ManualEnrollment) {
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase, ManualEnrollment) {
   TriggerEnrollmentAndSignInSuccessfully();
 
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepSuccess);
@@ -403,9 +406,9 @@
   EXPECT_TRUE(InstallAttributes::Get()->IsCloudManaged());
 }
 
-// The test case is the same as EnrollmentLocalPolicyServerBase.ManualEnrollment
-// but the environment is different (simulate reven board, simulate state keys
-// not being available).
+// The test case is the same as
+// EnrollmentEmbeddedPolicyServerBase.ManualEnrollment but the environment is
+// different (simulate reven board, simulate state keys not being available).
 IN_PROC_BROWSER_TEST_F(EnrollmentOnRevenWithNoStateKeysResponse,
                        ManualEnrollment) {
   TriggerEnrollmentAndSignInSuccessfully();
@@ -418,7 +421,7 @@
 
 // Device policy blocks dev mode and this is not prohibited by a command-line
 // flag.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        DeviceBlockDevmodeAllowed) {
   enterprise_management::ChromeDeviceSettingsProto proto;
   proto.mutable_system_settings()->set_block_devmode(true);
@@ -434,7 +437,7 @@
 
 // Device policy blocks dev mode and a command-line flag prevents this from
 // applying.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        DeviceBlockDevmodeDisallowed) {
   base::CommandLine::ForCurrentProcess()->AppendSwitch(
       ash::switches::kDisallowPolicyBlockDevMode);
@@ -452,7 +455,7 @@
 }
 
 // Simple manual enrollment with device attributes prompt.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        ManualEnrollmentWithDeviceAttributes) {
   policy_server_.SetUpdateDeviceAttributesPermission(true);
 
@@ -470,9 +473,10 @@
 // device_management_service.cc
 
 // Error during enrollment : 402 - missing licenses.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorNoLicenses) {
-  policy_server_.SetExpectedDeviceEnrollmentError(402);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kMissingLicenses);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -484,11 +488,12 @@
   EXPECT_FALSE(InstallAttributes::Get()->IsEnterpriseManaged());
 }
 
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorNoLicensesMeets) {
   policy::EnrollmentRequisitionManager::SetDeviceRequisition(
       kRemoraRequisition);
-  policy_server_.SetExpectedDeviceEnrollmentError(402);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kMissingLicenses);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -502,9 +507,10 @@
 }
 
 // Error during enrollment : 403 - management not allowed.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorManagementNotAllowed) {
-  policy_server_.SetExpectedDeviceEnrollmentError(403);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kDeviceManagementNotAllowed);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -516,11 +522,12 @@
   EXPECT_FALSE(InstallAttributes::Get()->IsEnterpriseManaged());
 }
 
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorManagementNotAllowedMeets) {
   policy::EnrollmentRequisitionManager::SetDeviceRequisition(
       kRemoraRequisition);
-  policy_server_.SetExpectedDeviceEnrollmentError(403);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kDeviceManagementNotAllowed);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -533,9 +540,10 @@
 }
 
 // Error during enrollment : 405 - invalid device serial.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorInvalidDeviceSerial) {
-  policy_server_.SetExpectedDeviceEnrollmentError(405);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kInvalidSerialNumber);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -550,9 +558,10 @@
 }
 
 // Error during enrollment : 406 - domain mismatch
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorDomainMismatch) {
-  policy_server_.SetExpectedDeviceEnrollmentError(406);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kDomainMismatch);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -565,9 +574,10 @@
 }
 
 // Error during enrollment : 409 - Device ID is already in use
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorDeviceIDConflict) {
-  policy_server_.SetExpectedDeviceEnrollmentError(409);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kDeviceIdConflict);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -581,9 +591,10 @@
 }
 
 // Error during enrollment : 412 - Activation is pending
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorActivationIsPending) {
-  policy_server_.SetExpectedDeviceEnrollmentError(412);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kPendingApproval);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -596,9 +607,10 @@
 }
 
 // Error during enrollment : 417 - Consumer account with packaged license.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorConsumerAccountWithPackagedLicense) {
-  policy_server_.SetExpectedDeviceEnrollmentError(417);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kConsumerAccountWithPackagedLicense);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -612,9 +624,10 @@
 }
 
 // Error during enrollment : 500 - Consumer account with packaged license.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorServerError) {
-  policy_server_.SetExpectedDeviceEnrollmentError(500);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kInternalServerError);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -627,9 +640,10 @@
 }
 
 // Error during enrollment : 905 - Ineligible enterprise account.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorEnterpriseAccountIsNotEligibleToEnroll) {
-  policy_server_.SetExpectedDeviceEnrollmentError(905);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kInvalidDomainlessCustomer);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -642,9 +656,10 @@
   EXPECT_FALSE(InstallAttributes::Get()->IsEnterpriseManaged());
 }
 
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorEnterpriseTosHasNotBeenAccepeted) {
-  policy_server_.SetExpectedDeviceEnrollmentError(906);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kTosHasNotBeenAccepted);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -657,11 +672,12 @@
   EXPECT_FALSE(InstallAttributes::Get()->IsEnterpriseManaged());
 }
 
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
-                       EnrollmentErrorEnterpriseTosHasNotBeenAccepetedMeets) {
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
+                       EnrollmentErrorEnterpriseTosHasNotBeenAcceptedMeets) {
   policy::EnrollmentRequisitionManager::SetDeviceRequisition(
       kRemoraRequisition);
-  policy_server_.SetExpectedDeviceEnrollmentError(906);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kTosHasNotBeenAccepted);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -674,9 +690,10 @@
   EXPECT_FALSE(InstallAttributes::Get()->IsEnterpriseManaged());
 }
 
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorIllegalAccountForPackagedEDULicense) {
-  policy_server_.SetExpectedDeviceEnrollmentError(907);
+  policy_server_.SetDeviceEnrollmentError(
+      policy::DeviceManagementService::kIllegalAccountForPackagedEDULicense);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -690,9 +707,9 @@
 }
 
 // Error during enrollment : Strange HTTP response from server.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorServerIsDrunk) {
-  policy_server_.SetExpectedDeviceEnrollmentError(12345);
+  policy_server_.SetDeviceEnrollmentError(12345);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -705,10 +722,11 @@
 }
 
 // Error during enrollment : Can not update device attributes
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorUploadingDeviceAttributes) {
   policy_server_.SetUpdateDeviceAttributesPermission(true);
-  policy_server_.SetExpectedDeviceAttributeUpdateError(500);
+  policy_server_.SetDeviceAttributeUpdateError(
+      policy::DeviceManagementService::kInternalServerError);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -726,9 +744,10 @@
 }
 
 // Error during enrollment : Error fetching policy : 500 server error.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorFetchingPolicyTransient) {
-  policy_server_.SetExpectedPolicyFetchError(500);
+  policy_server_.SetPolicyFetchError(
+      policy::DeviceManagementService::kInternalServerError);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -741,9 +760,10 @@
 }
 
 // Error during enrollment : Error fetching policy : 902 - policy not found.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorFetchingPolicyNotFound) {
-  policy_server_.SetExpectedPolicyFetchError(902);
+  policy_server_.SetPolicyFetchError(
+      policy::DeviceManagementService::kPolicyNotFound);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -757,9 +777,10 @@
 }
 
 // Error during enrollment : Error fetching policy : 903 - deprovisioned.
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        EnrollmentErrorFetchingPolicyDeprovisioned) {
-  policy_server_.SetExpectedPolicyFetchError(903);
+  policy_server_.SetPolicyFetchError(
+      policy::DeviceManagementService::kDeprovisioned);
 
   TriggerEnrollmentAndSignInSuccessfully();
 
@@ -772,13 +793,14 @@
 }
 
 // No state keys on the server. Auto enrollment check should proceed to login.
-IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, AutoEnrollmentCheck) {
+IN_PROC_BROWSER_TEST_F(AutoEnrollmentEmbeddedPolicyServer,
+                       AutoEnrollmentCheck) {
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
   OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // State keys are present but restore mode is not requested.
-IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, ReenrollmentNone) {
+IN_PROC_BROWSER_TEST_F(AutoEnrollmentEmbeddedPolicyServer, ReenrollmentNone) {
   EXPECT_TRUE(policy_server_.SetDeviceStateRetrievalResponse(
       state_keys_broker(),
       enterprise_management::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
@@ -788,7 +810,8 @@
 }
 
 // Reenrollment requested. User can skip.
-IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, ReenrollmentRequested) {
+IN_PROC_BROWSER_TEST_F(AutoEnrollmentEmbeddedPolicyServer,
+                       ReenrollmentRequested) {
   EXPECT_TRUE(policy_server_.SetDeviceStateRetrievalResponse(
       state_keys_broker(),
       enterprise_management::DeviceStateRetrievalResponse::
@@ -801,7 +824,7 @@
 }
 
 // Reenrollment forced. User can not skip.
-IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, ReenrollmentForced) {
+IN_PROC_BROWSER_TEST_F(AutoEnrollmentEmbeddedPolicyServer, ReenrollmentForced) {
   EXPECT_TRUE(policy_server_.SetDeviceStateRetrievalResponse(
       state_keys_broker(),
       enterprise_management::DeviceStateRetrievalResponse::
@@ -815,7 +838,7 @@
 }
 
 // Device is disabled.
-IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, DeviceDisabled) {
+IN_PROC_BROWSER_TEST_F(AutoEnrollmentEmbeddedPolicyServer, DeviceDisabled) {
   EXPECT_TRUE(policy_server_.SetDeviceStateRetrievalResponse(
       state_keys_broker(),
       enterprise_management::DeviceStateRetrievalResponse::
@@ -826,7 +849,7 @@
 }
 
 // Attestation enrollment.
-IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, Attestation) {
+IN_PROC_BROWSER_TEST_F(AutoEnrollmentEmbeddedPolicyServer, Attestation) {
   // Even though the server would allow device attributes update, Chrome OS will
   // not attempt that for attestation enrollment.
   policy_server_.SetUpdateDeviceAttributesPermission(true);
@@ -846,7 +869,7 @@
 }
 
 // Verify able to advance to login screen when error screen is shown.
-IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, TestCaptivePortal) {
+IN_PROC_BROWSER_TEST_F(AutoEnrollmentEmbeddedPolicyServer, TestCaptivePortal) {
   network_portal_detector_.SimulateDefaultNetworkState(
       NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL);
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
@@ -933,9 +956,9 @@
   OobeScreenWaiter(EnrollmentScreenView::kScreenId).Wait();
 }
 
-class EnrollmentRecoveryTest : public EnrollmentLocalPolicyServerBase {
+class EnrollmentRecoveryTest : public EnrollmentEmbeddedPolicyServerBase {
  public:
-  EnrollmentRecoveryTest() : EnrollmentLocalPolicyServerBase() {
+  EnrollmentRecoveryTest() : EnrollmentEmbeddedPolicyServerBase() {
     device_state_.SetState(
         DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED);
   }
@@ -947,7 +970,7 @@
 
  protected:
   void SetUpInProcessBrowserTestFixture() override {
-    EnrollmentLocalPolicyServerBase::SetUpInProcessBrowserTestFixture();
+    EnrollmentEmbeddedPolicyServerBase::SetUpInProcessBrowserTestFixture();
 
     // This triggers recovery enrollment.
     device_state_.RequestDevicePolicyUpdate()->policy_data()->Clear();
@@ -998,7 +1021,7 @@
           INITIAL_ENROLLMENT_MODE_ENROLLMENT_ENFORCED;
   policy_server_.SetDeviceInitialEnrollmentResponse(
       test::kTestRlzBrandCodeKey, test::kTestSerialNumber, initial_enrollment,
-      test::kTestDomain, /*is_license_packaged_with_device=*/absl::nullopt);
+      test::kTestDomain);
 
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
   OobeScreenWaiter(EnrollmentScreenView::kScreenId).Wait();
@@ -1038,7 +1061,7 @@
           INITIAL_ENROLLMENT_MODE_ZERO_TOUCH_ENFORCED;
   policy_server_.SetDeviceInitialEnrollmentResponse(
       test::kTestRlzBrandCodeKey, test::kTestSerialNumber, initial_enrollment,
-      test::kTestDomain, /*is_license_packaged_with_device=*/absl::nullopt);
+      test::kTestDomain);
 
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
   OobeScreenWaiter(EnrollmentScreenView::kScreenId).Wait();
@@ -1079,7 +1102,7 @@
 }
 
 class OobeGuestButtonPolicy : public testing::WithParamInterface<bool>,
-                              public EnrollmentLocalPolicyServerBase {
+                              public EnrollmentEmbeddedPolicyServerBase {
  public:
   OobeGuestButtonPolicy() = default;
 
@@ -1090,7 +1113,7 @@
     enterprise_management::ChromeDeviceSettingsProto proto;
     proto.mutable_guest_mode_enabled()->set_guest_mode_enabled(GetParam());
     policy_server_.UpdateDevicePolicy(proto);
-    EnrollmentLocalPolicyServerBase::SetUpOnMainThread();
+    EnrollmentEmbeddedPolicyServerBase::SetUpOnMainThread();
   }
 };
 
@@ -1113,14 +1136,14 @@
 
 INSTANTIATE_TEST_SUITE_P(All, OobeGuestButtonPolicy, ::testing::Bool());
 
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase, SwitchToViews) {
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase, SwitchToViews) {
   TriggerEnrollmentAndSignInSuccessfully();
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepSuccess);
   ConfirmAndWaitLoginScreen();
   EXPECT_TRUE(LoginScreenTestApi::IsOobeDialogVisible());
 }
 
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase,
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
                        SwitchToViewsLocalUsers) {
   AddPublicUser("test_user");
   TriggerEnrollmentAndSignInSuccessfully();
@@ -1130,7 +1153,8 @@
   EXPECT_EQ(LoginScreenTestApi::GetUsersCount(), 1);
 }
 
-IN_PROC_BROWSER_TEST_F(EnrollmentLocalPolicyServerBase, SwitchToViewsLocales) {
+IN_PROC_BROWSER_TEST_F(EnrollmentEmbeddedPolicyServerBase,
+                       SwitchToViewsLocales) {
   auto initial_label = LoginScreenTestApi::GetShutDownButtonLabel();
 
   SetLoginScreenLocale("ru-RU");
@@ -1141,16 +1165,16 @@
   EXPECT_NE(LoginScreenTestApi::GetShutDownButtonLabel(), initial_label);
 }
 
-class KioskEnrollmentTest : public EnrollmentLocalPolicyServerBase {
+class KioskEnrollmentTest : public EnrollmentEmbeddedPolicyServerBase {
  public:
   KioskEnrollmentTest() = default;
 
-  // EnrollmentLocalPolicyServerBase:
+  // EnrollmentEmbeddedPolicyServerBase:
   void SetUp() override {
     needs_background_networking_ = true;
     skip_splash_wait_override_ =
         KioskLaunchController::SkipSplashScreenWaitForTesting();
-    EnrollmentLocalPolicyServerBase::SetUp();
+    EnrollmentEmbeddedPolicyServerBase::SetUp();
   }
 
   void SetupAutoLaunchApp(FakeOwnerSettingsService* service) {
diff --git a/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.cc b/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.cc
index 9adc63d..d8a58b8 100644
--- a/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.cc
+++ b/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.cc
@@ -7,10 +7,12 @@
 #include <string>
 #include <utility>
 
+#include "ash/components/attestation/fake_attestation_flow.h"
 #include "base/guid.h"
 #include "base/json/values_util.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/test/fake_gaia_mixin.h"
+#include "chrome/browser/ash/login/test/policy_test_server_constants.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h"
 #include "chrome/browser/browser_process.h"
@@ -23,10 +25,20 @@
 #include "components/policy/test_support/embedded_policy_test_server.h"
 #include "components/policy/test_support/policy_storage.h"
 #include "components/policy/test_support/signature_provider.h"
+#include "net/http/http_status_code.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 
+namespace {
+
+std::string GetBrandSerialId(const std::string& device_brand_code,
+                             const std::string& device_serial_number) {
+  return device_brand_code + "_" + device_serial_number;
+}
+
+}  // namespace
+
 EmbeddedPolicyTestServerMixin::EmbeddedPolicyTestServerMixin(
     InProcessBrowserTestMixinHost* host)
     : InProcessBrowserTestMixin(host) {}
@@ -72,6 +84,12 @@
                                   policy_test_server_->GetServiceURL().spec());
 }
 
+void EmbeddedPolicyTestServerMixin::UpdateDevicePolicy(
+    const enterprise_management::ChromeDeviceSettingsProto& policy) {
+  policy_test_server_->policy_storage()->SetPolicyPayload(
+      policy::dm_protocol::kChromeDevicePolicyType, policy.SerializeAsString());
+}
+
 void EmbeddedPolicyTestServerMixin::UpdateUserPolicy(
     const enterprise_management::CloudPolicySettings& policy,
     const std::string& policy_user) {
@@ -80,4 +98,112 @@
       policy::dm_protocol::kChromeUserPolicyType, policy.SerializeAsString());
 }
 
+void EmbeddedPolicyTestServerMixin::SetUpdateDeviceAttributesPermission(
+    bool allowed) {
+  policy_test_server_->policy_storage()->set_allow_set_device_attributes(
+      allowed);
+}
+
+void EmbeddedPolicyTestServerMixin::SetDeviceEnrollmentError(
+    int net_error_code) {
+  policy_test_server_->ConfigureRequestError(
+      policy::dm_protocol::kValueRequestRegister,
+      static_cast<net::HttpStatusCode>(net_error_code));
+}
+
+void EmbeddedPolicyTestServerMixin::SetDeviceAttributeUpdateError(
+    int net_error_code) {
+  policy_test_server_->ConfigureRequestError(
+      policy::dm_protocol::kValueRequestDeviceAttributeUpdate,
+      static_cast<net::HttpStatusCode>(net_error_code));
+}
+
+void EmbeddedPolicyTestServerMixin::SetPolicyFetchError(int net_error_code) {
+  policy_test_server_->ConfigureRequestError(
+      policy::dm_protocol::kValueRequestPolicy,
+      static_cast<net::HttpStatusCode>(net_error_code));
+}
+
+void EmbeddedPolicyTestServerMixin::SetFakeAttestationFlow() {
+  g_browser_process->platform_part()
+      ->browser_policy_connector_ash()
+      ->SetAttestationFlowForTesting(
+          std::make_unique<attestation::FakeAttestationFlow>());
+}
+
+void EmbeddedPolicyTestServerMixin::SetExpectedPsmParamsInDeviceRegisterRequest(
+    const std::string& device_brand_code,
+    const std::string& device_serial_number,
+    int psm_execution_result,
+    int64_t psm_determination_timestamp) {
+  policy::PolicyStorage::PsmEntry psm_entry;
+  psm_entry.psm_execution_result = psm_execution_result;
+  psm_entry.psm_determination_timestamp = psm_determination_timestamp;
+  policy_test_server_->policy_storage()->SetPsmEntry(
+      GetBrandSerialId(device_brand_code, device_serial_number), psm_entry);
+}
+
+bool EmbeddedPolicyTestServerMixin::SetDeviceStateRetrievalResponse(
+    policy::ServerBackedStateKeysBroker* keys_broker,
+    enterprise_management::DeviceStateRetrievalResponse::RestoreMode
+        restore_mode,
+    const std::string& managemement_domain) {
+  std::vector<std::string> keys;
+  base::RunLoop loop;
+  keys_broker->RequestStateKeys(base::BindOnce(
+      [](std::vector<std::string>* keys, base::OnceClosure quit,
+         const std::vector<std::string>& state_keys) {
+        *keys = state_keys;
+        std::move(quit).Run();
+      },
+      &keys, loop.QuitClosure()));
+  loop.Run();
+  if (keys.empty())
+    return false;
+
+  policy::ClientStorage::ClientInfo client_info;
+  client_info.device_token = "dm_token";
+  client_info.device_id = base::GenerateGUID();
+  client_info.state_keys = keys;
+  policy_test_server_->client_storage()->RegisterClient(client_info);
+  policy_test_server_->policy_storage()->set_device_state(
+      policy::PolicyStorage::DeviceState{managemement_domain, restore_mode});
+  return true;
+}
+
+void EmbeddedPolicyTestServerMixin::SetDeviceInitialEnrollmentResponse(
+    const std::string& device_brand_code,
+    const std::string& device_serial_number,
+    enterprise_management::DeviceInitialEnrollmentStateResponse::
+        InitialEnrollmentMode initial_mode,
+    const std::string& management_domain) {
+  policy::PolicyStorage::InitialEnrollmentState initial_enrollment_state;
+  initial_enrollment_state.initial_enrollment_mode = initial_mode;
+  initial_enrollment_state.management_domain = management_domain;
+  policy_test_server_->policy_storage()->SetInitialEnrollmentState(
+      GetBrandSerialId(device_brand_code, device_serial_number),
+      initial_enrollment_state);
+}
+
+void EmbeddedPolicyTestServerMixin::SetupZeroTouchForcedEnrollment() {
+  SetFakeAttestationFlow();
+  auto initial_enrollment =
+      enterprise_management::DeviceInitialEnrollmentStateResponse::
+          INITIAL_ENROLLMENT_MODE_ZERO_TOUCH_ENFORCED;
+  SetUpdateDeviceAttributesPermission(false);
+  SetDeviceInitialEnrollmentResponse(test::kTestRlzBrandCodeKey,
+                                     test::kTestSerialNumber,
+                                     initial_enrollment, test::kTestDomain);
+}
+
+void EmbeddedPolicyTestServerMixin::ConfigureFakeStatisticsForZeroTouch(
+    system::ScopedFakeStatisticsProvider* provider) {
+  provider->SetMachineStatistic(system::kRlzBrandCodeKey,
+                                test::kTestRlzBrandCodeKey);
+  provider->SetMachineStatistic(system::kSerialNumberKeyForTest,
+                                test::kTestSerialNumber);
+  provider->SetMachineStatistic(system::kHardwareClassKey,
+                                test::kTestHardwareClass);
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h b/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h
index 3a217f5..0c4bcff 100644
--- a/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h
+++ b/chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h
@@ -9,9 +9,17 @@
 #include <string>
 
 #include "base/command_line.h"
+#include "chrome/browser/ash/policy/server_backed_state/server_backed_state_keys_broker.h"
 #include "chrome/test/base/mixin_based_in_process_browser_test.h"
+#include "chromeos/system/fake_statistics_provider.h"
+#include "components/policy/proto/chrome_device_policy.pb.h"
 #include "components/policy/proto/cloud_policy.pb.h"
-#include "components/policy/test_support/embedded_policy_test_server.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+
+namespace policy {
+class EmbeddedPolicyTestServer;
+}
 
 namespace ash {
 
@@ -36,15 +44,71 @@
   void SetUp() override;
   void SetUpCommandLine(base::CommandLine* command_line) override;
 
+  // Updates the device policy blob served by the local policy test server.
+  void UpdateDevicePolicy(
+      const enterprise_management::ChromeDeviceSettingsProto& policy);
+
   // Updates user policy blob served by the embedded policy test server.
   // `policy_user` - the policy user's email.
   void UpdateUserPolicy(
       const enterprise_management::CloudPolicySettings& policy,
       const std::string& policy_user);
 
+  // Configures whether the server should indicate that the client is
+  // allowed to update device attributes in response to
+  // DeviceAttributeUpdatePermissionRequest.
+  void SetUpdateDeviceAttributesPermission(bool allowed);
+
+  // Configures server to respond with particular error code during requests.
+  // `net_error_code` - error code from device_management_service.cc.
+  void SetDeviceEnrollmentError(int net_error_code);
+  void SetDeviceAttributeUpdateError(int net_error_code);
+  void SetPolicyFetchError(int net_error_code);
+
+  // Configures fake attestation flow so that we can test attestation-based
+  // enrollment flows.
+  void SetFakeAttestationFlow();
+
+  // Configures server to expect these PSM (private set membership) execution
+  // values (i.e. `psm_execution_result` and `psm_determination_timestamp`) as
+  // part of DeviceRegisterRequest. Note: `device_brand_code` and
+  // `device_serial_number` values will be used on the server as a key to
+  // retrieve the PSM execution values.
+  void SetExpectedPsmParamsInDeviceRegisterRequest(
+      const std::string& device_brand_code,
+      const std::string& device_serial_number,
+      int psm_execution_result,
+      int64_t psm_determination_timestamp);
+
+  // Set response for DeviceStateRetrievalRequest. Returns that if finds state
+  // key passed in the request. State keys could be set by RegisterClient call
+  // on policy test server.
+  bool SetDeviceStateRetrievalResponse(
+      policy::ServerBackedStateKeysBroker* keys_broker,
+      enterprise_management::DeviceStateRetrievalResponse::RestoreMode
+          restore_mode,
+      const std::string& managemement_domain);
+
+  // Set response for DeviceInitialEnrollmentStateRequest.
+  void SetDeviceInitialEnrollmentResponse(
+      const std::string& device_brand_code,
+      const std::string& device_serial_number,
+      enterprise_management::DeviceInitialEnrollmentStateResponse::
+          InitialEnrollmentMode initial_mode,
+      const std::string& management_domain);
+
+  // Utility function that configures server parameters for zero-touch
+  // enrollment. Should be used in conjunction with enabling zero-touch
+  // via command line and calling `ConfigureFakeStatisticsForZeroTouch`.
+  void SetupZeroTouchForcedEnrollment();
+
+  // Configures fake statistics provider with values that can be used with
+  // zero-touch enrollment.
+  void ConfigureFakeStatisticsForZeroTouch(
+      system::ScopedFakeStatisticsProvider* provider);
+
  private:
   std::unique_ptr<policy::EmbeddedPolicyTestServer> policy_test_server_;
-  base::Value server_config_;
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/login/test/local_policy_test_server_mixin.cc b/chrome/browser/ash/login/test/local_policy_test_server_mixin.cc
index ffb4617..b9a4e4a0 100644
--- a/chrome/browser/ash/login/test/local_policy_test_server_mixin.cc
+++ b/chrome/browser/ash/login/test/local_policy_test_server_mixin.cc
@@ -12,6 +12,7 @@
 #include "base/json/values_util.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/test/fake_gaia_mixin.h"
+#include "chrome/browser/ash/login/test/policy_test_server_constants.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h"
 #include "chrome/browser/browser_process.h"
@@ -216,8 +217,7 @@
     const std::string& device_serial_number,
     enterprise_management::DeviceInitialEnrollmentStateResponse::
         InitialEnrollmentMode initial_mode,
-    const absl::optional<std::string>& management_domain,
-    const absl::optional<bool> is_license_packaged_with_device) {
+    const absl::optional<std::string>& management_domain) {
   base::Value serial_entry(base::Value::Type::DICTIONARY);
   serial_entry.SetKey("initial_enrollment_mode", base::Value(initial_mode));
 
@@ -225,10 +225,6 @@
     serial_entry.SetKey("management_domain",
                         base::Value(management_domain.value()));
 
-  if (is_license_packaged_with_device.has_value())
-    serial_entry.SetKey("is_license_packaged_with_device",
-                        base::Value(is_license_packaged_with_device.value()));
-
   const std::string brand_serial_id =
       device_brand_code + "_" + device_serial_number;
   server_config_.SetPath("initial_enrollment_state." + brand_serial_id,
@@ -243,9 +239,9 @@
       enterprise_management::DeviceInitialEnrollmentStateResponse::
           INITIAL_ENROLLMENT_MODE_ZERO_TOUCH_ENFORCED;
   SetUpdateDeviceAttributesPermission(false);
-  SetDeviceInitialEnrollmentResponse(
-      test::kTestRlzBrandCodeKey, test::kTestSerialNumber, initial_enrollment,
-      test::kTestDomain, absl::nullopt /* is_license_packaged_with_device */);
+  SetDeviceInitialEnrollmentResponse(test::kTestRlzBrandCodeKey,
+                                     test::kTestSerialNumber,
+                                     initial_enrollment, test::kTestDomain);
 }
 
 void LocalPolicyTestServerMixin::ConfigureFakeStatisticsForZeroTouch(
diff --git a/chrome/browser/ash/login/test/local_policy_test_server_mixin.h b/chrome/browser/ash/login/test/local_policy_test_server_mixin.h
index feec964..fb87094 100644
--- a/chrome/browser/ash/login/test/local_policy_test_server_mixin.h
+++ b/chrome/browser/ash/login/test/local_policy_test_server_mixin.h
@@ -20,15 +20,6 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
-namespace test {
-
-// Test constants used during enrollment wherever appropriate.
-constexpr char kTestDomain[] = "test-domain.com";
-constexpr char kTestRlzBrandCodeKey[] = "TEST";
-constexpr char kTestSerialNumber[] = "111111";
-constexpr char kTestHardwareClass[] = "hw";
-
-}  // namespace test
 
 // This test mixin covers setting up LocalPolicyTestServer and adding a
 // command-line flag to use it. Please see SetUp function for default settings.
@@ -108,8 +99,7 @@
       const std::string& device_serial_number,
       enterprise_management::DeviceInitialEnrollmentStateResponse::
           InitialEnrollmentMode initial_mode,
-      const absl::optional<std::string>& management_domain,
-      const absl::optional<bool> is_license_packaged_with_device);
+      const absl::optional<std::string>& management_domain);
 
   // Utility function that configures server parameters for zero-touch
   // enrollment. Should be used in conjunction with enabling zero-touch
diff --git a/chrome/browser/ash/login/test/policy_test_server_constants.h b/chrome/browser/ash/login/test/policy_test_server_constants.h
new file mode 100644
index 0000000..2bfc68d5
--- /dev/null
+++ b/chrome/browser/ash/login/test/policy_test_server_constants.h
@@ -0,0 +1,20 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_LOGIN_TEST_POLICY_TEST_SERVER_CONSTANTS_H_
+#define CHROME_BROWSER_ASH_LOGIN_TEST_POLICY_TEST_SERVER_CONSTANTS_H_
+
+namespace ash {
+namespace test {
+
+// Test constants used during enrollment wherever appropriate.
+constexpr char kTestDomain[] = "test-domain.com";
+constexpr char kTestRlzBrandCodeKey[] = "TEST";
+constexpr char kTestSerialNumber[] = "111111";
+constexpr char kTestHardwareClass[] = "hw";
+
+}  // namespace test
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_TEST_POLICY_TEST_SERVER_CONSTANTS_H_
diff --git a/chrome/browser/browsing_data/access_context_audit_database_unittest.cc b/chrome/browser/browsing_data/access_context_audit_database_unittest.cc
index c0f42ef..47512ba 100644
--- a/chrome/browser/browsing_data/access_context_audit_database_unittest.cc
+++ b/chrome/browser/browsing_data/access_context_audit_database_unittest.cc
@@ -584,8 +584,7 @@
   ContentSettingsForOneType content_settings;
   content_settings.emplace_back(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(content_settings::ContentSettingToValue(
-          CONTENT_SETTING_SESSION_ONLY)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_SESSION_ONLY),
       std::string(), /* incognito */ false);
 
   database()->RemoveSessionOnlyRecords(content_settings);
@@ -600,19 +599,16 @@
   content_settings.emplace_back(
       ContentSettingsPattern::FromString(kManyContextsCookieDomain),
       ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(content_settings::ContentSettingToValue(
-          CONTENT_SETTING_SESSION_ONLY)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_SESSION_ONLY),
       std::string(), /* incognito */ false);
   content_settings.emplace_back(
       ContentSettingsPattern::FromString(kManyContextsStorageAPIOrigin),
       ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(content_settings::ContentSettingToValue(
-          CONTENT_SETTING_SESSION_ONLY)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_SESSION_ONLY),
       std::string(), /* incognito */ false);
   content_settings.emplace_back(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), /* incognito */ false);
   database()->RemoveSessionOnlyRecords(content_settings);
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 11450a8..e90e16c 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -3772,6 +3772,7 @@
     "../ash/login/test/oobe_screen_waiter.h",
     "../ash/login/test/oobe_screens_utils.cc",
     "../ash/login/test/oobe_screens_utils.h",
+    "../ash/login/test/policy_test_server_constants.h",
     "../ash/login/test/profile_prepared_waiter.cc",
     "../ash/login/test/profile_prepared_waiter.h",
     "../ash/login/test/scoped_help_app_for_test.cc",
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc b/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc
index 62a6fcc..f07b06d8 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc
@@ -27,16 +27,8 @@
 
 std::string GetServiceWorkerForError(const std::string& error) {
   std::string service_worker = R"(
-    chrome.test.runTests([
+    const tests = [
       // Telemetry APIs.
-      async function getVpdInfo() {
-        await chrome.test.assertPromiseRejects(
-            chrome.os.telemetry.getVpdInfo(),
-            'Error: Unauthorized access to chrome.os.telemetry.getVpdInfo. ' +
-            '%s'
-        );
-        chrome.test.succeed();
-      },
       async function getOemData() {
         await chrome.test.assertPromiseRejects(
             chrome.os.telemetry.getOemData(),
@@ -45,6 +37,14 @@
         );
         chrome.test.succeed();
       },
+      async function getVpdInfo() {
+        await chrome.test.assertPromiseRejects(
+            chrome.os.telemetry.getVpdInfo(),
+            'Error: Unauthorized access to chrome.os.telemetry.getVpdInfo. ' +
+            '%s'
+        );
+        chrome.test.succeed();
+      },
 
       // Diagnostics APIs.
       async function getAvailableRoutines() {
@@ -177,6 +177,25 @@
         );
         chrome.test.succeed();
       },
+    ];
+
+    chrome.test.runTests([
+      async function allAPIsTested() {
+        getTestNames = function(arr) {
+          return arr.map(item => item.name);
+        }
+        getMethods = function(obj) {
+          return Object.getOwnPropertyNames(obj).filter(
+            item => typeof obj[item] === 'function');
+        }
+        apiNames = [
+          ...getMethods(chrome.os.telemetry),
+          ...getMethods(chrome.os.diagnostics)
+        ];
+        chrome.test.assertEq(getTestNames(tests), apiNames);
+        chrome.test.succeed();
+      },
+      ...tests
     ]);
   )";
 
diff --git a/chrome/browser/content_settings/one_time_geolocation_permission_provider.cc b/chrome/browser/content_settings/one_time_geolocation_permission_provider.cc
index 9811649..13359368 100644
--- a/chrome/browser/content_settings/one_time_geolocation_permission_provider.cc
+++ b/chrome/browser/content_settings/one_time_geolocation_permission_provider.cc
@@ -29,8 +29,7 @@
   content_settings::Rule Next() override {
     content_settings::Rule rule(
         begin_iterator_->first, ContentSettingsPattern::Wildcard(),
-        base::Value::FromUniquePtrValue(
-            content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+        content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
         begin_iterator_->second + base::Days(1),
         content_settings::SessionModel::OneTime);
     begin_iterator_++;
diff --git a/chrome/browser/devtools/chrome_devtools_session.cc b/chrome/browser/devtools/chrome_devtools_session.cc
index f0b481d..f75c455 100644
--- a/chrome/browser/devtools/chrome_devtools_session.cc
+++ b/chrome/browser/devtools/chrome_devtools_session.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/devtools/chrome_devtools_session.h"
 
 #include <memory>
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/metrics_hashes.h"
 #include "base/strings/string_number_conversions.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/devtools/protocol/browser_handler.h"
@@ -50,6 +52,12 @@
 
 ChromeDevToolsSession::~ChromeDevToolsSession() = default;
 
+base::HistogramBase::Sample GetCommandUmaId(
+    const base::StringPiece command_name) {
+  return static_cast<base::HistogramBase::Sample>(
+      base::HashMetricName(command_name));
+}
+
 void ChromeDevToolsSession::HandleCommand(
     base::span<const uint8_t> message,
     content::DevToolsManagerDelegate::NotHandledCallback callback) {
@@ -57,6 +65,16 @@
   DCHECK(dispatchable.ok());  // Checked by content::DevToolsSession.
   crdtp::UberDispatcher::DispatchResult dispatched =
       dispatcher_.Dispatch(dispatchable);
+
+  auto command_uma_id = GetCommandUmaId(base::StringPiece(
+      reinterpret_cast<const char*>(dispatchable.Method().begin()),
+      dispatchable.Method().size()));
+  std::string client_type = client_channel_->GetClient()->GetTypeForMetrics();
+  DCHECK(client_type == "DevTools" || client_type == "Extension" ||
+         client_type == "RemoteDebugger" || client_type == "Other");
+  base::UmaHistogramSparse("DevTools.CDPCommandFrom" + client_type,
+                           command_uma_id);
+
   if (!dispatched.MethodFound()) {
     std::move(callback).Run(message);
     return;
diff --git a/chrome/browser/extensions/lazy_background_page_apitest.cc b/chrome/browser/extensions/lazy_background_page_apitest.cc
index f2d26d7..dc42d059 100644
--- a/chrome/browser/extensions/lazy_background_page_apitest.cc
+++ b/chrome/browser/extensions/lazy_background_page_apitest.cc
@@ -787,8 +787,9 @@
 
 // Tests that an extension can fetch a file scheme URL from the lazy background
 // page, if it has file access.
+// TODO(crbug.com/1283851): Deflake test.
 IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest,
-                       FetchFileSchemeURLWithFileAccess) {
+                       DISABLED_FetchFileSchemeURLWithFileAccess) {
   ASSERT_TRUE(RunExtensionTest(
       "lazy_background_page/fetch_file_scheme_url_with_file_access", {},
       {.allow_file_access = true}))
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
index f818ad02..caf0b3b 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
@@ -17,7 +17,11 @@
 
 namespace lookalikes {
 
-class LookalikeThrottleTest : public ChromeRenderViewHostTestHarness {};
+class LookalikeThrottleTest : public ChromeRenderViewHostTestHarness {
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_{
+      lookalikes::features::kLookalikeInterstitialForPunycode};
+};
 
 // Tests that spoofy hostnames are properly handled in the throttle.
 TEST_F(LookalikeThrottleTest, SpoofsBlocked) {
@@ -66,10 +70,6 @@
        url_formatter::IDNSpoofChecker::Result::kDeviationCharacters},
   };
 
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(
-      lookalikes::features::kLookalikeInterstitialForPunycode);
-
   for (const TestCase& test_case : kTestCases) {
     url_formatter::IDNConversionResult idn_result =
         url_formatter::UnsafeIDNToUnicodeWithDetails(test_case.hostname);
diff --git a/chrome/browser/permissions/prediction_based_permission_ui_selector.cc b/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
index 21756ad4..26ad1456 100644
--- a/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
+++ b/chrome/browser/permissions/prediction_based_permission_ui_selector.cc
@@ -11,6 +11,7 @@
 #include "base/rand_util.h"
 #include "base/time/default_clock.h"
 #include "chrome/browser/permissions/permission_actions_history_factory.h"
+#include "chrome/browser/permissions/prediction_model_handler_factory.h"
 #include "chrome/browser/permissions/prediction_service_factory.h"
 #include "chrome/browser/permissions/prediction_service_request.h"
 #include "chrome/browser/profiles/profile.h"
@@ -19,7 +20,10 @@
 #include "chrome/common/pref_names.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_actions_history.h"
+#include "components/permissions/permission_uma_util.h"
 #include "components/permissions/permission_util.h"
+#include "components/permissions/prediction_service/prediction_common.h"
+#include "components/permissions/prediction_service/prediction_model_handler.h"
 #include "components/permissions/prediction_service/prediction_service.h"
 #include "components/permissions/prediction_service/prediction_service_messages.pb.h"
 #include "components/prefs/pref_service.h"
@@ -82,6 +86,10 @@
     if (mock_likelihood.has_value())
       set_likelihood_override(mock_likelihood.value());
   }
+  if (base::FeatureList::IsEnabled(
+          permissions::features::kPermissionOnDevicePredictions)) {
+    PredictionModelHandlerFactory::GetForBrowserContext(profile);
+  }
 }
 
 PredictionBasedPermissionUiSelector::~PredictionBasedPermissionUiSelector() =
@@ -127,6 +135,24 @@
   }
 
   DCHECK(!request_);
+
+  if (base::FeatureList::IsEnabled(
+          permissions::features::kPermissionOnDevicePredictions)) {
+    permissions::PredictionModelHandler* prediction_model_handler =
+        PredictionModelHandlerFactory::GetForBrowserContext(profile_);
+    if (prediction_model_handler->ModelAvailable()) {
+      VLOG(1) << "[CPSS] Using locally available model";
+      auto proto_request = GetPredictionRequestProto(features);
+      prediction_model_handler->ExecuteModelWithInput(
+          base::BindOnce(
+              &PredictionBasedPermissionUiSelector::LookupResponseReceived,
+              weak_ptr_factory_.GetWeakPtr(), /*is_on_device=*/true,
+              /*lookup_succesful=*/true, /*response_from_cache=*/false),
+          *proto_request);
+      return;
+    }
+  }
+
   permissions::PredictionService* service =
       PredictionServiceFactory::GetForProfile(profile_);
 
@@ -134,8 +160,8 @@
   request_ = std::make_unique<PredictionServiceRequest>(
       service, features,
       base::BindOnce(
-          &PredictionBasedPermissionUiSelector::LookupReponseReceived,
-          base::Unretained(this)));
+          &PredictionBasedPermissionUiSelector::LookupResponseReceived,
+          base::Unretained(this), /*is_on_device=*/false));
 }
 
 void PredictionBasedPermissionUiSelector::Cancel() {
@@ -181,10 +207,11 @@
   return features;
 }
 
-void PredictionBasedPermissionUiSelector::LookupReponseReceived(
+void PredictionBasedPermissionUiSelector::LookupResponseReceived(
+    bool is_on_device,
     bool lookup_succesful,
     bool response_from_cache,
-    std::unique_ptr<permissions::GeneratePredictionsResponse> response) {
+    const absl::optional<permissions::GeneratePredictionsResponse>& response) {
   request_.reset();
   if (!lookup_succesful || !response || response->prediction_size() == 0) {
     VLOG(1) << "[CPSS] Prediction service request failed";
diff --git a/chrome/browser/permissions/prediction_based_permission_ui_selector.h b/chrome/browser/permissions/prediction_based_permission_ui_selector.h
index eed19c1c..ff9ba49c 100644
--- a/chrome/browser/permissions/prediction_based_permission_ui_selector.h
+++ b/chrome/browser/permissions/prediction_based_permission_ui_selector.h
@@ -52,16 +52,20 @@
  private:
   permissions::PredictionRequestFeatures BuildPredictionRequestFeatures(
       permissions::PermissionRequest* request);
-  void LookupReponseReceived(
+  void LookupResponseReceived(
+      bool is_on_device,
       bool lookup_succesful,
       bool response_from_cache,
-      std::unique_ptr<permissions::GeneratePredictionsResponse> response);
+      const absl::optional<permissions::GeneratePredictionsResponse>& response);
   bool IsAllowedToUseAssistedPrompts(permissions::RequestType request_type);
 
   void set_likelihood_override(PredictionGrantLikelihood mock_likelihood) {
     likelihood_override_for_testing_ = mock_likelihood;
   }
 
+  void OnModelExecutionComplete(
+      const absl::optional<permissions::GeneratePredictionsResponse>& result);
+
   raw_ptr<Profile> profile_;
   std::unique_ptr<PredictionServiceRequest> request_;
   absl::optional<PredictionGrantLikelihood> last_request_grant_likelihood_;
@@ -69,6 +73,9 @@
   absl::optional<PredictionGrantLikelihood> likelihood_override_for_testing_;
 
   DecisionMadeCallback callback_;
+
+  base::WeakPtrFactory<PredictionBasedPermissionUiSelector> weak_ptr_factory_{
+      this};
 };
 
 #endif  // CHROME_BROWSER_PERMISSIONS_PREDICTION_BASED_PERMISSION_UI_SELECTOR_H_
diff --git a/chrome/browser/permissions/prediction_model_handler_factory.cc b/chrome/browser/permissions/prediction_model_handler_factory.cc
new file mode 100644
index 0000000..fe687a2
--- /dev/null
+++ b/chrome/browser/permissions/prediction_model_handler_factory.cc
@@ -0,0 +1,51 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/permissions/prediction_model_handler_factory.h"
+
+#include "base/memory/singleton.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/permissions/prediction_service/prediction_model_handler.h"
+
+// static
+PredictionModelHandlerFactory* PredictionModelHandlerFactory::GetInstance() {
+  return base::Singleton<PredictionModelHandlerFactory>::get();
+}
+
+// static
+permissions::PredictionModelHandler*
+PredictionModelHandlerFactory::GetForBrowserContext(
+    content::BrowserContext* context) {
+  return static_cast<permissions::PredictionModelHandler*>(
+      GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+PredictionModelHandlerFactory::PredictionModelHandlerFactory()
+    : BrowserContextKeyedServiceFactory(
+          "PredictionModelHandler",
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(OptimizationGuideKeyedServiceFactory::GetInstance());
+}
+
+PredictionModelHandlerFactory::~PredictionModelHandlerFactory() = default;
+
+KeyedService* PredictionModelHandlerFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new permissions::PredictionModelHandler(
+      OptimizationGuideKeyedServiceFactory::GetForProfile(
+          Profile::FromBrowserContext(context)),
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::USER_VISIBLE}));
+}
+
+content::BrowserContext* PredictionModelHandlerFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return context;
+}
diff --git a/chrome/browser/permissions/prediction_model_handler_factory.h b/chrome/browser/permissions/prediction_model_handler_factory.h
new file mode 100644
index 0000000..e0b02e1
--- /dev/null
+++ b/chrome/browser/permissions/prediction_model_handler_factory.h
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PERMISSIONS_PREDICTION_MODEL_HANDLER_FACTORY_H_
+#define CHROME_BROWSER_PERMISSIONS_PREDICTION_MODEL_HANDLER_FACTORY_H_
+
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+namespace permissions {
+class PredictionModelHandler;
+}
+
+class PredictionModelHandlerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  PredictionModelHandlerFactory(const PredictionModelHandlerFactory&) = delete;
+  PredictionModelHandlerFactory& operator=(
+      const PredictionModelHandlerFactory&) = delete;
+
+  static PredictionModelHandlerFactory* GetInstance();
+  static permissions::PredictionModelHandler* GetForBrowserContext(
+      content::BrowserContext* context);
+
+ private:
+  PredictionModelHandlerFactory();
+  ~PredictionModelHandlerFactory() override;
+  friend struct base::DefaultSingletonTraits<PredictionModelHandlerFactory>;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+};
+
+#endif  // CHROME_BROWSER_PERMISSIONS_PREDICTION_MODEL_HANDLER_FACTORY_H_
diff --git a/chrome/browser/permissions/prediction_service_request.cc b/chrome/browser/permissions/prediction_service_request.cc
index 95e80a5..2736617 100644
--- a/chrome/browser/permissions/prediction_service_request.cc
+++ b/chrome/browser/permissions/prediction_service_request.cc
@@ -26,7 +26,6 @@
 void PredictionServiceRequest::LookupReponseReceived(
     bool lookup_succesful,
     bool response_from_cache,
-    std::unique_ptr<permissions::GeneratePredictionsResponse> response) {
-  std::move(callback_).Run(lookup_succesful, response_from_cache,
-                           std::move(response));
+    const absl::optional<permissions::GeneratePredictionsResponse>& response) {
+  std::move(callback_).Run(lookup_succesful, response_from_cache, response);
 }
diff --git a/chrome/browser/permissions/prediction_service_request.h b/chrome/browser/permissions/prediction_service_request.h
index 09b3efdf..08b5b9c 100644
--- a/chrome/browser/permissions/prediction_service_request.h
+++ b/chrome/browser/permissions/prediction_service_request.h
@@ -31,7 +31,7 @@
   void LookupReponseReceived(
       bool lookup_succesful,
       bool response_from_cache,
-      std::unique_ptr<permissions::GeneratePredictionsResponse> response);
+      const absl::optional<permissions::GeneratePredictionsResponse>& response);
 
   permissions::PredictionServiceBase::LookupResponseCallback callback_;
 
diff --git a/chrome/browser/profile_resetter/brandcoded_default_settings.cc b/chrome/browser/profile_resetter/brandcoded_default_settings.cc
index 2a24e4ec..765b002 100644
--- a/chrome/browser/profile_resetter/brandcoded_default_settings.cc
+++ b/chrome/browser/profile_resetter/brandcoded_default_settings.cc
@@ -78,9 +78,18 @@
 
 bool BrandcodedDefaultSettings::GetRestoreOnStartup(
     int* restore_on_startup) const {
-  return master_dictionary_ &&
-         master_dictionary_->GetInteger(prefs::kRestoreOnStartup,
-                                        restore_on_startup);
+  if (!master_dictionary_)
+    return false;
+
+  absl::optional<int> maybe_restore_on_startup =
+      master_dictionary_->FindIntPath(prefs::kRestoreOnStartup);
+  if (!maybe_restore_on_startup)
+    return false;
+
+  if (restore_on_startup)
+    *restore_on_startup = *maybe_restore_on_startup;
+
+  return true;
 }
 
 std::unique_ptr<base::ListValue>
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index c274527..aadf17a 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -63,6 +63,7 @@
 #include "chrome/browser/permissions/adaptive_quiet_notification_permission_ui_enabler.h"
 #include "chrome/browser/permissions/last_tab_standing_tracker_factory.h"
 #include "chrome/browser/permissions/permission_auditing_service_factory.h"
+#include "chrome/browser/permissions/prediction_model_handler_factory.h"
 #include "chrome/browser/persisted_state_db/persisted_state_db_content.pb.h"
 #include "chrome/browser/persisted_state_db/profile_proto_db_factory.h"
 #include "chrome/browser/plugins/plugin_prefs_factory.h"
@@ -432,6 +433,10 @@
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
   policy::UserPolicySigninServiceFactory::GetInstance();
 #endif
+  if (base::FeatureList::IsEnabled(
+          permissions::features::kPermissionOnDevicePredictions)) {
+    PredictionModelHandlerFactory::GetInstance();
+  }
   predictors::AutocompleteActionPredictorFactory::GetInstance();
   predictors::LoadingPredictorFactory::GetInstance();
   predictors::PredictorDatabaseFactory::GetInstance();
diff --git a/chrome/browser/profiles/profile_browsertest.cc b/chrome/browser/profiles/profile_browsertest.cc
index e8eb6a08..feb5e0e 100644
--- a/chrome/browser/profiles/profile_browsertest.cc
+++ b/chrome/browser/profiles/profile_browsertest.cc
@@ -360,7 +360,13 @@
 
 // Test OnProfileCreate is called with is_new_profile set to true when
 // creating a new profile synchronously.
-IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, CreateNewProfileSynchronous) {
+// TODO(crbug.com/1218591): Flaky on ChromeOS-Ash.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#define MAYBE_CreateNewProfileSynchronous DISABLED_CreateNewProfileSynchronous
+#else
+#define MAYBE_CreateNewProfileSynchronous CreateNewProfileSynchronous
+#endif
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, MAYBE_CreateNewProfileSynchronous) {
   base::ScopedAllowBlockingForTesting allow_blocking;
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
@@ -442,9 +448,15 @@
   FlushIoTaskRunnerAndSpinThreads();
 }
 
+// TODO(crbug.com/1282123): Flaky on ChromeOS-Ash.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#define MAYBE_CreateOldProfileAsynchronous DISABLED_CreateOldProfileAsynchronous
+#else
+#define MAYBE_CreateOldProfileAsynchronous CreateOldProfileAsynchronous
+#endif
 // Test OnProfileCreate is called with is_new_profile set to false when
 // creating a profile asynchronously with an existing prefs file.
-IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, CreateOldProfileAsynchronous) {
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, MAYBE_CreateOldProfileAsynchronous) {
   base::ScopedAllowBlockingForTesting allow_blocking;
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
diff --git a/chrome/browser/profiles/renderer_updater.cc b/chrome/browser/profiles/renderer_updater.cc
index 92ea2337..a2c7b9c4 100644
--- a/chrome/browser/profiles/renderer_updater.cc
+++ b/chrome/browser/profiles/renderer_updater.cc
@@ -40,23 +40,19 @@
     RendererContentSettingRules* rules) {
   rules->image_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), incognito));
   rules->auto_dark_content_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), incognito));
   rules->script_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), incognito));
   rules->mixed_content_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
       std::string(), incognito));
 }
 
@@ -163,8 +159,7 @@
       rules.script_rules.emplace_back(
           ContentSettingsPattern::Wildcard(),
           ContentSettingsPattern::Wildcard(),
-          base::Value::FromUniquePtrValue(
-              content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
           std::string(), is_incognito_process);
     }
   }
diff --git a/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn b/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
index 5b06aa3..2724558 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
+++ b/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
@@ -29,6 +29,7 @@
     ":build_mojo_grdp",
     ":build_preprocessed_grdp",
     ":emoji_data",
+    ":emoticon_data",
   ]
   grd_prefix = "emoji_picker"
   out_grd = resources_grd_file
@@ -122,6 +123,32 @@
          ] + rebase_path(keyword_xmls, root_build_dir)
 }
 
+action_foreach("emoticon_data") {
+  script = "tools/emoticon_data.py"
+
+  metadata_json =
+      [ "//third_party/emoji-metadata/src/emoji_14_0_ordering.json" ]
+  keyword_xmls = [
+    # later keywords will override earlier keywords for a particular emoji.
+    "//third_party/cldr/src/common/annotations/en.xml",
+    "//third_party/cldr/src/common/annotations/en_001.xml",
+    "//third_party/cldr/src/common/annotationsDerived/en.xml",
+    "//third_party/cldr/src/common/annotationsDerived/en_001.xml",
+  ]
+  merged_json = "$target_gen_dir/emoticon_{{source_name_part}}.json"
+
+  sources = metadata_json
+  inputs = keyword_xmls
+  outputs = [ merged_json ]
+  args = [
+           "--metadata",
+           "{{source}}",
+           "--output",
+           rebase_path(merged_json, root_build_dir),
+           "--keywords",
+         ] + rebase_path(keyword_xmls, root_build_dir)
+}
+
 js_library("emoji_picker") {
   deps = [
     ":constants",
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
index 8ebdfc6..1dc664d 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
@@ -231,6 +231,7 @@
   ready() {
     super.ready();
 
+    // TODO(b/211520561): Handle loading of emoticon data.
     const initializationPromise = Promise.all([
       this.apiProxy_.getFeatureList().then(
           (response) => this.setActiveFeatures(response.featureList)),
diff --git a/chrome/browser/resources/chromeos/emoji_picker/tools/emoticon_data.py b/chrome/browser/resources/chromeos/emoji_picker/tools/emoticon_data.py
new file mode 100644
index 0000000..a9b35f19
--- /dev/null
+++ b/chrome/browser/resources/chromeos/emoji_picker/tools/emoticon_data.py
@@ -0,0 +1,143 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+
+_SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
+_CHROME_SOURCE = os.path.realpath(
+    os.path.join(_SCRIPT_DIR, *[os.path.pardir] * 6))
+sys.path.append(os.path.join(_CHROME_SOURCE, 'build/android/gyp'))
+
+import argparse
+import json
+import xml.etree.ElementTree
+from itertools import chain
+from util import build_utils
+from emoji_data import parse_emoji_annotations, parse_emoji_metadata, _chr
+
+
+class EmojiConverter:
+    def __init__(self, names, keywords):
+        """
+        Args:
+            names (dict(str, str)): dictionary that maps an emoji string
+                representation to its names.
+            keywords (dict(str, list(str))): dictionary that maps an emoji
+                string representation to the emoji's keywords.
+        """
+        self.emoji_names = names
+        self.emoji_keywords = keywords
+
+    # TODO(b/211822956): Refactor to a common function with emoji_data.
+    def get_emoji_string(self, codepoints):
+        """Convert emoji codepoints into its unicode representation.
+
+        Args:
+            codepoints (list(int)): list of integer codepoints.
+
+        Returns:
+            str: unicode string representation of the emoji.
+        """
+        # Transform array of codepoint values into unicode string.
+        string = u''.join(_chr(x) for x in codepoints)
+        if string in self.emoji_names:
+            return string
+        else:
+            return string.replace(u'\ufe0f', u'')
+
+    def pull_emoticons_from_emoji(self, emoji):
+        """Convert a single emoji object into a list of emoticon objects.
+        Each emoji object contains zero, one or many emoticon representations.
+
+        Args:
+            emoji (dict): the emoji object from the emoji ordering.
+
+        Returns:
+            list(dict): array of emoticon objects.
+        """
+        if 'emoticons' not in emoji:
+            return []
+
+        emoticon_list = []
+        for emoticon_string in emoji['emoticons']:
+            codepoints = emoji['base']
+            emoji_repr = self.get_emoji_string(codepoints)
+            new_emoticon = {
+                'base': {
+                    'string': emoticon_string,
+                    'name': self.emoji_names.get(emoji_repr, ''),
+                    'keywords': self.emoji_keywords.get(emoji_repr, [])
+                },
+                # TODO(b/211696216): Handle the case when emoticons have
+                # alternates.
+                'alternates': []
+            }
+            emoticon_list.append(new_emoticon)
+
+        return emoticon_list
+
+    def get_emoticon_data(self, metadata):
+        """Get the emoticon data from the emoji ordering data.
+        Args:
+            metadata (list(dict)): list of emoji objects.
+
+        Returns:
+            list(dict): list of emoticon objects.
+        """
+
+        return [{
+            "group":
+            group["group"],
+            "items":
+            list(
+                chain.from_iterable(
+                    self.pull_emoticons_from_emoji(emoji)
+                    for emoji in group["emoji"]))
+        } for group in metadata]
+
+
+def main(args):
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--metadata',
+                        required=True,
+                        help='emoji metadata ordering file as JSON')
+    parser.add_argument('--output',
+                        required=True,
+                        help='output JSON file path')
+    parser.add_argument('--keywords',
+                        required=True,
+                        nargs='+',
+                        help='emoji keyword files as list of XML files')
+
+    options = parser.parse_args(args)
+
+    metadata_file = options.metadata
+    keyword_files = options.keywords
+    output_file = options.output
+
+    # TODO(b/211822956): Refactor to a common function with emoji_data.
+    # Iterate through keyword files and combine them.
+    names = {}
+    keywords = {}
+    for file in keyword_files:
+        _names, _keywords = parse_emoji_annotations(file)
+        names.update(_names)
+        keywords.update(_keywords)
+
+    # Parse emoji ordering data.
+    metadata = parse_emoji_metadata(metadata_file)
+    emoji_converter = EmojiConverter(names, keywords)
+    emoticon_data = emoji_converter.get_emoticon_data(metadata)
+
+    # Write output file atomically in utf-8 format.
+    with build_utils.AtomicOutput(output_file) as tmp_file:
+        tmp_file.write(
+            json.dumps(emoticon_data,
+                       separators=(',', ':'),
+                       ensure_ascii=False).encode('utf-8'))
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
index 106ec9c..206cd6b6 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
@@ -57,6 +57,9 @@
     }
   }
 </style>
+<iron-media-query query="(prefers-color-scheme: dark)"
+    query-matches="{{isDarkModeActive_}}">
+</iron-media-query>
 <settings-section page-title="$i18n{aboutOsPageTitle}" section="about">
   <settings-animated-pages id="pages" section="about"
       focus-config="[[focusConfig_]]">
@@ -80,7 +83,9 @@
               icon$="[[getUpdateStatusIcon_(
                   hasEndOfLife_, currentUpdateStatusEvent_)]]"
               src="[[getThrobberSrcIfUpdating_(
-                  hasEndOfLife_, currentUpdateStatusEvent_)]]">
+                  isDarkModeActive_,
+                  hasEndOfLife_,
+                  currentUpdateStatusEvent_)]]">
           </iron-icon>
         </div>
         <div class="start padded">
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
index d86ffe3..355ae2e 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
@@ -24,6 +24,7 @@
 import '//resources/cr_elements/cr_link_row/cr_link_row.js';
 import '//resources/cr_elements/icons.m.js';
 import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '//resources/polymer/v3_0/iron-media-query/iron-media-query.js';
 
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
 import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
@@ -55,6 +56,15 @@
   ],
 
   properties: {
+    /**
+     * Whether the about page is being rendered in dark mode.
+     * @private
+     */
+    isDarkModeActive_: {
+      type: Boolean,
+      value: false,
+    },
+
     /** @private {?UpdateStatusChangedEvent} */
     currentUpdateStatusEvent_: {
       type: Object,
@@ -549,7 +559,9 @@
     switch (this.currentUpdateStatusEvent_.status) {
       case UpdateStatus.CHECKING:
       case UpdateStatus.UPDATING:
-        return 'chrome://resources/images/throbber_small.svg';
+        return this.isDarkModeActive_ ?
+            'chrome://resources/images/throbber_small_dark.svg' :
+            'chrome://resources/images/throbber_small.svg';
       default:
         return null;
     }
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
index ac0c032..b67d70b 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
@@ -81,9 +81,9 @@
         label="$i18n{enableContentProtectionAttestation}"
         deep-link-focus-id$="[[Setting.kVerifiedAccess]]">
     </settings-toggle-button>
-    <div class="hr"></div>
     <template is="dom-if" if="[[isThunderboltSupported_]]">
       <template is="dom-if" if="[[isPciguardUiEnabled_]]">
+        <div class="hr"></div>
         <!-- This toggle is always disabled. The underlying pref state is
              handled by the JS impl. This is to prevent toggling the pref
              before the user confirms the action. -->
diff --git a/chrome/browser/sync/test/integration/single_client_standalone_transport_sync_test.cc b/chrome/browser/sync/test/integration/single_client_standalone_transport_sync_test.cc
index 6f542b7..4390d448 100644
--- a/chrome/browser/sync/test/integration/single_client_standalone_transport_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_standalone_transport_sync_test.cc
@@ -48,7 +48,8 @@
     allowed_types.PutAll({syncer::APPS, syncer::APP_SETTINGS, syncer::APP_LIST,
                           syncer::APP_SETTINGS, syncer::ARC_PACKAGE,
                           syncer::PRINTERS, syncer::OS_PREFERENCES,
-                          syncer::OS_PRIORITY_PREFERENCES, syncer::WEB_APPS});
+                          syncer::OS_PRIORITY_PREFERENCES, syncer::WEB_APPS,
+                          syncer::WORKSPACE_DESK});
   }
   if (base::FeatureList::IsEnabled(switches::kSyncWifiConfigurations)) {
     allowed_types.Put(syncer::WIFI_CONFIGURATIONS);
diff --git a/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc b/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
index 12b2dba..420d9019 100644
--- a/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
@@ -23,9 +23,18 @@
 #include "content/public/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/constants/ash_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 using syncer::UserSelectableType;
 using syncer::UserSelectableTypeSet;
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+using syncer::UserSelectableOsType;
+using syncer::UserSelectableOsTypeSet;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 namespace web_app {
 namespace {
 
@@ -113,6 +122,22 @@
   ASSERT_TRUE(SetupSync());
   syncer::SyncServiceImpl* service = GetSyncService(0);
   syncer::SyncUserSettings* settings = service->GetUserSettings();
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Apps is an OS type on Ash if SyncSettingsCategorization is enabled.
+  if (ash::features::IsSyncSettingsCategorizationEnabled()) {
+    ASSERT_TRUE(
+        settings->GetSelectedOsTypes().Has(UserSelectableOsType::kOsApps));
+    EXPECT_TRUE(service->GetActiveDataTypes().Has(syncer::WEB_APPS));
+
+    settings->SetSelectedOsTypes(false, UserSelectableOsTypeSet());
+    ASSERT_FALSE(
+        settings->GetSelectedOsTypes().Has(UserSelectableOsType::kOsApps));
+    EXPECT_FALSE(service->GetActiveDataTypes().Has(syncer::WEB_APPS));
+    return;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   ASSERT_TRUE(settings->GetSelectedTypes().Has(UserSelectableType::kApps));
   EXPECT_TRUE(service->GetActiveDataTypes().Has(syncer::WEB_APPS));
 
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 95e57e6..5f5000b 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -532,7 +532,7 @@
   return profile;
 }
 
-bool StartupBrowserCreator::LaunchBrowser(
+void StartupBrowserCreator::LaunchBrowser(
     const base::CommandLine& command_line,
     Profile* profile,
     const base::FilePath& cur_dir,
@@ -551,26 +551,17 @@
 
   if (!IsSilentLaunchEnabled(command_line, profile)) {
     StartupBrowserCreatorImpl lwp(cur_dir, command_line, this, is_first_run);
-    const bool launched = lwp.Launch(
-        profile,
-        in_synchronous_profile_launch_ ? chrome::startup::IsProcessStartup::kYes
-                                       : chrome::startup::IsProcessStartup::kNo,
-        std::move(launch_mode_recorder));
-    in_synchronous_profile_launch_ = false;
-    if (!launched) {
-      LOG(ERROR) << "launch error";
-      return false;
-    }
-  } else {
-    in_synchronous_profile_launch_ = false;
+    lwp.Launch(profile,
+               in_synchronous_profile_launch_
+                   ? chrome::startup::IsProcessStartup::kYes
+                   : chrome::startup::IsProcessStartup::kNo,
+               std::move(launch_mode_recorder));
   }
-
+  in_synchronous_profile_launch_ = false;
   profile_launch_observer.Get().AddLaunched(profile);
-
-  return true;
 }
 
-bool StartupBrowserCreator::LaunchBrowserForLastProfiles(
+void StartupBrowserCreator::LaunchBrowserForLastProfiles(
     const base::CommandLine& command_line,
     const base::FilePath& cur_dir,
     chrome::startup::IsProcessStartup process_startup,
@@ -599,7 +590,7 @@
     // The guest session is used to indicate the the profile picker should be
     // displayed on start-up. See GetStartupProfilePath().
     ShowProfilePicker(process_startup);
-    return true;
+    return;
   }
 
   // |last_opened_profiles| will be empty in the following circumstances:
@@ -626,22 +617,22 @@
                 profile_to_open);
         if (full_restore_service) {
           full_restore_service->LaunchBrowserWhenReady();
-          return true;
+          return;
         }
       }
 #endif
-      return LaunchBrowser(command_line, profile_to_open, cur_dir,
-                           process_startup, is_first_run,
-                           std::make_unique<LaunchModeRecorder>());
+      LaunchBrowser(command_line, profile_to_open, cur_dir, process_startup,
+                    is_first_run, std::make_unique<LaunchModeRecorder>());
+      return;
     }
 
     // Show ProfilePicker if |last_used_profile| can't be auto opened.
     ShowProfilePicker(process_startup);
-    return true;
+    return;
   }
-  return ProcessLastOpenedProfiles(command_line, cur_dir, process_startup,
-                                   is_first_run, last_used_profile,
-                                   last_opened_profiles);
+  ProcessLastOpenedProfiles(command_line, cur_dir, process_startup,
+                            is_first_run, last_used_profile,
+                            last_opened_profiles);
 }
 
 // static
@@ -1079,9 +1070,10 @@
 
   // Launch the browser if the profile is unable to open web apps.
   if (!CanOpenWebApp(privacy_safe_profile)) {
-    return LaunchBrowserForLastProfiles(command_line, cur_dir, process_startup,
-                                        is_first_run, last_used_profile,
-                                        last_opened_profiles);
+    LaunchBrowserForLastProfiles(command_line, cur_dir, process_startup,
+                                 is_first_run, last_used_profile,
+                                 last_opened_profiles);
+    return true;
   }
 
   bool handled_as_app =
@@ -1102,12 +1094,13 @@
   if (handled_as_app)
     return true;
 
-  return LaunchBrowserForLastProfiles(command_line, cur_dir, process_startup,
-                                      is_first_run, last_used_profile,
-                                      last_opened_profiles);
+  LaunchBrowserForLastProfiles(command_line, cur_dir, process_startup,
+                               is_first_run, last_used_profile,
+                               last_opened_profiles);
+  return true;
 }
 
-bool StartupBrowserCreator::ProcessLastOpenedProfiles(
+void StartupBrowserCreator::ProcessLastOpenedProfiles(
     const base::CommandLine& command_line,
     const base::FilePath& cur_dir,
     chrome::startup::IsProcessStartup process_startup,
@@ -1150,15 +1143,12 @@
     }
     // Only record a launch mode histogram for |last_used_profile|. Pass a
     // null launch_mode_recorder for other profiles.
-    if (!LaunchBrowser((profile == last_used_profile)
-                           ? command_line
-                           : command_line_without_urls,
-                       profile, cur_dir, process_startup, is_first_run,
-                       profile == last_used_profile
-                           ? std::make_unique<LaunchModeRecorder>()
-                           : nullptr)) {
-      return false;
-    }
+    LaunchBrowser((profile == last_used_profile) ? command_line
+                                                 : command_line_without_urls,
+                  profile, cur_dir, process_startup, is_first_run,
+                  profile == last_used_profile
+                      ? std::make_unique<LaunchModeRecorder>()
+                      : nullptr);
     // We've launched at least one browser.
     process_startup = chrome::startup::IsProcessStartup::kNo;
   }
@@ -1174,7 +1164,6 @@
   else
 #endif
     profile_launch_observer.Get().set_profile_to_activate(last_used_profile);
-  return true;
 }
 
 // static
diff --git a/chrome/browser/ui/startup/startup_browser_creator.h b/chrome/browser/ui/startup/startup_browser_creator.h
index 3f793b15..f147c87 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.h
+++ b/chrome/browser/ui/startup/startup_browser_creator.h
@@ -128,7 +128,7 @@
   // |is_first_run| indicates that this is a new profile.
   // If |launch_mode_recorder| is non null, and a browser is launched, a launch
   // mode histogram will be recorded.
-  bool LaunchBrowser(const base::CommandLine& command_line,
+  void LaunchBrowser(const base::CommandLine& command_line,
                      Profile* profile,
                      const base::FilePath& cur_dir,
                      chrome::startup::IsProcessStartup process_startup,
@@ -136,9 +136,8 @@
                      std::unique_ptr<LaunchModeRecorder> launch_mode_recorder);
 
   // Launch browser for `last_opened_profiles` if it's not empty. Otherwise,
-  // launch browser for `last_used_profile`. Return false if any browser is
-  // failed to be launched. Otherwise, return true.
-  bool LaunchBrowserForLastProfiles(
+  // launch browser for `last_used_profile`.
+  void LaunchBrowserForLastProfiles(
       const base::CommandLine& command_line,
       const base::FilePath& cur_dir,
       chrome::startup::IsProcessStartup process_startup,
@@ -227,10 +226,8 @@
                           const Profiles& last_opened_profiles);
 
   // Launch the |last_used_profile| with the full command line, and the other
-  // |last_opened_profiles| without the URLs to launch. Return false if any
-  // browser is failed to be launched. Otherwise, return true.
-
-  bool ProcessLastOpenedProfiles(
+  // |last_opened_profiles| without the URLs to launch.
+  void ProcessLastOpenedProfiles(
       const base::CommandLine& command_line,
       const base::FilePath& cur_dir,
       chrome::startup::IsProcessStartup process_startup,
diff --git a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
index f349895..568fb57a 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
@@ -2309,8 +2309,7 @@
   StartupBrowserCreatorImpl launch(base::FilePath(), dummy,
                                    chrome::startup::IsFirstRun::kNo);
   // Fake |process_startup| true.
-  EXPECT_TRUE(launch.Launch(profile1, chrome::startup::IsProcessStartup::kYes,
-                            nullptr));
+  launch.Launch(profile1, chrome::startup::IsProcessStartup::kYes, nullptr);
 
   // We should get two windows from profile1.
   ASSERT_EQ(3u, BrowserList::GetInstance()->size());
@@ -3222,8 +3221,8 @@
 
   StartupBrowserCreatorImpl launch(base::FilePath(), dummy, &browser_creator,
                                    chrome::startup::IsFirstRun::kYes);
-  ASSERT_TRUE(launch.Launch(browser()->profile(),
-                            chrome::startup::IsProcessStartup::kNo, nullptr));
+  launch.Launch(browser()->profile(), chrome::startup::IsProcessStartup::kNo,
+                nullptr);
 
   // This should have created a new browser window.
   Browser* new_browser = FindOneOtherBrowser(browser());
@@ -3279,8 +3278,8 @@
   base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
   StartupBrowserCreatorImpl launch(base::FilePath(), dummy, &browser_creator,
                                    chrome::startup::IsFirstRun::kYes);
-  ASSERT_TRUE(launch.Launch(browser()->profile(),
-                            chrome::startup::IsProcessStartup::kYes, nullptr));
+  launch.Launch(browser()->profile(), chrome::startup::IsProcessStartup::kYes,
+                nullptr);
 
   // This should have created a new browser window.
   Browser* new_browser = FindOneOtherBrowser(browser());
@@ -3326,8 +3325,8 @@
   base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
   StartupBrowserCreatorImpl launch(base::FilePath(), dummy, &browser_creator,
                                    chrome::startup::IsFirstRun::kYes);
-  ASSERT_TRUE(launch.Launch(browser()->profile(),
-                            chrome::startup::IsProcessStartup::kYes, nullptr));
+  launch.Launch(browser()->profile(), chrome::startup::IsProcessStartup::kYes,
+                nullptr);
 
   // This should have created a new browser window.
   Browser* new_browser = FindOneOtherBrowser(browser());
@@ -3549,8 +3548,7 @@
     Profile* profile = browser()->profile();
     StartupBrowserCreatorImpl launch(base::FilePath(), command_line,
                                      chrome::startup::IsFirstRun::kNo);
-    EXPECT_TRUE(launch.Launch(profile, chrome::startup::IsProcessStartup::kYes,
-                              nullptr));
+    launch.Launch(profile, chrome::startup::IsProcessStartup::kYes, nullptr);
 
     // This should have created a new browser window.
     Browser* new_browser = FindOneOtherBrowser(browser());
@@ -3738,8 +3736,7 @@
 
     StartupBrowserCreatorImpl launch(base::FilePath(), command_line,
                                      chrome::startup::IsFirstRun::kNo);
-    EXPECT_TRUE(launch.Launch(profile, chrome::startup::IsProcessStartup::kNo,
-                              nullptr));
+    launch.Launch(profile, chrome::startup::IsProcessStartup::kNo, nullptr);
     Browser* new_browser = BrowserList::GetInstance()->GetLastActive();
 
     return infobars::ContentInfoBarManager::FromWebContents(
@@ -3822,8 +3819,7 @@
     command_line.AppendSwitch(extra_switch);
     StartupBrowserCreatorImpl launch(base::FilePath(), command_line,
                                      chrome::startup::IsFirstRun::kNo);
-    EXPECT_TRUE(launch.Launch(profile, chrome::startup::IsProcessStartup::kYes,
-                              nullptr));
+    launch.Launch(profile, chrome::startup::IsProcessStartup::kYes, nullptr);
 
     // This should have created a new browser window.
     Browser* new_browser = FindOneOtherBrowser(browser());
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.cc b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
index bfd2cf06..fb8a2f6 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
@@ -160,7 +160,7 @@
   }
 }
 
-bool StartupBrowserCreatorImpl::Launch(
+void StartupBrowserCreatorImpl::Launch(
     Profile* profile,
     chrome::startup::IsProcessStartup process_startup,
     std::unique_ptr<LaunchModeRecorder> launch_mode_recorder) {
@@ -202,8 +202,6 @@
   Browser* browser = BrowserList::GetInstance()->GetLastActive();
   if (browser)
     MaybeToggleFullscreen(browser);
-
-  return true;
 }
 
 Browser* StartupBrowserCreatorImpl::OpenURLsInBrowser(
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.h b/chrome/browser/ui/startup/startup_browser_creator_impl.h
index 078cb1e6..99195f4 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.h
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.h
@@ -57,11 +57,10 @@
   // to full screen.
   static void MaybeToggleFullscreen(Browser* browser);
 
-  // Creates the necessary windows for startup. Returns true on success,
-  // false on failure. |process_startup| indicates whether Chrome is just
-  // starting up or already running and the user wants to launch another
-  // instance.
-  bool Launch(Profile* profile,
+  // Creates the necessary windows for startup. |process_startup| indicates
+  // whether Chrome is just starting up or already running and the user wants to
+  // launch another instance.
+  void Launch(Profile* profile,
               chrome::startup::IsProcessStartup process_startup,
               std::unique_ptr<LaunchModeRecorder> launch_mode_recorder);
 
diff --git a/chrome/browser/ui/startup/startup_browser_creator_triggered_reset_browsertest_win.cc b/chrome/browser/ui/startup/startup_browser_creator_triggered_reset_browsertest_win.cc
index e8cf2a4..1eed870 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_triggered_reset_browsertest_win.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_triggered_reset_browsertest_win.cc
@@ -148,8 +148,7 @@
   base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
   StartupBrowserCreatorImpl launch(base::FilePath(), dummy,
                                    chrome::startup::IsFirstRun::kNo);
-  ASSERT_TRUE(
-      launch.Launch(profile, chrome::startup::IsProcessStartup::kNo, nullptr));
+  launch.Launch(profile, chrome::startup::IsProcessStartup::kNo, nullptr);
 
   // This should have created a new browser window.  |browser()| is still
   // around at this point, even though we've closed its window.
@@ -196,8 +195,8 @@
   base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
   StartupBrowserCreatorImpl launch(base::FilePath(), dummy, &browser_creator,
                                    chrome::startup::IsFirstRun::kYes);
-  ASSERT_TRUE(launch.Launch(browser()->profile(),
-                            chrome::startup::IsProcessStartup::kYes, nullptr));
+  launch.Launch(browser()->profile(), chrome::startup::IsProcessStartup::kYes,
+                nullptr);
 
   // This should have created a new browser window.
   Browser* new_browser = FindOneOtherBrowser(browser());
@@ -233,8 +232,8 @@
   {
     StartupBrowserCreatorImpl launch(base::FilePath(), dummy,
                                      chrome::startup::IsFirstRun::kNo);
-    ASSERT_TRUE(launch.Launch(browser()->profile(),
-                              chrome::startup::IsProcessStartup::kNo, nullptr));
+    launch.Launch(browser()->profile(), chrome::startup::IsProcessStartup::kNo,
+                  nullptr);
   }
 
   // This should have created a new browser window.  |browser()| is still
@@ -273,8 +272,8 @@
   {
     StartupBrowserCreatorImpl launch(base::FilePath(), dummy,
                                      chrome::startup::IsFirstRun::kNo);
-    ASSERT_TRUE(launch.Launch(other_profile_ptr,
-                              chrome::startup::IsProcessStartup::kNo, nullptr));
+    launch.Launch(other_profile_ptr, chrome::startup::IsProcessStartup::kNo,
+                  nullptr);
   }
 
   Browser* other_profile_browser =
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 223c366..5ddf580 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1641079648-7e170b0b86e5a98c5faf2a32b99f43aaf1e1f1e3.profdata
+chrome-linux-main-1641209992-9b5e916eee4495025742febd993ea6a0513377c5.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 2f081cc..18d4e4b 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1641100268-01da50dad03a5d9b9dbbd3bd21b31a704e6aec96.profdata
+chrome-mac-main-1641209992-bd5ce016681e70f1f30da7f369b33094899b77c7.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 57ca13b7..0edea8f 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1641091700-3c9f0b505e41fd59384b335d85e20990eca858b4.profdata
+chrome-win32-main-1641209992-cb8e43d1baf1f72d48fe692da81014a92c1e64b0.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index a1d1be0..312fb47 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1641079648-d8fdbe173f87cd4d976a2073537e5e2b8acec8ce.profdata
+chrome-win64-main-1641199122-fb549b2a53aec65791690b158e8253403f2b856c.profdata
diff --git a/chrome/service/service_utility_process_host.cc b/chrome/service/service_utility_process_host.cc
index 51d60fe0..ea86fa0 100644
--- a/chrome/service/service_utility_process_host.cc
+++ b/chrome/service/service_utility_process_host.cc
@@ -34,7 +34,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/font_cache_dispatcher_win.h"
 #include "content/public/common/result_codes.h"
-#include "content/public/common/sandbox_init.h"
+#include "content/public/common/sandbox_init_win.h"
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 2300206..a127f81 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3217,7 +3217,7 @@
         "../browser/ash/login/device_family_link_allowed_policy_browsertest.cc",
         "../browser/ash/login/enable_debugging_browsertest.cc",
         "../browser/ash/login/encryption_migration_browsertest.cc",
-        "../browser/ash/login/enrollment/enrollment_local_policy_server_browsertest.cc",
+        "../browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc",
         "../browser/ash/login/enrollment/enrollment_screen_browsertest.cc",
         "../browser/ash/login/enrollment/hands_off_enrollment_browsertest.cc",
         "../browser/ash/login/enrollment/mock_auto_enrollment_check_screen.cc",
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 055afb8..ce14c96 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-14432.0.0
\ No newline at end of file
+14435.0.0
\ No newline at end of file
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index 9bab659..92a66a4c 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-99-4758.14-1640601734-benchmark-99.0.4799.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-99-4758.14-1640601734-benchmark-99.0.4801.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt
index 06d6f6067..2c79c773 100644
--- a/chromeos/profiles/bigcore.afdo.newest.txt
+++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-bigcore-99-4758.14-1640608967-benchmark-99.0.4799.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-bigcore-99-4758.14-1640608967-benchmark-99.0.4801.0-r1-redacted.afdo.xz
diff --git a/components/autofill_assistant/browser/service/rpc_type.h b/components/autofill_assistant/browser/service/rpc_type.h
index dca4bb77..17ad3fd 100644
--- a/components/autofill_assistant/browser/service/rpc_type.h
+++ b/components/autofill_assistant/browser/service/rpc_type.h
@@ -12,8 +12,8 @@
   GET_ACTIONS,
   GET_TRIGGER_SCRIPTS,
   SUPPORTS_SCRIPT,
+  GET_CAPABILITIES_BY_HASH_PREFIX,
 };
-
 }
 
 #endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_RPC_TYPE_H_
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index 6b08db1..43e655e 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@
 {
-  "version": "4.76",
-  "log_list_timestamp": "2022-01-01T01:34:09Z",
+  "version": "4.77",
+  "log_list_timestamp": "2022-01-02T01:33:58Z",
   "operators": [
     {
       "name": "Google",
diff --git a/components/content_settings/core/browser/content_settings_default_provider.cc b/components/content_settings/core/browser/content_settings_default_provider.cc
index e7d8c4c..4f141888 100644
--- a/components/content_settings/core/browser/content_settings_default_provider.cc
+++ b/components/content_settings/core/browser/content_settings_default_provider.cc
@@ -340,8 +340,9 @@
   DCHECK(!info || !value ||
          info->IsDefaultSettingValid(ValueToContentSetting(value)));
   default_settings_[content_type] =
-      value ? base::Value::ToUniquePtrValue(value->Clone())
-            : ContentSettingToValue(GetDefaultValue(content_type));
+      value ? ToNullableUniquePtrValue(value->Clone())
+            : ToNullableUniquePtrValue(
+                  ContentSettingToValue(GetDefaultValue(content_type)));
 }
 
 void DefaultProvider::WriteToPref(ContentSettingsType content_type,
@@ -397,7 +398,8 @@
 std::unique_ptr<base::Value> DefaultProvider::ReadFromPref(
     ContentSettingsType content_type) {
   int int_value = prefs_->GetInteger(GetPrefName(content_type));
-  return ContentSettingToValue(IntToContentSetting(int_value));
+  return ToNullableUniquePtrValue(
+      ContentSettingToValue(IntToContentSetting(int_value)));
 }
 
 void DefaultProvider::DiscardOrMigrateObsoletePreferences() {
diff --git a/components/content_settings/core/browser/content_settings_utils.cc b/components/content_settings/core/browser/content_settings_utils.cc
index 75473709..66d1dff 100644
--- a/components/content_settings/core/browser/content_settings_utils.cc
+++ b/components/content_settings/core/browser/content_settings_utils.cc
@@ -70,9 +70,9 @@
 
 bool ContentSettingFromString(const std::string& name,
                               ContentSetting* setting) {
-  for (size_t i = 0; i < base::size(kContentSettingsStringMapping); ++i) {
-    if (name == kContentSettingsStringMapping[i].content_setting_str) {
-      *setting = kContentSettingsStringMapping[i].content_setting;
+  for (const auto& string_mapping : kContentSettingsStringMapping) {
+    if (name == string_mapping.content_setting_str) {
+      *setting = string_mapping.content_setting;
       return true;
     }
   }
@@ -129,24 +129,21 @@
   // is added for all origins.
   rules->auto_dark_content_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          ContentSettingToValue(CONTENT_SETTING_ALLOW)),
-      std::string(), map->IsOffTheRecord()));
+      ContentSettingToValue(CONTENT_SETTING_ALLOW), std::string(),
+      map->IsOffTheRecord()));
 #else
   // Android doesn't use image content settings, so ALLOW rule is added for
   // all origins.
   rules->image_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          ContentSettingToValue(CONTENT_SETTING_ALLOW)),
-      std::string(), map->IsOffTheRecord()));
+      ContentSettingToValue(CONTENT_SETTING_ALLOW), std::string(),
+      map->IsOffTheRecord()));
   // In Android active mixed content is hard blocked, with no option to allow
   // it.
   rules->mixed_content_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          ContentSettingToValue(CONTENT_SETTING_BLOCK)),
-      std::string(), map->IsOffTheRecord()));
+      ContentSettingToValue(CONTENT_SETTING_BLOCK), std::string(),
+      map->IsOffTheRecord()));
   map->GetSettingsForOneType(ContentSettingsType::AUTO_DARK_WEB_CONTENT,
                              &(rules->auto_dark_content_rules));
 #endif
diff --git a/components/content_settings/core/browser/host_content_settings_map.cc b/components/content_settings/core/browser/host_content_settings_map.cc
index 3d9c1d68..537e979 100644
--- a/components/content_settings/core/browser/host_content_settings_map.cc
+++ b/components/content_settings/core/browser/host_content_settings_map.cc
@@ -142,7 +142,8 @@
         ContentSetting initial_setting =
             content_settings::ValueToContentSetting(initial_value);
         if (content_settings::IsMorePermissive(setting, initial_setting))
-          return content_settings::ContentSettingToValue(initial_setting);
+          return content_settings::ToNullableUniquePtrValue(
+              content_settings::ContentSettingToValue(initial_setting));
         return value;
     }
   }
@@ -346,7 +347,8 @@
       default_setting = content_settings::ValueToContentSetting(
           ProcessIncognitoInheritanceBehavior(
               content_type,
-              content_settings::ContentSettingToValue(default_setting))
+              content_settings::ToNullableUniquePtrValue(
+                  content_settings::ContentSettingToValue(default_setting)))
               .get());
     }
     if (default_setting != CONTENT_SETTING_DEFAULT) {
diff --git a/components/content_settings/core/common/content_settings_utils.cc b/components/content_settings/core/common/content_settings_utils.cc
index 45a3ee0d..1fb63196 100644
--- a/components/content_settings/core/common/content_settings_utils.cc
+++ b/components/content_settings/core/common/content_settings_utils.cc
@@ -15,33 +15,45 @@
 // Converts a |Value| to a |ContentSetting|. Returns true if |value| encodes
 // a valid content setting, false otherwise. Note that |CONTENT_SETTING_DEFAULT|
 // is encoded as a NULL value, so it is not allowed as an integer value.
-bool ParseContentSettingValue(const base::Value* value,
+bool ParseContentSettingValue(const base::Value& value,
                               ContentSetting* setting) {
-  if (!value) {
+  if (value.is_none()) {
     *setting = CONTENT_SETTING_DEFAULT;
     return true;
   }
-  if (!value->is_int())
+  if (!value.is_int())
     return false;
-  *setting = IntToContentSetting(value->GetInt());
+  *setting = IntToContentSetting(value.GetInt());
   return *setting != CONTENT_SETTING_DEFAULT;
 }
 
 }  // namespace
 
-ContentSetting ValueToContentSetting(const base::Value* value) {
+ContentSetting ValueToContentSetting(const base::Value& value) {
   ContentSetting setting = CONTENT_SETTING_DEFAULT;
   bool valid = ParseContentSettingValue(value, &setting);
   DCHECK(valid);
   return setting;
 }
 
-std::unique_ptr<base::Value> ContentSettingToValue(ContentSetting setting) {
+// DEPRECATED. Replace with method above when Value pointers are removed.
+ContentSetting ValueToContentSetting(const base::Value* value) {
+  base::Value empty;
+  return ValueToContentSetting(value ? *value : empty);
+}
+
+base::Value ContentSettingToValue(ContentSetting setting) {
   if (setting <= CONTENT_SETTING_DEFAULT ||
       setting >= CONTENT_SETTING_NUM_SETTINGS) {
-    return nullptr;
+    return base::Value();
   }
-  return std::make_unique<base::Value>(setting);
+  return base::Value(setting);
+}
+
+std::unique_ptr<base::Value> ToNullableUniquePtrValue(base::Value value) {
+  if (value.is_none())
+    return nullptr;
+  return base::Value::ToUniquePtrValue(std::move(value));
 }
 
 }  // namespace content_settings
diff --git a/components/content_settings/core/common/content_settings_utils.h b/components/content_settings/core/common/content_settings_utils.h
index 640483d..3695f1b 100644
--- a/components/content_settings/core/common/content_settings_utils.h
+++ b/components/content_settings/core/common/content_settings_utils.h
@@ -16,11 +16,17 @@
 namespace content_settings {
 
 // Converts |value| to |ContentSetting|.
+ContentSetting ValueToContentSetting(const base::Value& value);
+// DEPRECATED: Use the method above if possible.
 ContentSetting ValueToContentSetting(const base::Value* value);
 
 // Returns a base::Value representation of |setting| if |setting| is
-// a valid content setting. Otherwise, returns a nullptr.
-std::unique_ptr<base::Value> ContentSettingToValue(ContentSetting setting);
+// a valid content setting. Otherwise, returns an empty value.
+base::Value ContentSettingToValue(ContentSetting setting);
+
+// Adaptor for converting from the new way of base::Value to the old one.
+// Like base::Value::ToUniquePtrValue but converts NONE-type values to nullptr.
+std::unique_ptr<base::Value> ToNullableUniquePtrValue(base::Value value);
 
 }  // namespace content_settings
 
diff --git a/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc b/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
index 4954260..5dc1922cb4 100644
--- a/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
+++ b/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
@@ -93,13 +93,13 @@
 
 class MockContentSettingsAgentImpl : public ContentSettingsAgentImpl {
  public:
-  MockContentSettingsAgentImpl(content::RenderFrame* render_frame);
+  explicit MockContentSettingsAgentImpl(content::RenderFrame* render_frame);
 
   MockContentSettingsAgentImpl(const MockContentSettingsAgentImpl&) = delete;
   MockContentSettingsAgentImpl& operator=(const MockContentSettingsAgentImpl&) =
       delete;
 
-  ~MockContentSettingsAgentImpl() override {}
+  ~MockContentSettingsAgentImpl() override = default;
 
   const GURL& image_url() const { return image_url_; }
   const std::string& image_origin() const { return image_origin_; }
@@ -288,8 +288,7 @@
       content_setting_rules.script_rules;
   script_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
       std::string(), false));
   ContentSettingsAgentImpl* agent =
       ContentSettingsAgentImpl::Get(GetMainRenderFrame());
@@ -333,8 +332,7 @@
       content_setting_rules.image_rules;
   image_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -351,8 +349,7 @@
       ContentSettingPatternSource(
           ContentSettingsPattern::Wildcard(),
           ContentSettingsPattern::FromString(mock_agent.image_origin()),
-          base::Value::FromUniquePtrValue(
-              content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
           std::string(), false));
 
   EXPECT_TRUE(agent->AllowImage(true, mock_agent.image_url()));
@@ -372,8 +369,7 @@
       content_setting_rules.image_rules;
   image_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -389,8 +385,7 @@
       ContentSettingPatternSource(
           ContentSettingsPattern::Wildcard(),
           ContentSettingsPattern::FromString(mock_agent.image_origin()),
-          base::Value::FromUniquePtrValue(
-              content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
           std::string(), false));
   EXPECT_FALSE(agent->AllowImage(true, mock_agent.image_url()));
   base::RunLoop().RunUntilIdle();
@@ -406,8 +401,7 @@
       content_setting_rules.script_rules;
   script_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -429,8 +423,7 @@
       content_setting_rules.script_rules;
   script_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -453,8 +446,7 @@
       content_setting_rules.script_rules;
   script_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -480,8 +472,7 @@
       content_setting_rules.script_rules;
   script_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -513,8 +504,7 @@
   script_setting_rules.clear();
   script_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), false));
   agent->SetContentSettingRules(&content_setting_rules);
 
@@ -554,8 +544,7 @@
       content_setting_rules.script_rules;
   script_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -579,8 +568,7 @@
       content_setting_rules.mixed_content_rules;
   mixed_content_setting_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -594,8 +582,7 @@
       ContentSettingPatternSource(
           ContentSettingsPattern::FromString("https://example.com/"),
           ContentSettingsPattern::Wildcard(),
-          base::Value::FromUniquePtrValue(
-              content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
           std::string(), false));
 
   EXPECT_FALSE(agent->ShouldAutoupgradeMixedContent());
@@ -613,8 +600,7 @@
       content_setting_rules.auto_dark_content_rules;
   auto_dark_content_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_ALLOW),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
@@ -628,8 +614,7 @@
       ContentSettingPatternSource(
           ContentSettingsPattern::FromString("https://example.com/"),
           ContentSettingsPattern::Wildcard(),
-          base::Value::FromUniquePtrValue(
-              content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
           std::string(), false));
 
   EXPECT_FALSE(agent->AllowAutoDarkWebContent(true));
@@ -647,8 +632,7 @@
       content_setting_rules.auto_dark_content_rules;
   auto_dark_content_rules.push_back(ContentSettingPatternSource(
       ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
-      base::Value::FromUniquePtrValue(
-          content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK)),
+      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
       std::string(), false));
 
   ContentSettingsAgentImpl* agent =
diff --git a/components/exo/text_input.cc b/components/exo/text_input.cc
index 0f82d16..6441bb8f 100644
--- a/components/exo/text_input.cc
+++ b/components/exo/text_input.cc
@@ -7,7 +7,6 @@
 #include <algorithm>
 #include <utility>
 
-#include "ash/keyboard/ui/keyboard_ui_controller.h"
 #include "base/check.h"
 #include "base/logging.h"
 #include "base/strings/string_piece.h"
@@ -19,6 +18,7 @@
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/ime/input_method.h"
 #include "ui/base/ime/utf_offset.h"
+#include "ui/base/ime/virtual_keyboard_controller.h"
 #include "ui/events/event.h"
 
 namespace exo {
@@ -37,8 +37,6 @@
     : delegate_(std::move(delegate)) {}
 
 TextInput::~TextInput() {
-  if (keyboard_ui_controller_)
-    keyboard_ui_controller_->RemoveObserver(this);
   if (input_method_)
     Deactivate();
 }
@@ -66,10 +64,8 @@
 }
 
 void TextInput::HideVirtualKeyboard() {
-  // TODO(crbug.com/1275410): Use
-  // InputMethod::SetVirtualKeyboardVisibilityIfEnabled() here, too.
-  if (keyboard_ui_controller_)
-    keyboard_ui_controller_->HideKeyboardByUser();
+  if (input_method_)
+    input_method_->SetVirtualKeyboardVisibilityIfEnabled(false);
   pending_vk_visible_ = false;
 }
 
@@ -290,7 +286,11 @@
   if (input_method == input_method_)
     return;
   input_method_->DetachTextInputClient(this);
+  if (auto* controller = input_method_->GetVirtualKeyboardController())
+    controller->RemoveObserver(this);
   input_method_ = input_method;
+  if (auto* controller = input_method_->GetVirtualKeyboardController())
+    controller->AddObserver(this);
   input_method_->SetFocusedTextInputClient(this);
 }
 
@@ -395,8 +395,12 @@
   NOTIMPLEMENTED_LOG_ONCE();
 }
 
-void TextInput::OnKeyboardVisibilityChanged(bool is_visible) {
-  delegate_->OnVirtualKeyboardVisibilityChanged(is_visible);
+void TextInput::OnKeyboardVisible(const gfx::Rect& keyboard_rect) {
+  delegate_->OnVirtualKeyboardVisibilityChanged(true);
+}
+
+void TextInput::OnKeyboardHidden() {
+  delegate_->OnVirtualKeyboardVisibilityChanged(false);
 }
 
 void TextInput::AttachInputMethod() {
@@ -411,18 +415,11 @@
   input_mode_ = ui::TEXT_INPUT_MODE_TEXT;
   input_type_ = ui::TEXT_INPUT_TYPE_TEXT;
   input_method_ = input_method;
+  if (auto* controller = input_method_->GetVirtualKeyboardController())
+    controller->AddObserver(this);
   input_method_->SetFocusedTextInputClient(this);
   delegate_->Activated();
 
-  if (!keyboard_ui_controller_ &&
-      keyboard::KeyboardUIController::HasInstance()) {
-    auto* keyboard_ui_controller = keyboard::KeyboardUIController::Get();
-    if (keyboard_ui_controller->IsEnabled()) {
-      keyboard_ui_controller_ = keyboard_ui_controller;
-      keyboard_ui_controller_->AddObserver(this);
-    }
-  }
-
   if (pending_vk_visible_) {
     input_method_->SetVirtualKeyboardVisibilityIfEnabled(true);
     pending_vk_visible_ = false;
@@ -437,6 +434,8 @@
   input_mode_ = ui::TEXT_INPUT_MODE_DEFAULT;
   input_type_ = ui::TEXT_INPUT_TYPE_NONE;
   input_method_->DetachTextInputClient(this);
+  if (auto* controller = input_method_->GetVirtualKeyboardController())
+    controller->RemoveObserver(this);
   input_method_ = nullptr;
   delegate_->Deactivated();
 }
diff --git a/components/exo/text_input.h b/components/exo/text_input.h
index fd5a490..0c8c63e 100644
--- a/components/exo/text_input.h
+++ b/components/exo/text_input.h
@@ -7,7 +7,6 @@
 
 #include <string>
 
-#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/i18n/rtl.h"
 #include "base/strings/string_piece.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -16,6 +15,7 @@
 #include "ui/base/ime/text_input_flags.h"
 #include "ui/base/ime/text_input_mode.h"
 #include "ui/base/ime/text_input_type.h"
+#include "ui/base/ime/virtual_keyboard_controller_observer.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/range/range.h"
 
@@ -23,16 +23,12 @@
 class InputMethod;
 }
 
-namespace keyboard {
-class KeyboardUIController;
-}
-
 namespace exo {
 class Surface;
 
 // This class bridges the ChromeOS input method and a text-input context.
 class TextInput : public ui::TextInputClient,
-                  public ash::KeyboardControllerObserver {
+                  public ui::VirtualKeyboardControllerObserver {
  public:
   class Delegate {
    public:
@@ -180,8 +176,9 @@
       absl::optional<gfx::Rect>* control_bounds,
       absl::optional<gfx::Rect>* selection_bounds) override {}
 
-  // ash::KeyboardControllerObserver:
-  void OnKeyboardVisibilityChanged(bool is_visible) override;
+  // ui::VirtualKeyboardControllerObserver:
+  void OnKeyboardVisible(const gfx::Rect& keyboard_rect) override;
+  void OnKeyboardHidden() override;
 
  private:
   void AttachInputMethod();
@@ -189,8 +186,6 @@
 
   // Delegate to talk to actual its client.
   std::unique_ptr<Delegate> delegate_;
-  // Keyboard Controller to observe the visibility.
-  keyboard::KeyboardUIController* keyboard_ui_controller_ = nullptr;
 
   // On requesting to show Virtual Keyboard, InputMethod may not be connected.
   // So, remember the request temporarily, and then on InputMethod connection
diff --git a/components/exo/text_input_unittest.cc b/components/exo/text_input_unittest.cc
index 2bc232c..354ad3f 100644
--- a/components/exo/text_input_unittest.cc
+++ b/components/exo/text_input_unittest.cc
@@ -185,10 +185,17 @@
   testing::Mock::VerifyAndClearExpectations(&observer);
   testing::Mock::VerifyAndClearExpectations(delegate());
 
+  // Currently, Virtual Keyboard Controller is not set up, and so
+  // the virtual keyboard events are gone. Here, we capture the callback
+  // from the observer and translate it to the ones of
+  // VirtualKeyboardControllerObserver event as if it is done via
+  // real VirtualKeyboardController implementation.
   EXPECT_CALL(observer, OnVirtualKeyboardVisibilityChangedIfEnabled)
       .WillOnce(testing::Invoke([this](bool should_show) {
         if (should_show)
-          text_input()->OnKeyboardVisibilityChanged(true);
+          text_input()->OnKeyboardVisible(gfx::Rect());
+        else
+          text_input()->OnKeyboardHidden();
       }));
   EXPECT_CALL(*delegate(), OnVirtualKeyboardVisibilityChanged(true)).Times(1);
   text_input()->ShowVirtualKeyboardIfEnabled();
@@ -209,10 +216,18 @@
   text_input()->ShowVirtualKeyboardIfEnabled();
 
   EXPECT_CALL(observer, OnTextInputStateChanged(text_input())).Times(1);
+
+  // Currently, Virtual Keyboard Controller is not set up, and so
+  // the virtual keyboard events are gone. Here, we capture the callback
+  // from the observer and translate it to the ones of
+  // VirtualKeyboardControllerObserver event as if it is done via
+  // real VirtualKeyboardController implementation.
   EXPECT_CALL(observer, OnVirtualKeyboardVisibilityChangedIfEnabled)
       .WillOnce(testing::Invoke([this](bool should_show) {
         if (should_show)
-          text_input()->OnKeyboardVisibilityChanged(true);
+          text_input()->OnKeyboardVisible(gfx::Rect());
+        else
+          text_input()->OnKeyboardHidden();
       }));
   EXPECT_CALL(*delegate(), Activated).Times(1);
   EXPECT_CALL(*delegate(), OnVirtualKeyboardVisibilityChanged(true)).Times(1);
diff --git a/components/exo/wayland/zwp_text_input_manager.cc b/components/exo/wayland/zwp_text_input_manager.cc
index 7eec62f..778a3f18 100644
--- a/components/exo/wayland/zwp_text_input_manager.cc
+++ b/components/exo/wayland/zwp_text_input_manager.cc
@@ -68,7 +68,11 @@
   }
 
   void OnVirtualKeyboardVisibilityChanged(bool is_visible) override {
-    zwp_text_input_v1_send_input_panel_state(text_input_, is_visible);
+    // The detailed spec of |state| is implementation dependent.
+    // So, now we use the lowest bit to indicate whether keyboard is visible.
+    // This behavior is consistent with ozone/wayland to support Lacros.
+    zwp_text_input_v1_send_input_panel_state(text_input_,
+                                             static_cast<uint32_t>(is_visible));
     wl_client_flush(client());
   }
 
diff --git a/components/nacl/broker/BUILD.gn b/components/nacl/broker/BUILD.gn
index ecad577..31a7822 100644
--- a/components/nacl/broker/BUILD.gn
+++ b/components/nacl/broker/BUILD.gn
@@ -48,7 +48,7 @@
 source_set("content_dummy") {
   check_includes = false
   sources = [
-    "//content/public/common/sandbox_init.h",
+    "//content/public/common/sandbox_init_win.h",
     "//content/public/common/sandboxed_process_launcher_delegate.h",
   ]
 }
diff --git a/components/nacl/broker/nacl_broker_listener.cc b/components/nacl/broker/nacl_broker_listener.cc
index b551b86..6111d75 100644
--- a/components/nacl/broker/nacl_broker_listener.cc
+++ b/components/nacl/broker/nacl_broker_listener.cc
@@ -23,7 +23,7 @@
 #include "components/nacl/common/nacl_service.h"
 #include "components/nacl/common/nacl_switches.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/sandbox_init.h"
+#include "content/public/common/sandbox_init_win.h"
 #include "ipc/ipc_channel.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "mojo/public/cpp/system/invitation.h"
diff --git a/components/nacl/browser/nacl_process_host.cc b/components/nacl/browser/nacl_process_host.cc
index f912298..e9bfb3ce 100644
--- a/components/nacl/browser/nacl_process_host.cc
+++ b/components/nacl/browser/nacl_process_host.cc
@@ -87,7 +87,6 @@
 #include "base/win/windows_version.h"
 #include "components/nacl/browser/nacl_broker_service_win.h"
 #include "components/nacl/common/nacl_debug_exception_handler_win.h"
-#include "content/public/common/sandbox_init.h"
 #endif
 
 using content::BrowserThread;
diff --git a/components/nacl/loader/BUILD.gn b/components/nacl/loader/BUILD.gn
index 75f2adf..8deff67 100644
--- a/components/nacl/loader/BUILD.gn
+++ b/components/nacl/loader/BUILD.gn
@@ -61,14 +61,14 @@
 # as "minimal" (stuff that should not be in the nacl64.exe build).
 source_set("minimal_content_dummy") {
   check_includes = false
-  sources = [
-    "//content/public/common/main_function_params.h",
-    "//content/public/common/sandbox_init.h",
-  ]
+  sources = [ "//content/public/common/main_function_params.h" ]
 
   # Deps required by the above headers.
   deps = [ "//media:media_buildflags" ]
 
+  if (is_win) {
+    sources += [ "//content/public/common/sandbox_init_win.h" ]
+  }
   if (is_linux || is_chromeos) {
     sources += [ "//content/public/common/zygote/sandbox_support_linux.h" ]
   }
diff --git a/components/nacl/loader/nacl_helper_win_64.cc b/components/nacl/loader/nacl_helper_win_64.cc
index 56a3b1e..ab1bac06 100644
--- a/components/nacl/loader/nacl_helper_win_64.cc
+++ b/components/nacl/loader/nacl_helper_win_64.cc
@@ -25,7 +25,6 @@
 #include "content/public/app/sandbox_helper_win.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/main_function_params.h"
-#include "content/public/common/sandbox_init.h"
 #include "mojo/core/embedder/embedder.h"
 #include "sandbox/policy/sandbox.h"
 #include "sandbox/policy/sandbox_type.h"
diff --git a/components/nacl/loader/nacl_listener.cc b/components/nacl/loader/nacl_listener.cc
index fdd8485..8f83c88b6 100644
--- a/components/nacl/loader/nacl_listener.cc
+++ b/components/nacl/loader/nacl_listener.cc
@@ -51,8 +51,6 @@
 
 #if defined(OS_WIN)
 #include <io.h>
-
-#include "content/public/common/sandbox_init.h"
 #endif
 
 namespace {
diff --git a/components/nacl/loader/nacl_main_platform_delegate_mac.mm b/components/nacl/loader/nacl_main_platform_delegate_mac.mm
index 4c13fd786..42bcfe9 100644
--- a/components/nacl/loader/nacl_main_platform_delegate_mac.mm
+++ b/components/nacl/loader/nacl_main_platform_delegate_mac.mm
@@ -4,15 +4,6 @@
 
 #include "components/nacl/loader/nacl_main_platform_delegate.h"
 
-#import <Cocoa/Cocoa.h>
-
-#include "base/command_line.h"
-#include "components/nacl/common/nacl_switches.h"
-#include "content/public/common/sandbox_init.h"
-#include "sandbox/mac/seatbelt.h"
-#include "sandbox/mac/seatbelt_exec.h"
-#include "sandbox/policy/sandbox_type.h"
-
 void NaClMainPlatformDelegate::EnableSandbox(
     const content::MainFunctionParams& parameters) {
   // The sandbox on macOS is enabled as soon as main() executes, so there is
diff --git a/components/nacl/loader/sandbox_linux/BUILD.gn b/components/nacl/loader/sandbox_linux/BUILD.gn
index ec71a034..ff0382e 100644
--- a/components/nacl/loader/sandbox_linux/BUILD.gn
+++ b/components/nacl/loader/sandbox_linux/BUILD.gn
@@ -21,7 +21,6 @@
     "//base",
     "//components/nacl/common",
     "//components/nacl/loader",
-    "//content/public/common",
     "//crypto",
     "//ipc",
     "//sandbox",
diff --git a/components/nacl/loader/sandbox_linux/nacl_bpf_sandbox_linux.cc b/components/nacl/loader/sandbox_linux/nacl_bpf_sandbox_linux.cc
index 8e9730ba9..733c8627 100644
--- a/components/nacl/loader/sandbox_linux/nacl_bpf_sandbox_linux.cc
+++ b/components/nacl/loader/sandbox_linux/nacl_bpf_sandbox_linux.cc
@@ -25,11 +25,11 @@
 #include "base/files/scoped_file.h"
 #include "base/notreached.h"
 #include "components/nacl/common/nacl_switches.h"
-#include "content/public/common/sandbox_init.h"
 #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
 #include "sandbox/linux/bpf_dsl/policy.h"
 #include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h"
 #include "sandbox/linux/system_headers/linux_syscalls.h"
+#include "sandbox/policy/linux/sandbox_seccomp_bpf_linux.h"
 
 #endif  // BUILDFLAG(USE_SECCOMP_BPF)
 
@@ -46,7 +46,8 @@
 class NaClBPFSandboxPolicy : public sandbox::bpf_dsl::Policy {
  public:
   NaClBPFSandboxPolicy()
-      : baseline_policy_(content::GetBPFSandboxBaselinePolicy()),
+      : baseline_policy_(
+            sandbox::policy::SandboxSeccompBPF::GetBaselinePolicy()),
         policy_pid_(syscall(__NR_getpid)) {
     const base::CommandLine* command_line =
         base::CommandLine::ForCurrentProcess();
@@ -175,10 +176,8 @@
 
 bool InitializeBPFSandbox(base::ScopedFD proc_fd) {
 #if BUILDFLAG(USE_SECCOMP_BPF)
-  bool sandbox_is_initialized = content::InitializeSandbox(
-      std::unique_ptr<sandbox::bpf_dsl::Policy>(new NaClBPFSandboxPolicy),
-      std::move(proc_fd));
-  if (sandbox_is_initialized) {
+  if (sandbox::policy::SandboxSeccompBPF::StartSandboxWithExternalPolicy(
+          std::make_unique<NaClBPFSandboxPolicy>(), std::move(proc_fd))) {
     RunSandboxSanityChecks();
     return true;
   }
diff --git a/components/nacl/loader/sandbox_linux/nacl_sandbox_linux.cc b/components/nacl/loader/sandbox_linux/nacl_sandbox_linux.cc
index d020f107..582b7cc9 100644
--- a/components/nacl/loader/sandbox_linux/nacl_sandbox_linux.cc
+++ b/components/nacl/loader/sandbox_linux/nacl_sandbox_linux.cc
@@ -26,7 +26,6 @@
 #include "build/build_config.h"
 #include "components/nacl/common/nacl_switches.h"
 #include "components/nacl/loader/sandbox_linux/nacl_bpf_sandbox_linux.h"
-#include "content/public/common/content_switches.h"
 #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
 #include "sandbox/linux/services/credentials.h"
 #include "sandbox/linux/services/namespace_sandbox.h"
diff --git a/components/nacl/renderer/manifest_service_channel.cc b/components/nacl/renderer/manifest_service_channel.cc
index 08d82388..f53a439 100644
--- a/components/nacl/renderer/manifest_service_channel.cc
+++ b/components/nacl/renderer/manifest_service_channel.cc
@@ -9,7 +9,6 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "build/build_config.h"
-#include "content/public/common/sandbox_init.h"
 #include "content/public/renderer/render_thread.h"
 #include "ipc/ipc_channel.h"
 #include "ipc/ipc_platform_file.h"
diff --git a/components/nacl/renderer/nexe_load_manager.cc b/components/nacl/renderer/nexe_load_manager.cc
index cfc17c7..20b92af 100644
--- a/components/nacl/renderer/nexe_load_manager.cc
+++ b/components/nacl/renderer/nexe_load_manager.cc
@@ -22,7 +22,6 @@
 #include "components/nacl/renderer/progress_event.h"
 #include "components/nacl/renderer/trusted_plugin_channel.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/sandbox_init.h"
 #include "content/public/renderer/pepper_plugin_instance.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/public/renderer/render_view.h"
diff --git a/components/nacl/renderer/plugin/pnacl_translate_thread.cc b/components/nacl/renderer/plugin/pnacl_translate_thread.cc
index 1868095..76d73ef5 100644
--- a/components/nacl/renderer/plugin/pnacl_translate_thread.cc
+++ b/components/nacl/renderer/plugin/pnacl_translate_thread.cc
@@ -14,7 +14,6 @@
 #include "base/time/time.h"
 #include "components/nacl/renderer/plugin/plugin.h"
 #include "components/nacl/renderer/plugin/plugin_error.h"
-#include "content/public/common/sandbox_init.h"
 #include "ppapi/c/ppb_file_io.h"
 #include "ppapi/cpp/var.h"
 #include "ppapi/proxy/ppapi_messages.h"
diff --git a/components/permissions/features.cc b/components/permissions/features.cc
index 5b03e43e..c5905bcb 100644
--- a/components/permissions/features.cc
+++ b/components/permissions/features.cc
@@ -67,6 +67,9 @@
     "kPermissionPredictionServiceUseUrlOverride",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kPermissionOnDevicePredictions{
+    "PermissionOnDevicePredictions", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 namespace feature_params {
 
diff --git a/components/permissions/features.h b/components/permissions/features.h
index dd423a1..2d0c87f 100644
--- a/components/permissions/features.h
+++ b/components/permissions/features.h
@@ -49,6 +49,9 @@
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
 extern const base::Feature kPermissionPredictionServiceUseUrlOverride;
 
+COMPONENT_EXPORT(PERMISSIONS_COMMON)
+extern const base::Feature kPermissionOnDevicePredictions;
+
 }  // namespace features
 namespace feature_params {
 
diff --git a/components/permissions/permission_uma_util.cc b/components/permissions/permission_uma_util.cc
index ab48606..fecda65 100644
--- a/components/permissions/permission_uma_util.cc
+++ b/components/permissions/permission_uma_util.cc
@@ -17,8 +17,8 @@
 #include "components/permissions/permission_request.h"
 #include "components/permissions/permission_util.h"
 #include "components/permissions/permissions_client.h"
+#include "components/permissions/prediction_service/prediction_common.h"
 #include "components/permissions/prediction_service/prediction_request_features.h"
-#include "components/permissions/prediction_service/prediction_service.h"
 #include "components/permissions/request_type.h"
 #include "components/ukm/content/source_url_recorder.h"
 #include "content/public/browser/permission_type.h"
@@ -243,68 +243,57 @@
 
   builder
       .SetStats_LoudPromptsOfType_DenyRate(
-          PredictionService::GetRoundedRatioForUkm(
-              loud_ui_actions_counts_for_request_type.denies,
-              loud_ui_prompts_count_for_request_type))
-      .SetStats_LoudPromptsOfType_DismissRate(
-          PredictionService::GetRoundedRatioForUkm(
-              loud_ui_actions_counts_for_request_type.dismissals,
-              loud_ui_prompts_count_for_request_type))
+          GetRoundedRatioForUkm(loud_ui_actions_counts_for_request_type.denies,
+                                loud_ui_prompts_count_for_request_type))
+      .SetStats_LoudPromptsOfType_DismissRate(GetRoundedRatioForUkm(
+          loud_ui_actions_counts_for_request_type.dismissals,
+          loud_ui_prompts_count_for_request_type))
       .SetStats_LoudPromptsOfType_GrantRate(
-          PredictionService::GetRoundedRatioForUkm(
-              loud_ui_actions_counts_for_request_type.grants,
-              loud_ui_prompts_count_for_request_type))
+          GetRoundedRatioForUkm(loud_ui_actions_counts_for_request_type.grants,
+                                loud_ui_prompts_count_for_request_type))
       .SetStats_LoudPromptsOfType_IgnoreRate(
-          PredictionService::GetRoundedRatioForUkm(
-              loud_ui_actions_counts_for_request_type.ignores,
-              loud_ui_prompts_count_for_request_type))
-      .SetStats_LoudPromptsOfType_Count(PredictionService::BucketizeValue(
-          loud_ui_prompts_count_for_request_type));
+          GetRoundedRatioForUkm(loud_ui_actions_counts_for_request_type.ignores,
+                                loud_ui_prompts_count_for_request_type))
+      .SetStats_LoudPromptsOfType_Count(
+          BucketizeValue(loud_ui_prompts_count_for_request_type));
 
   builder
-      .SetStats_LoudPrompts_DenyRate(PredictionService::GetRoundedRatioForUkm(
+      .SetStats_LoudPrompts_DenyRate(GetRoundedRatioForUkm(
           loud_ui_actions_counts.denies, loud_ui_prompts_count))
-      .SetStats_LoudPrompts_DismissRate(
-          PredictionService::GetRoundedRatioForUkm(
-              loud_ui_actions_counts.dismissals, loud_ui_prompts_count))
-      .SetStats_LoudPrompts_GrantRate(PredictionService::GetRoundedRatioForUkm(
+      .SetStats_LoudPrompts_DismissRate(GetRoundedRatioForUkm(
+          loud_ui_actions_counts.dismissals, loud_ui_prompts_count))
+      .SetStats_LoudPrompts_GrantRate(GetRoundedRatioForUkm(
           loud_ui_actions_counts.grants, loud_ui_prompts_count))
-      .SetStats_LoudPrompts_IgnoreRate(PredictionService::GetRoundedRatioForUkm(
+      .SetStats_LoudPrompts_IgnoreRate(GetRoundedRatioForUkm(
           loud_ui_actions_counts.ignores, loud_ui_prompts_count))
-      .SetStats_LoudPrompts_Count(
-          PredictionService::BucketizeValue(loud_ui_prompts_count));
+      .SetStats_LoudPrompts_Count(BucketizeValue(loud_ui_prompts_count));
 
   builder
       .SetStats_AllPromptsOfType_DenyRate(
-          PredictionService::GetRoundedRatioForUkm(
-              actions_counts_for_request_type.denies,
-              prompts_count_for_request_type))
+          GetRoundedRatioForUkm(actions_counts_for_request_type.denies,
+                                prompts_count_for_request_type))
       .SetStats_AllPromptsOfType_DismissRate(
-          PredictionService::GetRoundedRatioForUkm(
-              actions_counts_for_request_type.dismissals,
-              prompts_count_for_request_type))
+          GetRoundedRatioForUkm(actions_counts_for_request_type.dismissals,
+                                prompts_count_for_request_type))
       .SetStats_AllPromptsOfType_GrantRate(
-          PredictionService::GetRoundedRatioForUkm(
-              actions_counts_for_request_type.grants,
-              prompts_count_for_request_type))
+          GetRoundedRatioForUkm(actions_counts_for_request_type.grants,
+                                prompts_count_for_request_type))
       .SetStats_AllPromptsOfType_IgnoreRate(
-          PredictionService::GetRoundedRatioForUkm(
-              actions_counts_for_request_type.ignores,
-              prompts_count_for_request_type))
+          GetRoundedRatioForUkm(actions_counts_for_request_type.ignores,
+                                prompts_count_for_request_type))
       .SetStats_AllPromptsOfType_Count(
-          PredictionService::BucketizeValue(prompts_count_for_request_type));
+          BucketizeValue(prompts_count_for_request_type));
 
   builder
-      .SetStats_AllPrompts_DenyRate(PredictionService::GetRoundedRatioForUkm(
-          actions_counts.denies, prompts_count))
-      .SetStats_AllPrompts_DismissRate(PredictionService::GetRoundedRatioForUkm(
-          actions_counts.dismissals, prompts_count))
-      .SetStats_AllPrompts_GrantRate(PredictionService::GetRoundedRatioForUkm(
-          actions_counts.grants, prompts_count))
-      .SetStats_AllPrompts_IgnoreRate(PredictionService::GetRoundedRatioForUkm(
-          actions_counts.ignores, prompts_count))
-      .SetStats_AllPrompts_Count(
-          PredictionService::BucketizeValue(prompts_count));
+      .SetStats_AllPrompts_DenyRate(
+          GetRoundedRatioForUkm(actions_counts.denies, prompts_count))
+      .SetStats_AllPrompts_DismissRate(
+          GetRoundedRatioForUkm(actions_counts.dismissals, prompts_count))
+      .SetStats_AllPrompts_GrantRate(
+          GetRoundedRatioForUkm(actions_counts.grants, prompts_count))
+      .SetStats_AllPrompts_IgnoreRate(
+          GetRoundedRatioForUkm(actions_counts.ignores, prompts_count))
+      .SetStats_AllPrompts_Count(BucketizeValue(prompts_count));
 
   if (ui_reason.has_value())
     builder.SetPromptDispositionReason(static_cast<int64_t>(ui_reason.value()));
diff --git a/components/permissions/prediction_service/BUILD.gn b/components/permissions/prediction_service/BUILD.gn
index 0a8f8ca..2abe8e2c 100644
--- a/components/permissions/prediction_service/BUILD.gn
+++ b/components/permissions/prediction_service/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//components/optimization_guide/features.gni")
 import("//third_party/protobuf/proto_library.gni")
 
 proto_library("prediction_service_messages_proto") {
@@ -10,21 +11,33 @@
 
 source_set("prediction_service") {
   sources = [
+    "prediction_common.cc",
+    "prediction_common.h",
+    "prediction_model_executor.cc",
+    "prediction_model_executor.h",
+    "prediction_model_handler.cc",
+    "prediction_model_handler.h",
     "prediction_request_features.h",
     "prediction_service.cc",
     "prediction_service.h",
     "prediction_service_base.h",
-    "prediction_service_common.cc",
-    "prediction_service_common.h",
   ]
   deps = [
     "//build:chromeos_buildflags",
     "//components/keyed_service/content",
     "//components/permissions:permissions_common",
+    "//content/public/browser",
     "//services/network/public/cpp:cpp",
     "//third_party/protobuf:protobuf_lite",
+    "//third_party/tflite:tflite_public_headers",
+    "//third_party/tflite_support",
+    "//third_party/tflite_support:tflite_support_proto",
   ]
-  public_deps = [ ":prediction_service_messages_proto" ]
+  public_deps = [
+    ":prediction_service_messages_proto",
+    "//components/optimization_guide/core",
+    "//components/optimization_guide/proto:optimization_guide_proto",
+  ]
 }
 
 source_set("unit_tests") {
diff --git a/components/permissions/prediction_service/DEPS b/components/permissions/prediction_service/DEPS
index e1fbc94..57af075d 100644
--- a/components/permissions/prediction_service/DEPS
+++ b/components/permissions/prediction_service/DEPS
@@ -1,7 +1,9 @@
 include_rules = [
   "+google/protobuf",
   "+net",
+  "+components/optimization_guide",
   "+services/network/public",
   "+services/network/test",
   "+third_party/googletest",
+  "+third_party/tflite_support",
 ]
diff --git a/components/permissions/prediction_service/prediction_common.cc b/components/permissions/prediction_service/prediction_common.cc
new file mode 100644
index 0000000..f670e82
--- /dev/null
+++ b/components/permissions/prediction_service/prediction_common.cc
@@ -0,0 +1,264 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/permissions/prediction_service/prediction_common.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/notreached.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+
+namespace permissions {
+
+float GetRoundedRatio(int numerator, int denominator) {
+  if (denominator == 0)
+    return 0;
+  return roundf(numerator / kRoundToMultiplesOf / denominator) *
+         kRoundToMultiplesOf;
+}
+
+int GetRoundedRatioForUkm(int numerator, int denominator) {
+  return GetRoundedRatio(numerator, denominator) * 100;
+}
+
+int BucketizeValue(int count) {
+  for (const int bucket : kCountBuckets) {
+    if (count >= bucket)
+      return bucket;
+  }
+  return 0;
+}
+
+ClientFeatures_Platform GetCurrentPlatformProto() {
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
+    defined(OS_MAC)
+  return permissions::ClientFeatures_Platform_PLATFORM_DESKTOP;
+#elif defined(OS_ANDROID) || defined(OS_FUCHSIA)
+  return permissions::ClientFeatures_Platform_PLATFORM_MOBILE;
+#else
+  return permissions::ClientFeatures_Platform_PLATFORM_UNSPECIFIED;
+#endif
+}
+
+ClientFeatures_PlatformEnum GetCurrentPlatformEnumProto() {
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
+    defined(OS_MAC)
+  return permissions::ClientFeatures_PlatformEnum_PLATFORM_DESKTOP_V2;
+#elif defined(OS_ANDROID) || defined(OS_FUCHSIA)
+  return permissions::ClientFeatures_PlatformEnum_PLATFORM_MOBILE_V2;
+#else
+  return permissions::ClientFeatures_PlatformEnum_PLATFORM_UNSPECIFIED_V2;
+#endif
+}
+
+ClientFeatures_Gesture ConvertToProtoGesture(
+    const permissions::PermissionRequestGestureType type) {
+  switch (type) {
+    case permissions::PermissionRequestGestureType::GESTURE:
+      return permissions::ClientFeatures_Gesture_GESTURE;
+    case permissions::PermissionRequestGestureType::NO_GESTURE:
+      return permissions::ClientFeatures_Gesture_NO_GESTURE;
+    case permissions::PermissionRequestGestureType::UNKNOWN:
+      return permissions::ClientFeatures_Gesture_GESTURE_UNSPECIFIED;
+    case permissions::PermissionRequestGestureType::NUM:
+      break;
+  }
+
+  NOTREACHED();
+  return permissions::ClientFeatures_Gesture_GESTURE_UNSPECIFIED;
+}
+
+ClientFeatures_GestureEnum ConvertToProtoGestureEnum(
+    const permissions::PermissionRequestGestureType type) {
+  switch (type) {
+    case permissions::PermissionRequestGestureType::GESTURE:
+      return permissions::ClientFeatures_GestureEnum_GESTURE_V2;
+    case permissions::PermissionRequestGestureType::NO_GESTURE:
+    case permissions::PermissionRequestGestureType::UNKNOWN:
+      return permissions::ClientFeatures_GestureEnum_GESTURE_UNSPECIFIED_V2;
+    case permissions::PermissionRequestGestureType::NUM:
+      break;
+  }
+
+  NOTREACHED();
+  return permissions::ClientFeatures_GestureEnum_GESTURE_UNSPECIFIED_V2;
+}
+
+void FillInStatsFeatures(const PredictionRequestFeatures::ActionCounts& counts,
+                         StatsFeatures* features) {
+  int total_counts = counts.total();
+
+  // Round to only 2 decimal places to help prevent fingerprinting.
+  features->set_avg_deny_rate(GetRoundedRatio(counts.denies, total_counts));
+  features->set_avg_dismiss_rate(
+      GetRoundedRatio(counts.dismissals, total_counts));
+  features->set_avg_grant_rate(GetRoundedRatio(counts.grants, total_counts));
+  features->set_avg_ignore_rate(GetRoundedRatio(counts.ignores, total_counts));
+  features->set_prompts_count(BucketizeValue(total_counts));
+}
+
+std::unique_ptr<GeneratePredictionsRequest> GetPredictionRequestProto(
+    const PredictionRequestFeatures& entity) {
+  auto proto_request = std::make_unique<GeneratePredictionsRequest>();
+
+  ClientFeatures* client_features = proto_request->mutable_client_features();
+  client_features->set_platform(GetCurrentPlatformProto());
+  client_features->set_gesture(ConvertToProtoGesture(entity.gesture));
+  client_features->set_platform_enum(GetCurrentPlatformEnumProto());
+  client_features->set_gesture_enum(ConvertToProtoGestureEnum(entity.gesture));
+  FillInStatsFeatures(entity.all_permission_counts,
+                      client_features->mutable_client_stats());
+
+  PermissionFeatures* permission_features =
+      proto_request->mutable_permission_features()->Add();
+  FillInStatsFeatures(entity.requested_permission_counts,
+                      permission_features->mutable_permission_stats());
+
+  switch (entity.type) {
+    case RequestType::kNotifications:
+      permission_features->mutable_notification_permission()->Clear();
+      break;
+    case RequestType::kGeolocation:
+      permission_features->mutable_geolocation_permission()->Clear();
+      break;
+    default:
+      NOTREACHED()
+          << "CPSS only supports notifications and geolocation at the moment.";
+  }
+
+  return proto_request;
+}
+
+constexpr char kPlatform[] = "platform";
+constexpr char kGesture[] = "gesture";
+constexpr char kPlatformEnum[] = "platformEnum";
+constexpr char kGestureEnum[] = "gestureEnum";
+constexpr char kAvgDenyRate[] = "avgDenyRate";
+constexpr char kAvgGrantRate[] = "avgGrantRate";
+constexpr char kAvgDismissRate[] = "avgDismissRate";
+constexpr char kAvgIgnoreRate[] = "avgIgnoreRate";
+constexpr char kPromptsCount[] = "promptsCount";
+constexpr char kClientStats[] = "clientStats";
+constexpr char kClientFeatures[] = "clientFeatures";
+constexpr char kNotificationPermission[] = "notificationPermission";
+constexpr char kPermissionStats[] = "permissionStats";
+constexpr char kPermissionFeatures[] = "permissionFeatures";
+constexpr char kPrediction[] = "prediction";
+constexpr char kGrantLikelihood[] = "grantLikelihood";
+constexpr char kDiscretizedLikelihood[] = "discretizedLikelihood";
+
+std::string GeneratePredictionsRequestMessageToJson(
+    const GeneratePredictionsRequest& message) {
+  base::Value dict_message(base::Value::Type::DICTIONARY);
+
+  base::Value client_features(base::Value::Type::DICTIONARY);
+  client_features.SetKey(kPlatform, base::Value(ClientFeatures_Platform_Name(
+                                        message.client_features().platform())));
+  client_features.SetKey(kGesture, base::Value(ClientFeatures_Gesture_Name(
+                                       message.client_features().gesture())));
+  client_features.SetKey(kPlatformEnum,
+                         base::Value(ClientFeatures_PlatformEnum_Name(
+                             message.client_features().platform_enum())));
+  client_features.SetKey(kGestureEnum,
+                         base::Value(ClientFeatures_GestureEnum_Name(
+                             message.client_features().gesture_enum())));
+  base::Value client_stats(base::Value::Type::DICTIONARY);
+  client_stats.SetKey(
+      kAvgDenyRate,
+      base::Value(message.client_features().client_stats().avg_deny_rate()));
+  client_stats.SetKey(
+      kAvgGrantRate,
+      base::Value(message.client_features().client_stats().avg_grant_rate()));
+  client_stats.SetKey(
+      kAvgDismissRate,
+      base::Value(message.client_features().client_stats().avg_dismiss_rate()));
+  client_stats.SetKey(
+      kAvgIgnoreRate,
+      base::Value(message.client_features().client_stats().avg_ignore_rate()));
+  client_stats.SetKey(
+      kPromptsCount,
+      base::Value(message.client_features().client_stats().prompts_count()));
+  client_features.SetKey(kClientStats, std::move(client_stats));
+  dict_message.SetKey(kClientFeatures, std::move(client_features));
+
+  CHECK_EQ(false, message.permission_features().empty());
+
+  base::Value permission_features(base::Value::Type::LIST);
+  base::Value permission_feature_entry(base::Value::Type::DICTIONARY);
+  base::Value notification_features(base::Value::Type::DICTIONARY);
+  permission_feature_entry.SetKey(kNotificationPermission,
+                                  std::move(notification_features));
+  base::Value permission_stats(base::Value::Type::DICTIONARY);
+  permission_stats.SetKey(
+      kAvgDenyRate,
+      base::Value(
+          message.permission_features()[0].permission_stats().avg_deny_rate()));
+  permission_stats.SetKey(kAvgGrantRate,
+                          base::Value(message.permission_features()[0]
+                                          .permission_stats()
+                                          .avg_grant_rate()));
+  permission_stats.SetKey(kAvgDismissRate,
+                          base::Value(message.permission_features()[0]
+                                          .permission_stats()
+                                          .avg_dismiss_rate()));
+  permission_stats.SetKey(kAvgIgnoreRate,
+                          base::Value(message.permission_features()[0]
+                                          .permission_stats()
+                                          .avg_ignore_rate()));
+  permission_stats.SetKey(
+      kPromptsCount,
+      base::Value(
+          message.permission_features()[0].permission_stats().prompts_count()));
+  permission_feature_entry.SetKey(kPermissionStats,
+                                  std::move(permission_stats));
+  permission_features.Append(std::move(permission_feature_entry));
+  dict_message.SetKey(kPermissionFeatures, std::move(permission_features));
+
+  std::string message_str;
+  if (base::JSONWriter::Write(dict_message, &message_str))
+    return message_str;
+
+  return std::string();
+}
+
+std::unique_ptr<GeneratePredictionsResponse>
+GeneratePredictionsResponseJsonToMessage(std::string input) {
+  auto message = std::make_unique<GeneratePredictionsResponse>();
+
+  auto parsed_message = base::JSONReader::Read(input);
+  if (!parsed_message.has_value() || !parsed_message->is_dict())
+    return message;
+
+  auto* prediction_list = parsed_message->FindListKey(kPrediction);
+  if (!prediction_list || prediction_list->GetList().empty() ||
+      !prediction_list->GetList()[0].is_dict()) {
+    return message;
+  }
+
+  auto* likelihood_dict =
+      prediction_list->GetList()[0].FindDictKey(kGrantLikelihood);
+  if (!likelihood_dict)
+    return message;
+
+  auto* likelihood_str = likelihood_dict->FindStringKey(kDiscretizedLikelihood);
+  if (!likelihood_str)
+    return message;
+
+  PermissionPrediction_Likelihood_DiscretizedLikelihood likelihood;
+  if (!PermissionPrediction_Likelihood_DiscretizedLikelihood_Parse(
+          *likelihood_str, &likelihood)) {
+    return message;
+  }
+
+  // Create entry and set likelihood.
+  auto* prediction = message->mutable_prediction()->Add();
+  prediction->mutable_grant_likelihood()->set_discretized_likelihood(
+      likelihood);
+
+  return message;
+}
+
+}  // namespace permissions
diff --git a/components/permissions/prediction_service/prediction_common.h b/components/permissions/prediction_service/prediction_common.h
new file mode 100644
index 0000000..a5e7dcc
--- /dev/null
+++ b/components/permissions/prediction_service/prediction_common.h
@@ -0,0 +1,74 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_COMMON_H_
+#define COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_COMMON_H_
+
+#include "components/permissions/prediction_service/prediction_request_features.h"
+#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
+
+namespace permissions {
+
+// TODO(andypaicu): when available, replace with actual URL.
+constexpr char kDefaultPredictionServiceUrl[] =
+    "https://webpermissionpredictions.googleapis.com/v1:generatePredictions";
+
+// A command line switch to override the default service url.
+constexpr char kDefaultPredictionServiceUrlSwitchKey[] =
+    "permission-predictions-service-url";
+
+constexpr float kRoundToMultiplesOf = 0.1f;
+
+constexpr int kCountBuckets[] = {20, 15, 12, 10, 9, 8, 7, 6, 5, 4};
+
+// Thresholds of the likelihood that triggers the CPSS prompts.
+constexpr float kNotificationPredictionsThreshold = 0.83;
+constexpr float kGeolocationPredictionsThreshold = 0.87;
+
+// Returns the ratio rounded to the nearest 10%. It returns a value between 0
+// and 1 in steps of 0.1
+float GetRoundedRatio(int numerator, int denominator);
+
+// This method normalises the value returned by GetRoundedRatio(int, int) for
+// sending it to ukm. It returns a value between 0 and 100 in steps of 10.
+int GetRoundedRatioForUkm(int numerator, int denominator);
+
+// Returns the appropriate bucket for `count`.
+int BucketizeValue(int count);
+
+// Get the current platform for proto message purposes.
+ClientFeatures_Platform GetCurrentPlatformProto();
+
+// Get the current platform for proto message purposes.
+ClientFeatures_PlatformEnum GetCurrentPlatformEnumProto();
+
+// Convert PermissionRequestGestureType to ClientFeatures_Gesture.
+ClientFeatures_Gesture ConvertToProtoGesture(
+    const permissions::PermissionRequestGestureType type);
+
+// Convert PermissionRequestGestureType to ClientFeatures_GestureEnum.
+ClientFeatures_GestureEnum ConvertToProtoGestureEnum(
+    const permissions::PermissionRequestGestureType type);
+
+// Fill in the values in StatsFeature using the values in
+// PredictionRequestFeatures::ActionCounts
+void FillInStatsFeatures(const PredictionRequestFeatures::ActionCounts& counts,
+                         StatsFeatures* features);
+
+std::unique_ptr<GeneratePredictionsRequest> GetPredictionRequestProto(
+    const PredictionRequestFeatures& entity);
+
+// Convert a GeneratePredictionsRequest from Message to Json String.
+// Returns empty string if the conversion is unsuccessful.
+std::string GeneratePredictionsRequestMessageToJson(
+    const GeneratePredictionsRequest&);
+
+// Convert a GeneratePredictionsResponse from Json String to Message.
+// Returns nullptr if the conversion is unsuccessful.
+std::unique_ptr<GeneratePredictionsResponse>
+    GeneratePredictionsResponseJsonToMessage(std::string);
+
+}  // namespace permissions
+
+#endif  // COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_COMMON_H_
diff --git a/components/permissions/prediction_service/prediction_model_executor.cc b/components/permissions/prediction_service/prediction_model_executor.cc
new file mode 100644
index 0000000..8cea2a3
--- /dev/null
+++ b/components/permissions/prediction_service/prediction_model_executor.cc
@@ -0,0 +1,92 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/permissions/prediction_service/prediction_model_executor.h"
+
+#include "base/notreached.h"
+#include "components/permissions/prediction_service/prediction_common.h"
+#include "components/permissions/prediction_service/prediction_request_features.h"
+#include "third_party/tflite_support/src/tensorflow_lite_support/cc/task/core/task_utils.h"
+
+namespace permissions {
+
+PredictionModelExecutor::PredictionModelExecutor() = default;
+PredictionModelExecutor::~PredictionModelExecutor() = default;
+
+absl::Status PredictionModelExecutor::Preprocess(
+    const std::vector<TfLiteTensor*>& input_tensors,
+    const GeneratePredictionsRequest& input) {
+  switch (input.permission_features()[0].permission_type_case()) {
+    case PermissionFeatures::kNotificationPermission:
+      request_type_ = RequestType::kNotifications;
+      break;
+    case PermissionFeatures::kGeolocationPermission:
+      request_type_ = RequestType::kGeolocation;
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  tflite::task::core::PopulateTensor<float>(
+      input.client_features().client_stats().avg_deny_rate(), input_tensors[0]);
+  tflite::task::core::PopulateTensor<float>(
+      input.client_features().client_stats().avg_dismiss_rate(),
+      input_tensors[1]);
+  tflite::task::core::PopulateTensor<float>(
+      input.client_features().client_stats().avg_grant_rate(),
+      input_tensors[2]);
+  tflite::task::core::PopulateTensor<float>(
+      input.client_features().client_stats().avg_ignore_rate(),
+      input_tensors[3]);
+  tflite::task::core::PopulateTensor<float>(
+      input.permission_features()[0].permission_stats().avg_deny_rate(),
+      input_tensors[4]);
+  tflite::task::core::PopulateTensor<float>(
+      input.permission_features()[0].permission_stats().avg_dismiss_rate(),
+      input_tensors[5]);
+  tflite::task::core::PopulateTensor<float>(
+      input.permission_features()[0].permission_stats().avg_grant_rate(),
+      input_tensors[6]);
+  tflite::task::core::PopulateTensor<float>(
+      input.permission_features()[0].permission_stats().avg_ignore_rate(),
+      input_tensors[7]);
+  tflite::task::core::PopulateTensor<int64_t>(
+      static_cast<int64_t>(
+          input.permission_features()[0].permission_stats().prompts_count()),
+      input_tensors[8]);
+  tflite::task::core::PopulateTensor<int64_t>(
+      static_cast<int64_t>(
+          input.client_features().client_stats().prompts_count()),
+      input_tensors[9]);
+  tflite::task::core::PopulateTensor<int64_t>(
+      static_cast<int64_t>(input.client_features().gesture_enum()),
+      input_tensors[10]);
+  tflite::task::core::PopulateTensor<int64_t>(
+      static_cast<int64_t>(input.client_features().platform_enum()),
+      input_tensors[11]);
+
+  return absl::OkStatus();
+}
+GeneratePredictionsResponse PredictionModelExecutor::Postprocess(
+    const std::vector<const TfLiteTensor*>& output_tensors) {
+  DCHECK(request_type_ == RequestType::kNotifications ||
+         request_type_ == RequestType::kGeolocation);
+  std::vector<float> data;
+  tflite::task::core::PopulateVector<float>(output_tensors[0], &data);
+  GeneratePredictionsResponse response;
+  float threshold = request_type_ == RequestType::kNotifications
+                        ? kNotificationPredictionsThreshold
+                        : kGeolocationPredictionsThreshold;
+  response.mutable_prediction()
+      ->Add()
+      ->mutable_grant_likelihood()
+      ->set_discretized_likelihood(
+          data[1] >= threshold
+              ? PermissionPrediction_Likelihood_DiscretizedLikelihood_VERY_UNLIKELY
+              : PermissionPrediction_Likelihood_DiscretizedLikelihood_DISCRETIZED_LIKELIHOOD_UNSPECIFIED);
+
+  return response;
+}
+
+}  // namespace permissions
diff --git a/components/permissions/prediction_service/prediction_model_executor.h b/components/permissions/prediction_service/prediction_model_executor.h
new file mode 100644
index 0000000..eff3f35
--- /dev/null
+++ b/components/permissions/prediction_service/prediction_model_executor.h
@@ -0,0 +1,39 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_MODEL_EXECUTOR_H_
+#define COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_MODEL_EXECUTOR_H_
+
+#include <vector>
+
+#include "components/optimization_guide/core/base_model_executor.h"
+#include "components/permissions/prediction_service/prediction_request_features.h"
+#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
+
+namespace permissions {
+
+class PredictionModelExecutor : public optimization_guide::BaseModelExecutor<
+                                    GeneratePredictionsResponse,
+                                    const GeneratePredictionsRequest&> {
+ public:
+  PredictionModelExecutor();
+  ~PredictionModelExecutor() override;
+
+  PredictionModelExecutor(const PredictionModelExecutor&) = delete;
+  PredictionModelExecutor& operator=(const PredictionModelExecutor&) = delete;
+
+ protected:
+  // optimization_guide::BaseModelExecutor:
+  absl::Status Preprocess(const std::vector<TfLiteTensor*>& input_tensors,
+                          const GeneratePredictionsRequest& input) override;
+
+  GeneratePredictionsResponse Postprocess(
+      const std::vector<const TfLiteTensor*>& output_tensors) override;
+
+ private:
+  RequestType request_type_;
+};
+
+}  // namespace permissions
+#endif  // COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_MODEL_EXECUTOR_H_
diff --git a/components/permissions/prediction_service/prediction_model_handler.cc b/components/permissions/prediction_service/prediction_model_handler.cc
new file mode 100644
index 0000000..0dbbc3f
--- /dev/null
+++ b/components/permissions/prediction_service/prediction_model_handler.cc
@@ -0,0 +1,35 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/permissions/prediction_service/prediction_model_handler.h"
+
+#include <memory>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/permissions/prediction_service/prediction_model_executor.h"
+#include "components/permissions/prediction_service/prediction_request_features.h"
+#include "content/public/browser/browser_context.h"
+
+namespace permissions {
+
+PredictionModelHandler::PredictionModelHandler(
+    optimization_guide::OptimizationGuideModelProvider* model_provider,
+    scoped_refptr<base::SequencedTaskRunner> background_task_runner)
+    : ModelHandler<GeneratePredictionsResponse,
+                   const GeneratePredictionsRequest&>(
+          model_provider,
+          background_task_runner,
+          std::make_unique<PredictionModelExecutor>(),
+          optimization_guide::proto::OptimizationTarget::
+              OPTIMIZATION_TARGET_NOTIFICATION_PERMISSION_PREDICTIONS,
+          absl::nullopt) {}
+
+}  // namespace permissions
diff --git a/components/permissions/prediction_service/prediction_model_handler.h b/components/permissions/prediction_service/prediction_model_handler.h
new file mode 100644
index 0000000..ad57b6a
--- /dev/null
+++ b/components/permissions/prediction_service/prediction_model_handler.h
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_MODEL_HANDLER_H_
+#define COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_MODEL_HANDLER_H_
+
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/optimization_guide/core/model_handler.h"
+#include "components/permissions/prediction_service/prediction_model_executor.h"
+#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
+
+namespace permissions {
+class PredictionModelHandler : public KeyedService,
+                               public optimization_guide::ModelHandler<
+                                   GeneratePredictionsResponse,
+                                   const GeneratePredictionsRequest&> {
+ public:
+  explicit PredictionModelHandler(
+      optimization_guide::OptimizationGuideModelProvider* model_provider,
+      scoped_refptr<base::SequencedTaskRunner> background_task_runner);
+
+  ~PredictionModelHandler() override = default;
+  PredictionModelHandler(const PredictionModelHandler&) = delete;
+  PredictionModelHandler& operator=(const PredictionModelHandler&) = delete;
+};
+
+}  // namespace permissions
+#endif  // COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_MODEL_HANDLER_H_
diff --git a/components/permissions/prediction_service/prediction_service.cc b/components/permissions/prediction_service/prediction_service.cc
index 4bd63e9..96d36e1 100644
--- a/components/permissions/prediction_service/prediction_service.cc
+++ b/components/permissions/prediction_service/prediction_service.cc
@@ -13,8 +13,8 @@
 #include "base/no_destructor.h"
 #include "base/notreached.h"
 #include "components/permissions/features.h"
+#include "components/permissions/prediction_service/prediction_common.h"
 #include "components/permissions/prediction_service/prediction_request_features.h"
-#include "components/permissions/prediction_service/prediction_service_common.h"
 #include "components/permissions/prediction_service/prediction_service_messages.pb.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
@@ -30,45 +30,6 @@
 
 constexpr base::TimeDelta kURLLookupTimeout = base::Seconds(2);
 
-constexpr float kRoundToMultiplesOf = 0.1f;
-
-constexpr int kCountBuckets[] = {20, 15, 12, 10, 9, 8, 7, 6, 5, 4};
-
-permissions::ClientFeatures_Gesture ConvertToProtoGesture(
-    const permissions::PermissionRequestGestureType type) {
-  switch (type) {
-    case permissions::PermissionRequestGestureType::GESTURE:
-      return permissions::ClientFeatures_Gesture_GESTURE;
-    case permissions::PermissionRequestGestureType::NO_GESTURE:
-      return permissions::ClientFeatures_Gesture_NO_GESTURE;
-    case permissions::PermissionRequestGestureType::UNKNOWN:
-      return permissions::ClientFeatures_Gesture_GESTURE_UNSPECIFIED;
-    case permissions::PermissionRequestGestureType::NUM:
-      break;
-  }
-
-  NOTREACHED();
-  return permissions::ClientFeatures_Gesture_GESTURE_UNSPECIFIED;
-}
-
-void FillInStatsFeatures(
-    const permissions::PredictionRequestFeatures::ActionCounts& counts,
-    permissions::StatsFeatures* features) {
-  using PredictionService = permissions::PredictionService;
-  int total_counts = counts.total();
-
-  // Round to only 2 decimal places to help prevent fingerprinting.
-  features->set_avg_deny_rate(
-      PredictionService::GetRoundedRatio(counts.denies, total_counts));
-  features->set_avg_dismiss_rate(
-      PredictionService::GetRoundedRatio(counts.dismissals, total_counts));
-  features->set_avg_grant_rate(
-      PredictionService::GetRoundedRatio(counts.grants, total_counts));
-  features->set_avg_ignore_rate(
-      PredictionService::GetRoundedRatio(counts.ignores, total_counts));
-  features->set_prompts_count(PredictionService::BucketizeValue(total_counts));
-}
-
 net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() {
   return net::DefineNetworkTrafficAnnotation("permission_predictions", R"(
     semantics {
@@ -180,37 +141,6 @@
   return request;
 }
 
-std::unique_ptr<GeneratePredictionsRequest>
-PredictionService::GetPredictionRequestProto(
-    const PredictionRequestFeatures& entity) {
-  auto proto_request = std::make_unique<GeneratePredictionsRequest>();
-
-  ClientFeatures* client_features = proto_request->mutable_client_features();
-  client_features->set_platform(GetCurrentPlatformProto());
-  client_features->set_gesture(ConvertToProtoGesture(entity.gesture));
-  FillInStatsFeatures(entity.all_permission_counts,
-                      client_features->mutable_client_stats());
-
-  PermissionFeatures* permission_features =
-      proto_request->mutable_permission_features()->Add();
-  FillInStatsFeatures(entity.requested_permission_counts,
-                      permission_features->mutable_permission_stats());
-
-  switch (entity.type) {
-    case RequestType::kNotifications:
-      permission_features->mutable_notification_permission()->Clear();
-      break;
-    case RequestType::kGeolocation:
-      permission_features->mutable_geolocation_permission()->Clear();
-      break;
-    default:
-      NOTREACHED()
-          << "CPSS only supports notifications and geolocation at the moment.";
-  }
-
-  return proto_request;
-}
-
 void PredictionService::SendRequestInternal(
     std::unique_ptr<network::ResourceRequest> request,
     const std::string& request_data,
@@ -245,10 +175,15 @@
           CreatePredictionsResponse(loader, response_body.get());
 
       if (request.second) {
+        absl::optional<GeneratePredictionsResponse> response;
+        if (prediction_response == nullptr) {
+          response = absl::nullopt;
+        } else {
+          response = *prediction_response;
+        }
         bool lookup_success = prediction_response != nullptr;
         std::move(request.second)
-            .Run(lookup_success, false /* Response from cache */,
-                 std::move(prediction_response));
+            .Run(lookup_success, /*Response from cache=*/false, response);
       }
 
       pending_requests_.erase(request.first);
@@ -281,26 +216,4 @@
   return predictions_response;
 }
 
-// static
-float PredictionService::GetRoundedRatio(int numerator, int denominator) {
-  if (denominator == 0)
-    return 0;
-  return roundf(numerator / kRoundToMultiplesOf / denominator) *
-         kRoundToMultiplesOf;
-}
-
-// static
-int PredictionService::GetRoundedRatioForUkm(int numerator, int denominator) {
-  return GetRoundedRatio(numerator, denominator) * 100;
-}
-
-// static
-int PredictionService::BucketizeValue(int count) {
-  for (const int bucket : kCountBuckets) {
-    if (count >= bucket)
-      return bucket;
-  }
-  return 0;
-}
-
 }  // namespace permissions
diff --git a/components/permissions/prediction_service/prediction_service.h b/components/permissions/prediction_service/prediction_service.h
index c8a4058..8717805 100644
--- a/components/permissions/prediction_service/prediction_service.h
+++ b/components/permissions/prediction_service/prediction_service.h
@@ -11,7 +11,6 @@
 #include "base/callback.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/permissions/permission_request_enums.h"
-#include "components/permissions/prediction_service/prediction_request_features.h"
 #include "components/permissions/prediction_service/prediction_service_base.h"
 #include "components/permissions/prediction_service/prediction_service_messages.pb.h"
 #include "services/network/public/cpp/resource_request.h"
@@ -51,22 +50,10 @@
     recalculate_service_url_every_time = true;
   }
 
-  // Returns the ratio rounded to the nearest 10%. It returns a value between 0
-  // and 1 in steps of 0.1
-  static float GetRoundedRatio(int numerator, int denominator);
-
-  // This method normalises the value returned by GetRoundedRatio(int, int) for
-  // sending it to ukm. It returns a value between 0 and 100 in steps of 10.
-  static int GetRoundedRatioForUkm(int numerator, int denominator);
-
-  // Returns the appropriate bucket for `count`.
-  static int BucketizeValue(int count);
-
  private:
   static const GURL GetPredictionServiceUrl(bool recalculate_for_testing);
   std::unique_ptr<network::ResourceRequest> GetResourceRequest();
-  std::unique_ptr<GeneratePredictionsRequest> GetPredictionRequestProto(
-      const PredictionRequestFeatures& entity);
+
   void SendRequestInternal(std::unique_ptr<network::ResourceRequest> request,
                            const std::string& request_data,
                            const PredictionRequestFeatures& entity,
diff --git a/components/permissions/prediction_service/prediction_service_base.h b/components/permissions/prediction_service/prediction_service_base.h
index 4257241..4c28b6b 100644
--- a/components/permissions/prediction_service/prediction_service_base.h
+++ b/components/permissions/prediction_service/prediction_service_base.h
@@ -28,10 +28,10 @@
       base::OnceCallback<void(std::unique_ptr<GeneratePredictionsRequest>,
                               std::string)>;  // Access token.
 
-  using LookupResponseCallback =
-      base::OnceCallback<void(bool,  // Lookup successful.
-                              bool,  // Response from cache.
-                              std::unique_ptr<GeneratePredictionsResponse>)>;
+  using LookupResponseCallback = base::OnceCallback<void(
+      bool,  // Lookup successful.
+      bool,  // Response from cache.
+      const absl::optional<GeneratePredictionsResponse>&)>;
 
   virtual void StartLookup(const PredictionRequestFeatures& entity,
                            LookupRequestCallback request_callback,
diff --git a/components/permissions/prediction_service/prediction_service_common.cc b/components/permissions/prediction_service/prediction_service_common.cc
deleted file mode 100644
index c295aaa..0000000
--- a/components/permissions/prediction_service/prediction_service_common.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/permissions/prediction_service/prediction_service_common.h"
-
-#include "base/json/json_reader.h"
-#include "base/json/json_writer.h"
-#include "base/values.h"
-#include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
-
-namespace permissions {
-ClientFeatures_Platform GetCurrentPlatformProto() {
-#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
-    defined(OS_MAC)
-  return permissions::ClientFeatures_Platform_PLATFORM_DESKTOP;
-#elif defined(OS_ANDROID) || defined(OS_FUCHSIA)
-  return permissions::ClientFeatures_Platform_PLATFORM_MOBILE;
-#else
-  return permissions::ClientFeatures_Platform_PLATFORM_UNSPECIFIED;
-#endif
-}
-
-constexpr char kPlatform[] = "platform";
-constexpr char kGesture[] = "gesture";
-constexpr char kAvgDenyRate[] = "avgDenyRate";
-constexpr char kAvgGrantRate[] = "avgGrantRate";
-constexpr char kAvgDismissRate[] = "avgDismissRate";
-constexpr char kAvgIgnoreRate[] = "avgIgnoreRate";
-constexpr char kPromptsCount[] = "promptsCount";
-constexpr char kClientStats[] = "clientStats";
-constexpr char kClientFeatures[] = "clientFeatures";
-constexpr char kNotificationPermission[] = "notificationPermission";
-constexpr char kPermissionStats[] = "permissionStats";
-constexpr char kPermissionFeatures[] = "permissionFeatures";
-constexpr char kPrediction[] = "prediction";
-constexpr char kGrantLikelihood[] = "grantLikelihood";
-constexpr char kDiscretizedLikelihood[] = "discretizedLikelihood";
-
-std::string GeneratePredictionsRequestMessageToJson(
-    const GeneratePredictionsRequest& message) {
-  base::Value dict_message(base::Value::Type::DICTIONARY);
-
-  base::Value client_features(base::Value::Type::DICTIONARY);
-  client_features.SetKey(kPlatform, base::Value(ClientFeatures_Platform_Name(
-                                        message.client_features().platform())));
-  client_features.SetKey(kGesture, base::Value(ClientFeatures_Gesture_Name(
-                                       message.client_features().gesture())));
-  base::Value client_stats(base::Value::Type::DICTIONARY);
-  client_stats.SetKey(
-      kAvgDenyRate,
-      base::Value(message.client_features().client_stats().avg_deny_rate()));
-  client_stats.SetKey(
-      kAvgGrantRate,
-      base::Value(message.client_features().client_stats().avg_grant_rate()));
-  client_stats.SetKey(
-      kAvgDismissRate,
-      base::Value(message.client_features().client_stats().avg_dismiss_rate()));
-  client_stats.SetKey(
-      kAvgIgnoreRate,
-      base::Value(message.client_features().client_stats().avg_ignore_rate()));
-  client_stats.SetKey(
-      kPromptsCount,
-      base::Value(message.client_features().client_stats().prompts_count()));
-  client_features.SetKey(kClientStats, std::move(client_stats));
-  dict_message.SetKey(kClientFeatures, std::move(client_features));
-
-  CHECK_EQ(false, message.permission_features().empty());
-
-  base::Value permission_features(base::Value::Type::LIST);
-  base::Value permission_feature_entry(base::Value::Type::DICTIONARY);
-  base::Value notification_features(base::Value::Type::DICTIONARY);
-  permission_feature_entry.SetKey(kNotificationPermission,
-                                  std::move(notification_features));
-  base::Value permission_stats(base::Value::Type::DICTIONARY);
-  permission_stats.SetKey(
-      kAvgDenyRate,
-      base::Value(
-          message.permission_features()[0].permission_stats().avg_deny_rate()));
-  permission_stats.SetKey(kAvgGrantRate,
-                          base::Value(message.permission_features()[0]
-                                          .permission_stats()
-                                          .avg_grant_rate()));
-  permission_stats.SetKey(kAvgDismissRate,
-                          base::Value(message.permission_features()[0]
-                                          .permission_stats()
-                                          .avg_dismiss_rate()));
-  permission_stats.SetKey(kAvgIgnoreRate,
-                          base::Value(message.permission_features()[0]
-                                          .permission_stats()
-                                          .avg_ignore_rate()));
-  permission_stats.SetKey(
-      kPromptsCount,
-      base::Value(
-          message.permission_features()[0].permission_stats().prompts_count()));
-  permission_feature_entry.SetKey(kPermissionStats,
-                                  std::move(permission_stats));
-  permission_features.Append(std::move(permission_feature_entry));
-  dict_message.SetKey(kPermissionFeatures, std::move(permission_features));
-
-  std::string message_str;
-  if (base::JSONWriter::Write(dict_message, &message_str))
-    return message_str;
-
-  return std::string();
-}
-
-std::unique_ptr<GeneratePredictionsResponse>
-GeneratePredictionsResponseJsonToMessage(std::string input) {
-  auto message = std::make_unique<GeneratePredictionsResponse>();
-
-  auto parsed_message = base::JSONReader::Read(input);
-  if (!parsed_message.has_value() || !parsed_message->is_dict())
-    return message;
-
-  auto* prediction_list = parsed_message->FindListKey(kPrediction);
-  if (!prediction_list || prediction_list->GetList().empty() ||
-      !prediction_list->GetList()[0].is_dict()) {
-    return message;
-  }
-
-  auto* likelihood_dict =
-      prediction_list->GetList()[0].FindDictKey(kGrantLikelihood);
-  if (!likelihood_dict)
-    return message;
-
-  auto* likelihood_str = likelihood_dict->FindStringKey(kDiscretizedLikelihood);
-  if (!likelihood_str)
-    return message;
-
-  PermissionPrediction_Likelihood_DiscretizedLikelihood likelihood;
-  if (!PermissionPrediction_Likelihood_DiscretizedLikelihood_Parse(
-          *likelihood_str, &likelihood)) {
-    return message;
-  }
-
-  // Create entry and set likelihood.
-  auto* prediction = message->mutable_prediction()->Add();
-  prediction->mutable_grant_likelihood()->set_discretized_likelihood(
-      likelihood);
-
-  return message;
-}
-
-}  // namespace permissions
diff --git a/components/permissions/prediction_service/prediction_service_common.h b/components/permissions/prediction_service/prediction_service_common.h
deleted file mode 100644
index 5e7453d..0000000
--- a/components/permissions/prediction_service/prediction_service_common.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_COMMON_H_
-#define COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_COMMON_H_
-
-#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
-
-namespace permissions {
-
-// TODO(andypaicu): when available, replace with actual URL.
-constexpr char kDefaultPredictionServiceUrl[] =
-    "https://webpermissionpredictions.googleapis.com/v1:generatePredictions";
-
-// A command line switch to override the default service url.
-constexpr char kDefaultPredictionServiceUrlSwitchKey[] =
-    "permission-predictions-service-url";
-
-// Get the current platform for proto message purposes.
-ClientFeatures_Platform GetCurrentPlatformProto();
-
-// Convert a GeneratePredictionsRequest from Message to Json String.
-// Returns empty string if the conversion is unsuccessful.
-std::string GeneratePredictionsRequestMessageToJson(
-    const GeneratePredictionsRequest&);
-
-// Convert a GeneratePredictionsResponse from Json String to Message.
-// Returns nullptr if the conversion is unsuccessful.
-std::unique_ptr<GeneratePredictionsResponse>
-    GeneratePredictionsResponseJsonToMessage(std::string);
-
-}  // namespace permissions
-
-#endif  // COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_COMMON_H_
diff --git a/components/permissions/prediction_service/prediction_service_messages.proto b/components/permissions/prediction_service/prediction_service_messages.proto
index 22ebe67..6c0178a 100644
--- a/components/permissions/prediction_service/prediction_service_messages.proto
+++ b/components/permissions/prediction_service/prediction_service_messages.proto
@@ -55,6 +55,21 @@
   // The type of gesture performed by the user on the page before the permission
   // prompt was shown.
   optional Gesture gesture = 3;
+
+  enum GestureEnum {
+    GESTURE_V2 = 0;
+    GESTURE_UNSPECIFIED_V2 = 1;
+  }
+
+  optional GestureEnum gesture_enum = 4;
+
+  enum PlatformEnum {
+    PLATFORM_MOBILE_V2 = 0;
+    PLATFORM_DESKTOP_V2 = 1;
+    PLATFORM_UNSPECIFIED_V2 = 3;
+  }
+
+  optional PlatformEnum platform_enum = 5;
 }
 
 // Features related to a specific permission type.
diff --git a/components/permissions/prediction_service/prediction_service_unittest.cc b/components/permissions/prediction_service/prediction_service_unittest.cc
index 0b296d5..94f49a1 100644
--- a/components/permissions/prediction_service/prediction_service_unittest.cc
+++ b/components/permissions/prediction_service/prediction_service_unittest.cc
@@ -16,8 +16,8 @@
 #include "base/time/time.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_request_enums.h"
+#include "components/permissions/prediction_service/prediction_common.h"
 #include "components/permissions/prediction_service/prediction_request_features.h"
-#include "components/permissions/prediction_service/prediction_service_common.h"
 #include "components/permissions/prediction_service/prediction_service_messages.pb.h"
 #include "components/permissions/request_type.h"
 #include "google/protobuf/message_lite.h"
@@ -103,8 +103,12 @@
       ->set_prompts_count(0);
   kRequestAllCountsZero.mutable_client_features()->set_platform(
       permissions::GetCurrentPlatformProto());
+  kRequestAllCountsZero.mutable_client_features()->set_platform_enum(
+      permissions::GetCurrentPlatformEnumProto());
   kRequestAllCountsZero.mutable_client_features()->set_gesture(
       permissions::ClientFeatures_Gesture_GESTURE);
+  kRequestAllCountsZero.mutable_client_features()->set_gesture_enum(
+      permissions::ClientFeatures_GestureEnum_GESTURE_V2);
   kRequestAllCountsZero.mutable_permission_features()->Clear();
   auto* permission_feature =
       kRequestAllCountsZero.mutable_permission_features()->Add();
@@ -132,8 +136,12 @@
       ->set_prompts_count(20);
   kRequestRoundedCounts.mutable_client_features()->set_platform(
       permissions::GetCurrentPlatformProto());
+  kRequestRoundedCounts.mutable_client_features()->set_platform_enum(
+      permissions::GetCurrentPlatformEnumProto());
   kRequestRoundedCounts.mutable_client_features()->set_gesture(
       permissions::ClientFeatures_Gesture_NO_GESTURE);
+  kRequestRoundedCounts.mutable_client_features()->set_gesture_enum(
+      permissions::ClientFeatures_GestureEnum_GESTURE_UNSPECIFIED_V2);
   kRequestRoundedCounts.mutable_permission_features()->Clear();
   permission_feature =
       kRequestRoundedCounts.mutable_permission_features()->Add();
@@ -161,8 +169,12 @@
       ->set_prompts_count(20);
   kRequestEqualCountsTotal20.mutable_client_features()->set_platform(
       permissions::GetCurrentPlatformProto());
+  kRequestEqualCountsTotal20.mutable_client_features()->set_platform_enum(
+      permissions::GetCurrentPlatformEnumProto());
   kRequestEqualCountsTotal20.mutable_client_features()->set_gesture(
       permissions::ClientFeatures_Gesture_GESTURE);
+  kRequestEqualCountsTotal20.mutable_client_features()->set_gesture_enum(
+      permissions::ClientFeatures_GestureEnum_GESTURE_V2);
   kRequestEqualCountsTotal20.mutable_permission_features()->Clear();
   permission_feature =
       kRequestEqualCountsTotal20.mutable_permission_features()->Add();
@@ -190,8 +202,12 @@
       ->set_prompts_count(20);
   kRequestDifferentCounts.mutable_client_features()->set_platform(
       permissions::GetCurrentPlatformProto());
+  kRequestDifferentCounts.mutable_client_features()->set_platform_enum(
+      permissions::GetCurrentPlatformEnumProto());
   kRequestDifferentCounts.mutable_client_features()->set_gesture(
       permissions::ClientFeatures_Gesture_NO_GESTURE);
+  kRequestDifferentCounts.mutable_client_features()->set_gesture_enum(
+      permissions::ClientFeatures_GestureEnum_GESTURE_UNSPECIFIED_V2);
   kRequestDifferentCounts.mutable_permission_features()->Clear();
   permission_feature =
       kRequestDifferentCounts.mutable_permission_features()->Add();
@@ -277,11 +293,12 @@
     EXPECT_EQ(std::string(), access_token);
   }
 
-  void ResponseCallback(base::RunLoop* response_loop,
-                        bool lookup_successful,
-                        bool response_from_cache,
-                        std::unique_ptr<GeneratePredictionsResponse> response) {
-    received_responses_.emplace_back(std::move(response));
+  void ResponseCallback(
+      base::RunLoop* response_loop,
+      bool lookup_successful,
+      bool response_from_cache,
+      const absl::optional<GeneratePredictionsResponse>& response) {
+    received_responses_.emplace_back(response);
     if (response_loop)
       response_loop->Quit();
 
@@ -291,7 +308,7 @@
 
  protected:
   std::vector<std::unique_ptr<GeneratePredictionsRequest>> received_requests_;
-  std::vector<std::unique_ptr<GeneratePredictionsResponse>> received_responses_;
+  std::vector<absl::optional<GeneratePredictionsResponse>> received_responses_;
   std::unique_ptr<PredictionService> prediction_service_;
 
   // Different paths to simulate different server behaviours.
@@ -497,7 +514,7 @@
   response_loop.Run();
 
   EXPECT_EQ(2u, received_responses_.size());
-  EXPECT_TRUE(received_responses_[1].get() != nullptr);
+  EXPECT_TRUE(received_responses_[1]);
   EXPECT_EQ(kResponseLikely.SerializeAsString(),
             received_responses_[1]->SerializeAsString());
   EXPECT_EQ(0u, prediction_service_->pending_requests_for_testing().size());
@@ -524,13 +541,16 @@
   std::string kPlatformName =
       ClientFeatures_Platform_Name(permissions::GetCurrentPlatformProto());
 
+  std::string kPlatformEnumName = ClientFeatures_PlatformEnum_Name(
+      permissions::GetCurrentPlatformEnumProto());
+
   std::string expected_round_counts =
       "{\"clientFeatures\":{\"clientStats\":{\"avgDenyRate\":0."
       "20000000298023224,\"avgDismissRate\":0.20000000298023224,"
       "\"avgGrantRate\":0.30000001192092896,\"avgIgnoreRate\":0."
       "20000000298023224,\"promptsCount\":20},\"gesture\":\"NO_GESTURE\","
-      "\"platform\":\"" +
-      kPlatformName +
+      "\"gestureEnum\":\"GESTURE_UNSPECIFIED_V2\",\"platform\":\"" +
+      kPlatformName + "\",\"platformEnum\":\"" + kPlatformEnumName +
       "\"},\"permissionFeatures\":[{\"notificationPermission\":{},"
       "\"permissionStats\":{\"avgDenyRate\":0.20000000298023224,"
       "\"avgDismissRate\":0.20000000298023224,\"avgGrantRate\":0."
@@ -542,8 +562,8 @@
       "30000001192092896,\"avgDismissRate\":0.30000001192092896,"
       "\"avgGrantRate\":0.30000001192092896,\"avgIgnoreRate\":0."
       "30000001192092896,\"promptsCount\":20},\"gesture\":\"GESTURE\","
-      "\"platform\":\"" +
-      kPlatformName +
+      "\"gestureEnum\":\"GESTURE_V2\",\"platform\":\"" +
+      kPlatformName + "\",\"platformEnum\":\"" + kPlatformEnumName +
       "\"},\"permissionFeatures\":[{\"notificationPermission\":{},"
       "\"permissionStats\":{\"avgDenyRate\":0.30000001192092896,"
       "\"avgDismissRate\":0.30000001192092896,\"avgGrantRate\":0."
@@ -555,8 +575,8 @@
       "30000001192092896,\"avgDismissRate\":0.30000001192092896,"
       "\"avgGrantRate\":0.30000001192092896,\"avgIgnoreRate\":0."
       "30000001192092896,\"promptsCount\":20},\"gesture\":\"NO_GESTURE\","
-      "\"platform\":\"" +
-      kPlatformName +
+      "\"gestureEnum\":\"GESTURE_UNSPECIFIED_V2\",\"platform\":\"" +
+      kPlatformName + "\",\"platformEnum\":\"" + kPlatformEnumName +
       "\"},\"permissionFeatures\":[{\"notificationPermission\":{},"
       "\"permissionStats\":{\"avgDenyRate\":0.0,\"avgDismissRate\":0.0,"
       "\"avgGrantRate\":0.0,\"avgIgnoreRate\":0.0,\"promptsCount\":0}}]}";
@@ -564,8 +584,9 @@
   std::string expected_zero_counts =
       "{\"clientFeatures\":{\"clientStats\":{\"avgDenyRate\":0.0,"
       "\"avgDismissRate\":0.0,\"avgGrantRate\":0.0,\"avgIgnoreRate\":0.0,"
-      "\"promptsCount\":0},\"gesture\":\"GESTURE\",\"platform\":\"" +
-      kPlatformName +
+      "\"promptsCount\":0},\"gesture\":\"GESTURE\","
+      "\"gestureEnum\":\"GESTURE_V2\",\"platform\":\"" +
+      kPlatformName + "\",\"platformEnum\":\"" + kPlatformEnumName +
       "\"},\"permissionFeatures\":[{\"notificationPermission\":{},"
       "\"permissionStats\":{\"avgDenyRate\":0.0,\"avgDismissRate\":0.0,"
       "\"avgGrantRate\":0.0,\"avgIgnoreRate\":0.0,\"promptsCount\":0}}]}";
diff --git a/components/policy/resources/policy_templates_ja.xtb b/components/policy/resources/policy_templates_ja.xtb
index 5dfca2b..9b1a73f8 100644
--- a/components/policy/resources/policy_templates_ja.xtb
+++ b/components/policy/resources/policy_templates_ja.xtb
@@ -2522,6 +2522,7 @@
 <translation id="3964298692570794635">指定サイトで安全でないコンテンツを許可する</translation>
 <translation id="3965339130942650562">アイドル状態のユーザーがログアウトされるまでのタイムアウト</translation>
 <translation id="396536755218079668">監視対象ユーザー</translation>
+<translation id="3968218878014278212">Eche の有効化を許可します。</translation>
 <translation id="3971673686578912106">このポリシーを有効に設定した場合、<ph name="PLUGIN_VM_NAME" /> がデバイスでオンになります(ただし他の設定でも許可されている必要があります)。<ph name="PLUGIN_VM_NAME" /> を実行するには、<ph name="PLUGIN_VM_ALLOWED_POLICY_NAME" /> と <ph name="USER_PLUGIN_VM_ALLOWED_POLICY_NAME" /> を True に設定し、<ph name="PLUGIN_VM_LICENSE_KEY_POLICY_NAME" /> または <ph name="PLUGIN_VM_USER_ID_POLICY_NAME" /> のいずれかを設定する必要があります。
 
       このポリシーを無効に設定するか未設定のままにした場合、<ph name="PLUGIN_VM_NAME" /> はデバイスでオンになりません。</translation>
@@ -3322,6 +3323,7 @@
 <translation id="5075190314377370852">このポリシーを True に設定した場合、ローカルにインストールされた CA 証明書によって正常に確認されたサーバー証明書に対し、<ph name="PRODUCT_NAME" /> は常に失効確認を行います。<ph name="PRODUCT_NAME" /> が失効のステータス情報を取得できない場合、<ph name="PRODUCT_NAME" /> ではその証明書は失効したものとして扱われます(強制エラー)。このポリシーを False に設定するか未設定のままにした場合、<ph name="PRODUCT_NAME" /> は既存のオンライン失効確認設定を使用します。</translation>
 <translation id="5078623750797048009">PDF の注釈を有効にする</translation>
 <translation id="5081204761483900654">制限付き管理対象ゲスト セッション</translation>
+<translation id="5082296146261894974">ユーザーがスマートフォン ハブの通知をクリックして Eche アプリを起動することを許可します。</translation>
 <translation id="5082572440690475059">File System API 経由での読み取りアクセスを許可するサイトを指定する</translation>
 <translation id="5085647276663819155">印刷プレビューを無効にする</translation>
 <translation id="5090791951240382356">ソースの異なる辞書ポリシーの統合を許可する</translation>
@@ -4045,6 +4047,7 @@
 
       <ph name="RUN_ON_OS_LOGIN_FIELD" /> フィールドでは、OS へのログイン中にウェブアプリを実行できるかどうかを指定します。このフィールドを <ph name="BLOCKED" /> に設定した場合、OS へのログイン中にウェブアプリは実行されず、ユーザーが後から有効にすることもできません。このフィールドを <ph name="RUN_WINDOWED" /> に設定した場合、OS へのログイン中にウェブアプリが実行され、ユーザーは後から無効にできません。このフィールドを <ph name="ALLOWED" /> に設定した場合、ユーザーは OS へのログイン時にウェブアプリを実行するよう設定できます。デフォルトの設定では、<ph name="ALLOWED" /> と <ph name="BLOCKED" /> の値のみが許可されています。
       </translation>
+<translation id="5945312246863177268">ユーザーがスマートフォン ハブの通知をクリックして Eche アプリを起動することを許可しません。</translation>
 <translation id="5946082169633555022">Beta チャンネル</translation>
 <translation id="5946329690214660966">アップデート確認のカスタム スケジュールを設定する</translation>
 <translation id="5950069117106131681">このポリシーを有効に設定した場合、印刷プレビューでヘッダーとフッターがオンになります。このポリシーを無効に設定した場合、印刷プレビューでヘッダーとフッターがオフになります。
@@ -4571,6 +4574,11 @@
 <translation id="66265932317331474">CPU 情報を報告する</translation>
 <translation id="6628120204569232711">ストレージの状態を報告する</translation>
 <translation id="663685822663765995">カラー印刷モードを制限</translation>
+<translation id="6640355849038068978">この設定が有効な場合、ユーザーはスマートフォン ハブの通知をクリックするなどして Eche アプリを起動できます。
+
+      この設定が無効な場合、ユーザーは Eche アプリを起動できません。
+
+      このポリシーが未設定の場合、デフォルトでは、企業で管理されているユーザーと管理されていないユーザーのいずれもアプリを起動できます。</translation>
 <translation id="6641981670621198190">3D グラフィックス API のサポートを無効にする</translation>
 <translation id="6642682198621199507">このポリシーでは、使用可能なすべてのシリアルポートへのアクセスを自動的に許可するサイトのリストを指定できます。必ず有効な URL を指定してください。無効な URL を指定した場合、このポリシーは無視されます。URL のオリジン(スキーム、ホストとポート)のみが考慮されます。Chrome OS では、このポリシーは関連付けられたユーザーにのみ適用されます。
 
diff --git a/components/policy/resources/policy_templates_nl.xtb b/components/policy/resources/policy_templates_nl.xtb
index 881e8da..c610b0f 100644
--- a/components/policy/resources/policy_templates_nl.xtb
+++ b/components/policy/resources/policy_templates_nl.xtb
@@ -2566,6 +2566,7 @@
 <translation id="3964298692570794635">Niet-beveiligde content op deze sites toestaan</translation>
 <translation id="3965339130942650562">Time-out tot de inactieve gebruiker is uitgelogd</translation>
 <translation id="396536755218079668">Gebruikers met beperkte rechten</translation>
+<translation id="3968218878014278212">Toestaan dat Eche wordt aangezet.</translation>
 <translation id="3971673686578912106">Als je het beleid toepast, wordt <ph name="PLUGIN_VM_NAME" /> voor het apparaat aangezet, zolang andere instellingen dit ook toestaan. <ph name="PLUGIN_VM_ALLOWED_POLICY_NAME" /> en <ph name="USER_PLUGIN_VM_ALLOWED_POLICY_NAME" /> moeten zijn ingesteld op Waar en <ph name="PLUGIN_VM_LICENSE_KEY_POLICY_NAME" /> of <ph name="PLUGIN_VM_USER_ID_POLICY_NAME" /> moet zijn ingesteld zodat <ph name="PLUGIN_VM_NAME" /> kan worden uitgevoerd.
 
       Als je het beleid niet toepast of niet instelt, is <ph name="PLUGIN_VM_NAME" /> niet aangezet voor het apparaat.</translation>
@@ -3399,6 +3400,7 @@
       Als je het beleid instelt op 'False' of niet instelt, gebruikt <ph name="PRODUCT_NAME" /> de bestaande instellingen voor online intrekkingscontrole.</translation>
 <translation id="5078623750797048009">Pdf-annotaties aanzetten</translation>
 <translation id="5081204761483900654">Beperkte beheerde gastsessies</translation>
+<translation id="5082296146261894974">Staat toe dat gebruikers op een Telefoonhub-melding klikken om de Eche-app te starten.</translation>
 <translation id="5082572440690475059">Alleen leestoegang via de File System API toestaan op deze sites</translation>
 <translation id="5085647276663819155">Afdrukvoorbeeld uitzetten</translation>
 <translation id="5090791951240382356">Toestaan dat woordenboekbeleid van verschillende bronnen wordt samengevoegd</translation>
@@ -4142,6 +4144,7 @@
 
       Het veld <ph name="RUN_ON_OS_LOGIN_FIELD" /> geeft aan of een web-app kan worden uitgevoerd tijdens de OS-login. Als dit veld is ingesteld op <ph name="BLOCKED" />, wordt de web-app niet uitgevoerd tijdens de OS-login en kan de gebruiker dit later niet aanzetten. Als dit veld is ingesteld op <ph name="RUN_WINDOWED" />, wordt de web-app uitgevoerd tijdens de OS-login en kan de gebruiker dit later niet uitzetten. Als dit veld is ingesteld op <ph name="ALLOWED" />, kan de gebruiker instellen dat de web-app wordt uitgevoerd tijdens de OS-login. De standaardconfiguratie staat alleen de waarden <ph name="ALLOWED" /> en <ph name="BLOCKED" /> toe
       </translation>
+<translation id="5945312246863177268">Staat niet toe dat gebruikers op een Telefoonhub-melding klikken om de Eche-app te starten.</translation>
 <translation id="5946082169633555022">Bètakanaal</translation>
 <translation id="5946329690214660966">Custom planning instellen om te controleren op updates</translation>
 <translation id="5950069117106131681">Als je dit beleid toepast, zijn kop- en voetteksten zichtbaar in het afdrukvoorbeeld. Als je het beleid niet toepast, zijn ze niet zichtbaar in het afdrukvoorbeeld.
@@ -4698,6 +4701,11 @@
 <translation id="66265932317331474">CPU-info melden</translation>
 <translation id="6628120204569232711">Opslagstatus rapporteren</translation>
 <translation id="663685822663765995">Afdrukken in kleurenmodus beperken</translation>
+<translation id="6640355849038068978">Als deze instelling aanstaat, kunnen gebruikers de Eche-app starten, bijvoorbeeld door op een Telefoonhub-melding te klikken.
+
+      Staat deze instelling uit, dan kunnen gebruikers de Eche-app niet starten.
+
+      Als je dit beleid niet instelt, is toegestaan de standaardoptie voor zowel zakelijk beheerde gebruikers als niet-beheerde gebruikers.</translation>
 <translation id="6641981670621198190">Ondersteuning voor API's voor 3D-beelden uitzetten</translation>
 <translation id="6642682198621199507">Als je dit beleid instelt, kun je een lijst met sites maken die automatisch toestemming krijgen voor toegang tot alle beschikbare seriële poorten.
 
diff --git a/components/policy/resources/policy_templates_th.xtb b/components/policy/resources/policy_templates_th.xtb
index 86aa0af..9ece3861 100644
--- a/components/policy/resources/policy_templates_th.xtb
+++ b/components/policy/resources/policy_templates_th.xtb
@@ -2566,6 +2566,7 @@
 <translation id="3964298692570794635">อนุญาตเนื้อหาที่ไม่ปลอดภัยในเว็บไซต์เหล่านี้</translation>
 <translation id="3965339130942650562">หมดเวลาจนกว่าจะดำเนินการออกจากระบบของผู้ใช้ที่ไม่มีการใช้งาน</translation>
 <translation id="396536755218079668">ผู้ใช้ภายใต้การควบคุมดูแล</translation>
+<translation id="3968218878014278212">อนุญาตให้เปิดใช้ Eche</translation>
 <translation id="3971673686578912106">การตั้งค่านโยบายเป็น "เปิดใช้" จะทำให้ <ph name="PLUGIN_VM_NAME" /> เปิดขึ้นสำหรับอุปกรณ์เครื่องนั้น ตราบใดที่การตั้งค่าอื่นๆ อนุญาตให้เปิดได้เช่นกัน <ph name="PLUGIN_VM_ALLOWED_POLICY_NAME" /> และ <ph name="USER_PLUGIN_VM_ALLOWED_POLICY_NAME" /> ต้องเป็น "จริง" รวมทั้ง <ph name="PLUGIN_VM_LICENSE_KEY_POLICY_NAME" /> หรือ <ph name="PLUGIN_VM_USER_ID_POLICY_NAME" /> อย่างใดอย่างหนึ่งต้องมีการตั้งค่าเพื่อให้ <ph name="PLUGIN_VM_NAME" /> ทำงานได้
 
       การตั้งค่านโยบายเป็น "ปิดใช้" หรือไม่ตั้งค่าจะทำให้ไม่มีการเปิดใช้ <ph name="PLUGIN_VM_NAME" /> สำหรับอุปกรณ์เครื่องนั้น</translation>
@@ -3398,6 +3399,7 @@
       การตั้งค่านโยบายเป็น "เท็จ" หรือไม่ได้ตั้งค่า หมายความว่า <ph name="PRODUCT_NAME" /> จะใช้การตั้งค่าการตรวจสอบการเพิกถอนทางออนไลน์ที่มีอยู่</translation>
 <translation id="5078623750797048009">เปิดใช้คำอธิบายประกอบ PDF</translation>
 <translation id="5081204761483900654">เซสชันผู้เยี่ยมชมที่มีการจัดการแบบจำกัด</translation>
+<translation id="5082296146261894974">อนุญาตให้ผู้ใช้คลิกการแจ้งเตือนฮับโทรศัพท์เพื่อเปิดแอปพลิเคชัน Eche</translation>
 <translation id="5082572440690475059">อนุญาตสิทธิ์การเข้าถึงในการอ่านผ่าน File System API ในเว็บไซต์เหล่านี้</translation>
 <translation id="5085647276663819155">ปิดใช้งานหน้าตัวอย่างก่อนพิมพ์</translation>
 <translation id="5090791951240382356">อนุญาตให้รวมนโยบายพจนานุกรมจากแหล่งที่มาต่างๆ</translation>
@@ -4139,6 +4141,7 @@
 
       ช่อง <ph name="RUN_ON_OS_LOGIN_FIELD" /> ระบุว่าเว็บแอปจะทำงานระหว่างการเข้าสู่ระบบปฏิบัติการได้หรือไม่ หากตั้งค่าช่องนี้เป็น <ph name="BLOCKED" /> เว็บแอปจะไม่ทำงานระหว่างการเข้าสู่ระบบปฏิบัติการและผู้ใช้จะเปิดใช้ภายหลังไม่ได้ หากตั้งค่าช่องนี้เป็น <ph name="RUN_WINDOWED" /> เว็บแอปจะทำงานระหว่างการเข้าสู่ระบบปฏิบัติการและผู้ใช้จะปิดใช้ภายหลังไม่ได้ หากตั้งค่าช่องนี้เป็น <ph name="ALLOWED" /> ผู้ใช้จะกำหนดค่าเว็บแอปให้ทำงานเมื่อเข้าสู่ระบบปฏิบัติการได้ การกำหนดค่าเริ่มต้นอนุญาตให้มีค่า <ph name="ALLOWED" /> และ <ph name="BLOCKED" /> เท่านั้น
       </translation>
+<translation id="5945312246863177268">ไม่อนุญาตให้ผู้ใช้คลิกการแจ้งเตือนฮับโทรศัพท์เพื่อเปิดแอปพลิเคชัน Eche</translation>
 <translation id="5946082169633555022">เวอร์ชันเบต้า</translation>
 <translation id="5946329690214660966">ตั้งค่ากำหนดการที่กำหนดเองเพื่อตรวจหาอัปเดต</translation>
 <translation id="5950069117106131681">การตั้งค่านโยบายเป็น "เปิดใช้" จะเปิดส่วนหัวและส่วนท้ายในการแสดงตัวอย่างก่อนพิมพ์ การตั้งค่านโยบายเป็น "ปิดใช้" จะปิดส่วนดังกล่าวในการแสดงตัวอย่างก่อนพิมพ์
@@ -4697,6 +4700,11 @@
 <translation id="66265932317331474">รายงานข้อมูล CPU</translation>
 <translation id="6628120204569232711">รายงานสถานะของพื้นที่เก็บข้อมูล</translation>
 <translation id="663685822663765995">จำกัดโหมดสีการพิมพ์</translation>
+<translation id="6640355849038068978">หากเปิดใช้การตั้งค่านี้ ผู้ใช้จะเปิดแอปพลิเคชัน Eche ได้ เช่น เปิดโดยคลิกการแจ้งเตือนฮับโทรศัพท์
+
+      หากปิดใช้การตั้งค่านี้ ผู้ใช้จะไม่สามารถเปิดแอปพลิเคชัน Eche
+
+      หากไม่ได้ตั้งค่านโยบายนี้ ทั้งผู้ใช้ที่มีองค์กรเป็นผู้จัดการและผู้ใช้ที่ไม่มีการจัดการจะใช้ค่าเริ่มต้นได้</translation>
 <translation id="6641981670621198190">ปิดใช้งานการสนับสนุน API ของกราฟิก 3 มิติ</translation>
 <translation id="6642682198621199507">การตั้งค่านโยบายนี้ให้คุณระบุเว็บไซต์ที่ได้รับสิทธิ์โดยอัตโนมัติในการเข้าถึงพอร์ตอนุกรมที่ใช้ได้ทุกพอร์ต
 
diff --git a/components/policy/resources/policy_templates_zh-TW.xtb b/components/policy/resources/policy_templates_zh-TW.xtb
index 137cf84..4dc408da 100644
--- a/components/policy/resources/policy_templates_zh-TW.xtb
+++ b/components/policy/resources/policy_templates_zh-TW.xtb
@@ -2556,6 +2556,7 @@
 <translation id="3964298692570794635">允許這些網站的不安全內容</translation>
 <translation id="3965339130942650562">逾時直到閒置使用者登出</translation>
 <translation id="396536755218079668">受監管的使用者</translation>
+<translation id="3968218878014278212">允許啟用 Eche。</translation>
 <translation id="3971673686578912106">如果將這項政策設為啟用,只要其他設定允許,裝置就會啟用 <ph name="PLUGIN_VM_NAME" />。<ph name="PLUGIN_VM_ALLOWED_POLICY_NAME" /> 和 <ph name="USER_PLUGIN_VM_ALLOWED_POLICY_NAME" /> 必須設為 True,且必須設定 <ph name="PLUGIN_VM_LICENSE_KEY_POLICY_NAME" /> 或 <ph name="PLUGIN_VM_USER_ID_POLICY_NAME" />,<ph name="PLUGIN_VM_NAME" /> 才能順利執行。
 
       如果將這項政策設為停用或不設定,則裝置不會啟用 <ph name="PLUGIN_VM_NAME" />。</translation>
@@ -3388,6 +3389,7 @@
       如果將這項政策設為 False 或不設定,<ph name="PRODUCT_NAME" /> 會使用現有的線上撤銷檢查設定。</translation>
 <translation id="5078623750797048009">啟用 PDF 註解功能</translation>
 <translation id="5081204761483900654">有限制的受管理訪客工作階段</translation>
+<translation id="5082296146261894974">允許使用者透過點選 Phone Hub 通知的方式啟動 Eche 應用程式。</translation>
 <translation id="5082572440690475059">允許這些網站透過 File System API 進行讀取</translation>
 <translation id="5085647276663819155">停用列印預覽</translation>
 <translation id="5090791951240382356">允許合併不同來源的字典政策</translation>
@@ -4115,6 +4117,7 @@
 
       <ph name="RUN_ON_OS_LOGIN_FIELD" /> 欄位會指定網頁應用程式是否可在 OS 登入期間執行。如果將這個欄位設為 <ph name="BLOCKED" />,網頁應用程式不會在 OS 登入期間執行,使用者之後也無法啟用此欄位。如果將這個欄位設為 <ph name="RUN_WINDOWED" />,網頁應用程式就會在 OS 登入期間執行,而且使用者之後無法停用此欄位。如果將這個欄位設為 <ph name="ALLOWED" />,使用者則可將網頁應用程式設為在 OS 登入期間執行。請注意,預設設定可指定的值僅限 <ph name="ALLOWED" /> 和 <ph name="BLOCKED" />。
       </translation>
+<translation id="5945312246863177268">禁止使用者透過點選 Phone Hub 通知的方式啟動 Eche 應用程式。</translation>
 <translation id="5946082169633555022">Beta 版</translation>
 <translation id="5946329690214660966">自訂檢查更新的時間表</translation>
 <translation id="5950069117106131681">如果將這項政策設為啟用,系統會在列印預覽中顯示頁首和頁尾。如果將這項政策設為停用,則不會顯示這些內容。
@@ -4679,6 +4682,11 @@
 <translation id="66265932317331474">回報 CPU 資訊</translation>
 <translation id="6628120204569232711">回報儲存空間狀態</translation>
 <translation id="663685822663765995">限制列印色彩模式</translation>
+<translation id="6640355849038068978">如果啟用這項設定,使用者將可以透過像是點選 Phone Hub 通知的方式,啟動 Eche 應用程式。
+
+      如果停用這項設定,使用者就無法啟動 Eche 應用程式。
+
+      如果不設定這項政策,預設值是允許受企業管理以及未受管理的使用者執行這項功能。</translation>
 <translation id="6641981670621198190">停止支援 3D 圖形 API</translation>
 <translation id="6642682198621199507">你可以透過這項政策設定網站清單,指定哪些網站會自動取得權限,能夠存取所有可用的序列埠。
 
diff --git a/components/policy/test_support/client_storage.cc b/components/policy/test_support/client_storage.cc
index 524cece..3a4fe03 100644
--- a/components/policy/test_support/client_storage.cc
+++ b/components/policy/test_support/client_storage.cc
@@ -77,7 +77,7 @@
     uint64_t remainder) const {
   std::vector<std::string> hashes;
   for (const auto& [device_id, client_info] : clients_) {
-    // This does not actually divide hashes by |modulus| and verifies that
+    // This does not actually divide hashes by |modulus| and verify that
     // |remainder| is correct as current tests do not rely on this behavior.
     // This is difficult to implement since 32-byte hashes do not fit into
     // regular integer types and thus long-arithmetic approach is needed.
diff --git a/components/policy/test_support/policy_storage.cc b/components/policy/test_support/policy_storage.cc
index 26c3bc7..0d8381e 100644
--- a/components/policy/test_support/policy_storage.cc
+++ b/components/policy/test_support/policy_storage.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "components/policy/test_support/policy_storage.h"
+#include "base/big_endian.h"
 #include "crypto/sha2.h"
 
 namespace policy {
@@ -61,16 +62,14 @@
     uint64_t remainder) const {
   std::vector<std::string> hashes;
   for (const auto& [serial, enrollment_state] : initial_enrollment_states_) {
-    uint64_t hash = 0;
-    char hstr[sizeof(hash)];
-    crypto::SHA256HashString(serial, &hstr, sizeof(hash));
-    // Convert hash string to uint64_t using big-endian byte-order.
-    for (size_t i = 0; i < sizeof(hash); i++) {
-      uint64_t byte = static_cast<unsigned char>(hstr[i]);
-      hash += byte << (sizeof(hash) - i - 1) * CHAR_BIT;
+    uint64_t hash = 0UL;
+    uint8_t hash_bytes[sizeof(hash)];
+    crypto::SHA256HashString(serial, hash_bytes, sizeof(hash));
+    base::ReadBigEndian(hash_bytes, &hash);
+    if (hash % modulus == remainder) {
+      hashes.emplace_back(reinterpret_cast<const char*>(hash_bytes),
+                          sizeof(hash));
     }
-    if (hash % modulus == remainder)
-      hashes.emplace_back(hstr, sizeof(uint64_t));
   }
   return hashes;
 }
diff --git a/components/policy/test_support/test_server_helpers.cc b/components/policy/test_support/test_server_helpers.cc
index ec73939..faf9251 100644
--- a/components/policy/test_support/test_server_helpers.cc
+++ b/components/policy/test_support/test_server_helpers.cc
@@ -35,8 +35,15 @@
 void CustomHttpResponse::SendResponse(
     base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) {
   std::string reason = "Custom";
-  if (base::ranges::lower_bound(kStandardHttpStatusCodes, code()))
+  // The implementation of the BasicHttpResponse::reason() calls
+  // net::GetHttpReasonPhrase, which requires status code to be a standard HTTP
+  // status code and crashes otherwise. Hence we avoid calling it if a custom
+  // HTTP code is used.
+  // TODO(crbug/1280752): Make GetHttpReasonPhrase support custom codes instead.
+  if (base::ranges::lower_bound(kStandardHttpStatusCodes, code()) !=
+      base::ranges::end(kStandardHttpStatusCodes)) {
     reason = BasicHttpResponse::reason();
+  }
   delegate->SendHeadersContentAndFinish(code(), reason, BuildHeaders(),
                                         content());
 }
diff --git a/components/sync/engine/commit_contribution_impl.cc b/components/sync/engine/commit_contribution_impl.cc
index 16c3670..c1747c4 100644
--- a/components/sync/engine/commit_contribution_impl.cc
+++ b/components/sync/engine/commit_contribution_impl.cc
@@ -242,8 +242,9 @@
       DCHECK(unique_position.IsValid());
       commit_proto->set_position_in_parent(unique_position.ToInt64());
       *commit_proto->mutable_unique_position() = unique_position.ToProto();
-      if (!entity_data.parent_id.empty()) {
-        commit_proto->set_parent_id_string(entity_data.parent_id);
+      // parent_id field is set only for legacy clients only, before M99.
+      if (!entity_data.legacy_parent_id.empty()) {
+        commit_proto->set_parent_id_string(entity_data.legacy_parent_id);
       }
     }
     commit_proto->set_ctime(TimeToProtoTime(entity_data.creation_time));
diff --git a/components/sync/engine/commit_contribution_impl_unittest.cc b/components/sync/engine/commit_contribution_impl_unittest.cc
index e37094cc..18a88a91 100644
--- a/components/sync/engine/commit_contribution_impl_unittest.cc
+++ b/components/sync/engine/commit_contribution_impl_unittest.cc
@@ -121,7 +121,7 @@
   data->creation_time = creation_time;
   data->modification_time = modification_time;
   data->name = "Name:";
-  data->parent_id = "ParentOf:";
+  data->legacy_parent_id = "ParentOf:";
 
   CommitRequestData request_data;
   request_data.sequence_number = 2;
@@ -164,7 +164,7 @@
   data->creation_time = creation_time;
   data->modification_time = modification_time;
   data->name = "Name:";
-  data->parent_id = "ParentOf:";
+  data->legacy_parent_id = "ParentOf:";
 
   CommitRequestData request_data;
   request_data.sequence_number = 2;
diff --git a/components/sync/engine/entity_data.cc b/components/sync/engine/entity_data.cc
index 1f78e7f..89fd12f 100644
--- a/components/sync/engine/entity_data.cc
+++ b/components/sync/engine/entity_data.cc
@@ -51,7 +51,7 @@
   // title.
   dict->SetString("NON_UNIQUE_NAME", name);
   ADD_TO_DICT(dict, name);
-  ADD_TO_DICT(dict, parent_id);
+  ADD_TO_DICT(dict, legacy_parent_id);
   ADD_TO_DICT_WITH_TRANSFORM(dict, ctime, GetTimeDebugString);
   ADD_TO_DICT_WITH_TRANSFORM(dict, mtime, GetTimeDebugString);
   return dict;
@@ -70,7 +70,7 @@
   memory_usage += EstimateMemoryUsage(server_defined_unique_tag);
   memory_usage += EstimateMemoryUsage(name);
   memory_usage += EstimateMemoryUsage(specifics);
-  memory_usage += EstimateMemoryUsage(parent_id);
+  memory_usage += EstimateMemoryUsage(legacy_parent_id);
   return memory_usage;
 }
 
diff --git a/components/sync/engine/entity_data.h b/components/sync/engine/entity_data.h
index 9844045a..1f235203 100644
--- a/components/sync/engine/entity_data.h
+++ b/components/sync/engine/entity_data.h
@@ -76,9 +76,12 @@
   base::Time creation_time;
   base::Time modification_time;
 
-  // Sync ID of the parent entity. This is supposed to be set only for
-  // hierarchical datatypes (e.g. Bookmarks).
-  std::string parent_id;
+  // Server-provided sync ID of the parent entity, used for legacy bookmarks
+  // only. Unused for modern data created or reuploaded by M94 or above, which
+  // relies exclusively on the parent's GUID in BookmarkSpecifics.
+  // WARNING: Avoid references to this field outside
+  // components/sync_bookmarks/parent_guid_preprocessing.cc.
+  std::string legacy_parent_id;
 
   // Indicate whether bookmark's |unique_position| was missing in the original
   // specifics during GetUpdates. If the |unique_position| in specifics was
diff --git a/components/sync/engine/model_type_worker.cc b/components/sync/engine/model_type_worker.cc
index fed782c..d70dfa3 100644
--- a/components/sync/engine/model_type_worker.cc
+++ b/components/sync/engine/model_type_worker.cc
@@ -78,7 +78,7 @@
   // entities. This code manually asks the bridge to create the client tags for
   // each entity, so that we can use ClientTagBasedModelTypeProcessor for
   // AUTOFILL_WALLET_DATA or AUTOFILL_WALLET_OFFER.
-  if (data->parent_id == "0") {
+  if (data->legacy_parent_id == "0") {
     // Ignore the permanent root node as that one should have no client tag
     // hash.
     return;
@@ -434,7 +434,7 @@
   data.creation_time = ProtoTimeToTime(update_entity.ctime());
   data.modification_time = ProtoTimeToTime(update_entity.mtime());
   data.name = update_entity.name();
-  data.parent_id = update_entity.parent_id_string();
+  data.legacy_parent_id = update_entity.parent_id_string();
   data.server_defined_unique_tag = update_entity.server_defined_unique_tag();
 
   // Populate |originator_cache_guid| and |originator_client_item_id|. This is
diff --git a/components/sync/engine/model_type_worker_unittest.cc b/components/sync/engine/model_type_worker_unittest.cc
index 66977e4e..3f418349e 100644
--- a/components/sync/engine/model_type_worker_unittest.cc
+++ b/components/sync/engine/model_type_worker_unittest.cc
@@ -1559,7 +1559,7 @@
                 FakeCryptographer(), PREFERENCES, entity, &response_data));
   const EntityData& data = response_data.entity;
   EXPECT_FALSE(data.id.empty());
-  EXPECT_FALSE(data.parent_id.empty());
+  EXPECT_FALSE(data.legacy_parent_id.empty());
   EXPECT_EQ("CLIENT_TAG", data.client_tag_hash.value());
   EXPECT_EQ("SERVER_TAG", data.server_defined_unique_tag);
   EXPECT_FALSE(data.is_deleted());
diff --git a/components/sync/protocol/sync_entity.proto b/components/sync/protocol/sync_entity.proto
index e6e76e25..70eef11 100644
--- a/components/sync/protocol/sync_entity.proto
+++ b/components/sync/protocol/sync_entity.proto
@@ -29,6 +29,14 @@
   // ID if there was a new created item with that ID appearing earlier
   // in the message.  In all other situations, it is a server ID.
   // Present in both GetUpdatesResponse and CommitMessage.
+  //
+  // Starting with M99, this field is optional and used for legacy bookmarks
+  // only:
+  // 1. When processing GetUpdatesResponse, it is unused for modern data created
+  //    or reuploaded by M94 or above, which populates the parent's GUID in
+  //    BookmarkSpecifics (which is sufficient).
+  // 2. When issuing CommitMessage, the field is populated for compatibility
+  //    with clients before M99.
   optional string parent_id_string = 2;
 
   reserved 3;
diff --git a/components/sync/test/engine/mock_model_type_worker.cc b/components/sync/test/engine/mock_model_type_worker.cc
index 5b43d17..3cb32a1 100644
--- a/components/sync/test/engine/mock_model_type_worker.cc
+++ b/components/sync/test/engine/mock_model_type_worker.cc
@@ -197,7 +197,7 @@
     const ModelType& model_type) {
   syncer::EntityData data;
   data.id = syncer::ModelTypeToRootTag(model_type);
-  data.parent_id = "r";
+  data.legacy_parent_id = "r";
   data.server_defined_unique_tag = syncer::ModelTypeToRootTag(model_type);
   syncer::AddDefaultFieldValue(model_type, &data.specifics);
   // These elements should have no effect on behavior, but we set them anyway
diff --git a/components/sync_bookmarks/bookmark_local_changes_builder.cc b/components/sync_bookmarks/bookmark_local_changes_builder.cc
index f619061..13375580 100644
--- a/components/sync_bookmarks/bookmark_local_changes_builder.cc
+++ b/components/sync_bookmarks/bookmark_local_changes_builder.cc
@@ -93,7 +93,7 @@
       const SyncedBookmarkTracker::Entity* parent_entity =
           bookmark_tracker_->GetEntityForBookmarkNode(parent);
       DCHECK(parent_entity);
-      data->parent_id = parent_entity->metadata()->server_id();
+      data->legacy_parent_id = parent_entity->metadata()->server_id();
       // Assign specifics only for the non-deletion case. In case of deletion,
       // EntityData should contain empty specifics to indicate deletion.
       data->specifics = CreateSpecificsFromBookmarkNode(
diff --git a/components/sync_bookmarks/bookmark_model_merger_unittest.cc b/components/sync_bookmarks/bookmark_model_merger_unittest.cc
index 9140621..29d0127 100644
--- a/components/sync_bookmarks/bookmark_model_merger_unittest.cc
+++ b/components/sync_bookmarks/bookmark_model_merger_unittest.cc
@@ -1820,7 +1820,7 @@
       /*url=*/"http://url1",
       /*is_folder=*/false,
       /*unique_position=*/MakeRandomPosition()));
-  updates.back().entity.parent_id = kRootNodeId;
+  updates.back().entity.legacy_parent_id = kRootNodeId;
   // To cover a slightly different case and guard against future bugs, let's
   // assume one of the updates uses a GUID in specifics that refers to the
   // root node.
@@ -1832,7 +1832,7 @@
       /*url=*/"http://url2",
       /*is_folder=*/false,
       /*unique_position=*/MakeRandomPosition()));
-  updates.back().entity.parent_id = kRootNodeId;
+  updates.back().entity.legacy_parent_id = kRootNodeId;
 
   base::HistogramTester histogram_tester;
   Merge(std::move(updates), bookmark_model.get());
diff --git a/components/sync_bookmarks/bookmark_model_type_processor.cc b/components/sync_bookmarks/bookmark_model_type_processor.cc
index 449f884..8973ef5 100644
--- a/components/sync_bookmarks/bookmark_model_type_processor.cc
+++ b/components/sync_bookmarks/bookmark_model_type_processor.cc
@@ -533,13 +533,13 @@
     // Set the parent to empty string to indicate it's parent of the root node
     // for bookmarks. The code in sync_node_browser.js links nodes with the
     // "modelType" when they are lacking a parent id.
-    data.parent_id = "";
+    data.legacy_parent_id = "";
   } else {
     const bookmarks::BookmarkNode* parent = node->parent();
     const SyncedBookmarkTracker::Entity* parent_entity =
         bookmark_tracker_->GetEntityForBookmarkNode(parent);
     DCHECK(parent_entity);
-    data.parent_id = parent_entity->metadata()->server_id();
+    data.legacy_parent_id = parent_entity->metadata()->server_id();
   }
 
   std::unique_ptr<base::DictionaryValue> data_dictionary =
@@ -555,7 +555,7 @@
     data_dictionary->SetString("UNIQUE_SERVER_TAG",
                                data.server_defined_unique_tag);
   } else {
-    data_dictionary->SetString("PARENT_ID", "s" + data.parent_id);
+    data_dictionary->SetString("PARENT_ID", "s" + data.legacy_parent_id);
   }
   data_dictionary->SetInteger("LOCAL_EXTERNAL_ID", node->id());
   data_dictionary->SetInteger("positionIndex", index);
diff --git a/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc b/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc
index 0a1755a..f6862f0 100644
--- a/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc
+++ b/components/sync_bookmarks/bookmark_model_type_processor_unittest.cc
@@ -85,7 +85,7 @@
     const base::GUID& guid) {
   syncer::EntityData data;
   data.id = bookmark_info.server_id;
-  data.parent_id = bookmark_info.parent_id;
+  data.legacy_parent_id = bookmark_info.parent_id;
   data.server_defined_unique_tag = bookmark_info.server_tag;
   data.originator_client_item_id = guid.AsLowercaseString();
 
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler.cc b/components/sync_bookmarks/bookmark_remote_updates_handler.cc
index 36402b52..96d2cb3 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler.cc
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler.cc
@@ -133,7 +133,7 @@
 }
 
 bool IsPermanentNodeUpdate(const syncer::EntityData& update_entity) {
-  return update_entity.parent_id == "0" ||
+  return update_entity.legacy_parent_id == "0" ||
          !update_entity.server_defined_unique_tag.empty();
 }
 
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc b/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
index 0054e01..c325263 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
@@ -218,7 +218,6 @@
     const std::string& tag) {
   syncer::EntityData data;
   data.id = id;
-  data.parent_id = "root_id";
   data.server_defined_unique_tag = tag;
 
   data.specifics.mutable_bookmark();
diff --git a/components/sync_bookmarks/parent_guid_preprocessing.cc b/components/sync_bookmarks/parent_guid_preprocessing.cc
index 727664e..46e9062a 100644
--- a/components/sync_bookmarks/parent_guid_preprocessing.cc
+++ b/components/sync_bookmarks/parent_guid_preprocessing.cc
@@ -36,7 +36,7 @@
 
 bool NeedsParentGuidInSpecifics(const syncer::UpdateResponseData& update) {
   return !update.entity.is_deleted() &&
-         update.entity.parent_id != std::string("0") &&
+         update.entity.legacy_parent_id != std::string("0") &&
          update.entity.server_defined_unique_tag.empty() &&
          !update.entity.specifics.bookmark().has_parent_guid();
 }
@@ -49,12 +49,12 @@
     const syncer::UpdateResponseData& update) {
   DCHECK(tracker);
   DCHECK(!update.entity.is_deleted());
-  DCHECK(!update.entity.parent_id.empty());
+  DCHECK(!update.entity.legacy_parent_id.empty());
   DCHECK(update.entity.server_defined_unique_tag.empty());
   DCHECK(!update.entity.specifics.bookmark().has_parent_guid());
 
   const SyncedBookmarkTracker::Entity* const tracked_parent =
-      tracker->GetEntityForSyncId(update.entity.parent_id);
+      tracker->GetEntityForSyncId(update.entity.legacy_parent_id);
   if (!tracked_parent) {
     // Parent not known by tracker.
     return base::GUID();
@@ -139,9 +139,9 @@
   DCHECK(tracker);
   DCHECK(sync_id_to_guid_map_in_updates);
 
-  if (update.entity.parent_id.empty()) {
-    // Without the |parent_id| field set, there is no information available to
-    // determine the parent and/or its GUID.
+  if (update.entity.legacy_parent_id.empty()) {
+    // Without the |SyncEntity.parent_id| field set, there is no information
+    // available to determine the parent and/or its GUID.
     return base::GUID();
   }
 
@@ -159,14 +159,14 @@
   // parent and child, none of which would be known by |tracker|.
   guid = base::GUID::ParseLowercase(
       sync_id_to_guid_map_in_updates->GetGuidForSyncId(
-          update.entity.parent_id));
+          update.entity.legacy_parent_id));
   if (guid.is_valid()) {
     return guid;
   }
 
   // At this point the parent's GUID couldn't be determined, but actually
-  // the |parent_id| was non-empty. The update will be ignored regardless, but
-  // to avoid behavioral differences in UMA metrics
+  // the |SyncEntity.parent_id| was non-empty. The update will be ignored
+  // regardless, but to avoid behavioral differences in UMA metrics
   // Sync.ProblematicServerSideBookmarks[DuringMerge], a fake parent GUID is
   // used here, which is known to never match an existing entity.
   guid = base::GUID::ParseLowercase(kInvalidParentGuid);
diff --git a/components/sync_bookmarks/parent_guid_preprocessing_unittest.cc b/components/sync_bookmarks/parent_guid_preprocessing_unittest.cc
index 82abaa6..c5ef601 100644
--- a/components/sync_bookmarks/parent_guid_preprocessing_unittest.cc
+++ b/components/sync_bookmarks/parent_guid_preprocessing_unittest.cc
@@ -103,11 +103,11 @@
   updates.back().entity.server_defined_unique_tag = "bookmark_bar";
   updates.emplace_back();
   updates.back().entity.id = kParentFolderId;
-  updates.back().entity.parent_id = kBookmarkBarId;
+  updates.back().entity.legacy_parent_id = kBookmarkBarId;
   updates.back().entity.specifics.mutable_bookmark()->set_guid(
       kParentFolderGuid);
   updates.emplace_back();
-  updates.back().entity.parent_id = kParentFolderId;
+  updates.back().entity.legacy_parent_id = kParentFolderId;
   updates.back().entity.specifics.mutable_bookmark()->set_guid("child_guid");
 
   PopulateParentGuidInSpecifics(/*tracker=*/nullptr, &updates);
@@ -139,7 +139,7 @@
   updates.back().entity.server_defined_unique_tag = "bookmark_bar";
   updates.emplace_back();
   updates.back().entity.id = kFolderId;
-  updates.back().entity.parent_id = kBookmarkBarId;
+  updates.back().entity.legacy_parent_id = kBookmarkBarId;
   updates.back().entity.specifics.mutable_bookmark()->set_guid(kFolderGuid);
   updates.back().entity.specifics.mutable_bookmark()->set_parent_guid(
       kParentGuidInSpecifics);
@@ -191,10 +191,10 @@
 
   syncer::UpdateResponseDataList updates;
   updates.emplace_back();
-  updates.back().entity.parent_id = kSyncId;
+  updates.back().entity.legacy_parent_id = kSyncId;
   updates.back().entity.specifics.mutable_bookmark()->set_guid("guid1");
   updates.emplace_back();
-  updates.back().entity.parent_id = kBookmarkBarId;
+  updates.back().entity.legacy_parent_id = kBookmarkBarId;
   updates.back().entity.specifics.mutable_bookmark()->set_guid("guid2");
   PopulateParentGuidInSpecifics(tracker.get(), &updates);
 
@@ -218,7 +218,7 @@
   syncer::UpdateResponseDataList updates;
   updates.emplace_back();
   updates.back().entity.id = kParentFolderId;
-  updates.back().entity.parent_id = "some_unknown_parent";
+  updates.back().entity.legacy_parent_id = "some_unknown_parent";
   updates.back().entity.specifics.mutable_bookmark()->set_guid(
       kParentFolderGuid);
 
diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
index 9b3e3ca..3999a567 100644
--- a/content/app/content_main_runner_impl.cc
+++ b/content/app/content_main_runner_impl.cc
@@ -83,7 +83,6 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/main_function_params.h"
 #include "content/public/common/network_service_util.h"
-#include "content/public/common/sandbox_init.h"
 #include "content/public/common/zygote/zygote_buildflags.h"
 #include "content/public/gpu/content_gpu_client.h"
 #include "content/public/renderer/content_renderer_client.h"
@@ -100,6 +99,7 @@
 #include "mojo/public/cpp/system/invitation.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 #include "ppapi/buildflags/buildflags.h"
+#include "sandbox/policy/sandbox.h"
 #include "sandbox/policy/sandbox_type.h"
 #include "sandbox/policy/switches.h"
 #include "services/network/public/cpp/features.h"
@@ -920,7 +920,7 @@
   delegate_->PreSandboxStartup();
 
 #if defined(OS_WIN)
-  if (!InitializeSandbox(
+  if (!sandbox::policy::Sandbox::Initialize(
           sandbox::policy::SandboxTypeFromCommandLine(command_line),
           content_main_params_->sandbox_info))
     return TerminateForFatalInitializationError();
diff --git a/content/browser/child_process_launcher_helper_win.cc b/content/browser/child_process_launcher_helper_win.cc
index e4ec0ff..92604148 100644
--- a/content/browser/child_process_launcher_helper_win.cc
+++ b/content/browser/child_process_launcher_helper_win.cc
@@ -12,7 +12,7 @@
 #include "content/browser/child_process_launcher_helper.h"
 #include "content/public/browser/child_process_launcher_utils.h"
 #include "content/public/common/result_codes.h"
-#include "content/public/common/sandbox_init.h"
+#include "content/public/common/sandbox_init_win.h"
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index f7f84ace..857f6f2 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -173,15 +173,17 @@
   return is_site_instance_for_failures == is_failure;
 }
 
+// Simple wrapper around WebExposedIsolationInfo::AreCompatible for easier use
+// within the process model.
 bool IsSiteInstanceCompatibleWithWebExposedIsolation(
     SiteInstance* site_instance,
-    const UrlInfo& url_info) {
+    const absl::optional<WebExposedIsolationInfo>& web_exposed_isolation_info) {
   SiteInstanceImpl* site_instance_impl =
       static_cast<SiteInstanceImpl*>(site_instance);
 
   return WebExposedIsolationInfo::AreCompatible(
       site_instance_impl->GetWebExposedIsolationInfo(),
-      url_info.web_exposed_isolation_info);
+      web_exposed_isolation_info);
 }
 
 // Helper for appending more information to the optional |reason| parameter
@@ -1899,8 +1901,8 @@
       ConvertToSiteInstance(new_instance_descriptor, candidate_instance);
   SiteInstanceImpl* new_instance_impl =
       static_cast<SiteInstanceImpl*>(new_instance.get());
-  DCHECK(IsSiteInstanceCompatibleWithWebExposedIsolation(new_instance_impl,
-                                                         dest_url_info));
+  DCHECK(IsSiteInstanceCompatibleWithWebExposedIsolation(
+      new_instance_impl, dest_url_info.web_exposed_isolation_info));
 
   // If |should_swap| is true, we must use a different SiteInstance than the
   // current one. If we didn't, we would have two RenderFrameHosts in the same
@@ -2082,8 +2084,8 @@
     // when error pages are involved.
     if (IsSiteInstanceCompatibleWithErrorIsolation(
             dest_instance, *frame_tree_node_, is_failure)) {
-      if (IsSiteInstanceCompatibleWithWebExposedIsolation(dest_instance,
-                                                          dest_url_info)) {
+      if (IsSiteInstanceCompatibleWithWebExposedIsolation(
+              dest_instance, dest_url_info.web_exposed_isolation_info)) {
         // TODO(nasko,creis): The check whether data: or about: URLs are allowed
         // to commit in the current process should be in IsSuitableForUrlInfo.
         // However, making this change has further implications and needs more
@@ -2254,9 +2256,7 @@
   }
 
   // Use the current SiteInstance for same site navigations.
-  if (render_frame_host_->IsNavigationSameSite(dest_url_info) &&
-      IsSiteInstanceCompatibleWithWebExposedIsolation(
-          render_frame_host_->GetSiteInstance(), dest_url_info)) {
+  if (render_frame_host_->IsNavigationSameSite(dest_url_info)) {
     AppendReason(reason, "DetermineSiteInstanceForURL / same-site-navigation");
     return SiteInstanceDescriptor(render_frame_host_->GetSiteInstance());
   }
@@ -2338,7 +2338,8 @@
   // BrowsingInstance, unless the destination URL's web-exposed isolated state
   // cannot be hosted by it.
   if (IsSiteInstanceCompatibleWithWebExposedIsolation(
-          render_frame_host_->GetSiteInstance(), dest_url_info)) {
+          render_frame_host_->GetSiteInstance(),
+          dest_url_info.web_exposed_isolation_info)) {
     AppendReason(reason,
                  "DetermineSiteInstanceForURL / fallback / coop-compatible");
     return SiteInstanceDescriptor(dest_url_info, SiteInstanceRelation::RELATED);
@@ -2422,7 +2423,8 @@
   // check if the candidate matches.
   if (candidate_instance &&
       IsSiteInstanceCompatibleWithWebExposedIsolation(
-          candidate_instance, descriptor.dest_url_info) &&
+          candidate_instance,
+          descriptor.dest_url_info.web_exposed_isolation_info) &&
       !current_instance->IsRelatedSiteInstance(candidate_instance) &&
       candidate_instance->DoesSiteInfoForURLMatch(descriptor.dest_url_info)) {
     return candidate_instance;
@@ -2469,8 +2471,8 @@
     return false;
   }
 
-  if (!IsSiteInstanceCompatibleWithWebExposedIsolation(source_instance,
-                                                       dest_url_info)) {
+  if (!IsSiteInstanceCompatibleWithWebExposedIsolation(
+          source_instance, dest_url_info.web_exposed_isolation_info)) {
     return false;
   }
 
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index 80d9602..7f6833d 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -340,7 +340,6 @@
 
   if (is_linux || is_chromeos) {
     sources += [
-      "sandbox_init_linux.cc",
       "zygote/sandbox_support_linux.cc",
       "zygote/send_zygote_child_ping_linux.cc",
       "zygote/zygote_commands_linux.h",
diff --git a/content/common/sandbox_init_linux.cc b/content/common/sandbox_init_linux.cc
deleted file mode 100644
index d9930f7..0000000
--- a/content/common/sandbox_init_linux.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/public/common/sandbox_init.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/files/scoped_file.h"
-#include "build/build_config.h"
-#include "sandbox/linux/bpf_dsl/policy.h"
-#include "sandbox/policy/linux/sandbox_seccomp_bpf_linux.h"
-
-namespace content {
-
-bool InitializeSandbox(std::unique_ptr<sandbox::bpf_dsl::Policy> policy,
-                       base::ScopedFD proc_fd) {
-  return sandbox::policy::SandboxSeccompBPF::StartSandboxWithExternalPolicy(
-      std::move(policy), std::move(proc_fd));
-}
-
-std::unique_ptr<sandbox::bpf_dsl::Policy> GetBPFSandboxBaselinePolicy() {
-  return sandbox::policy::SandboxSeccompBPF::GetBaselinePolicy();
-}
-
-}  // namespace content
diff --git a/content/common/sandbox_init_win.cc b/content/common/sandbox_init_win.cc
index 8813a7e..753f7a4 100644
--- a/content/common/sandbox_init_win.cc
+++ b/content/common/sandbox_init_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/public/common/sandbox_init.h"
+#include "content/public/common/sandbox_init_win.h"
 
 #include <string>
 
@@ -10,7 +10,6 @@
 #include "base/trace_event/trace_event.h"
 #include "base/win/scoped_process_information.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/sandbox_init.h"
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
 #include "sandbox/policy/mojom/sandbox.mojom.h"
 #include "sandbox/policy/sandbox.h"
@@ -20,11 +19,6 @@
 
 namespace content {
 
-bool InitializeSandbox(sandbox::mojom::Sandbox sandbox_type,
-                       sandbox::SandboxInterfaceInfo* sandbox_info) {
-  return sandbox::policy::Sandbox::Initialize(sandbox_type, sandbox_info);
-}
-
 sandbox::ResultCode StartSandboxedProcess(
     SandboxedProcessLauncherDelegate* delegate,
     const base::CommandLine& target_command_line,
diff --git a/content/gpu/gpu_main.cc b/content/gpu/gpu_main.cc
index cdd437a..ae9a1951 100644
--- a/content/gpu/gpu_main.cc
+++ b/content/gpu/gpu_main.cc
@@ -85,7 +85,6 @@
 
 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
 #include "content/gpu/gpu_sandbox_hook_linux.h"
-#include "content/public/common/sandbox_init.h"
 #include "sandbox/policy/linux/sandbox_linux.h"
 #include "sandbox/policy/sandbox_type.h"
 #endif
diff --git a/content/ppapi_plugin/ppapi_plugin_main.cc b/content/ppapi_plugin/ppapi_plugin_main.cc
index 50148eb..37c47c0 100644
--- a/content/ppapi_plugin/ppapi_plugin_main.cc
+++ b/content/ppapi_plugin/ppapi_plugin_main.cc
@@ -45,7 +45,6 @@
 #endif
 
 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
-#include "content/public/common/sandbox_init.h"
 #include "sandbox/policy/linux/sandbox_linux.h"
 #include "sandbox/policy/sandbox_type.h"
 #endif
diff --git a/content/ppapi_plugin/ppapi_thread.cc b/content/ppapi_plugin/ppapi_thread.cc
index e874f04..8643249 100644
--- a/content/ppapi_plugin/ppapi_thread.cc
+++ b/content/ppapi_plugin/ppapi_thread.cc
@@ -29,7 +29,6 @@
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/pepper_plugin_info.h"
-#include "content/public/common/sandbox_init.h"
 #include "ipc/ipc_channel_handle.h"
 #include "ipc/ipc_platform_file.h"
 #include "ipc/ipc_sync_channel.h"
diff --git a/content/public/browser/devtools_agent_host_client.cc b/content/public/browser/devtools_agent_host_client.cc
index 8d5455e..efcb528 100644
--- a/content/public/browser/devtools_agent_host_client.cc
+++ b/content/public/browser/devtools_agent_host_client.cc
@@ -44,4 +44,8 @@
   return absl::nullopt;
 }
 
+std::string DevToolsAgentHostClient::GetTypeForMetrics() {
+  return "Other";
+}
+
 }  // namespace content
diff --git a/content/public/browser/devtools_agent_host_client.h b/content/public/browser/devtools_agent_host_client.h
index cb13d3c6..8efc8fad 100644
--- a/content/public/browser/devtools_agent_host_client.h
+++ b/content/public/browser/devtools_agent_host_client.h
@@ -63,6 +63,10 @@
 
   // Determines protocol message format.
   virtual bool UsesBinaryProtocol();
+
+  // Returns "DevTools" | "Extension" | "RemoteDebugger" | "Other", which is
+  // used to emit to the correct UMA histogram.
+  virtual std::string GetTypeForMetrics();
 };
 
 }  // namespace content
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index a3a1d3e03..0179bbb 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -150,7 +150,6 @@
     "resource_usage_reporter_type_converters.cc",
     "resource_usage_reporter_type_converters.h",
     "result_codes.h",
-    "sandbox_init.h",
     "sandboxed_process_launcher_delegate.cc",
     "sandboxed_process_launcher_delegate.h",
     "socket_permission_request.h",
@@ -172,7 +171,10 @@
   ]
 
   if (is_win) {
-    sources += [ "font_cache_dispatcher_win.h" ]
+    sources += [
+      "font_cache_dispatcher_win.h",
+      "sandbox_init_win.h",
+    ]
   }
 
   if (is_android) {
@@ -253,6 +255,10 @@
     deps += [ "//content/public/android:jni" ]
   }
 
+  if (is_win) {
+    deps += [ "//sandbox/win:common" ]
+  }
+
   if (is_chromeos_ash) {
     public_deps += [ "//media/capture/video/chromeos/public" ]
   }
diff --git a/content/public/common/sandbox_init.h b/content/public/common/sandbox_init.h
deleted file mode 100644
index 7988123f..0000000
--- a/content/public/common/sandbox_init.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_PUBLIC_COMMON_SANDBOX_INIT_H_
-#define CONTENT_PUBLIC_COMMON_SANDBOX_INIT_H_
-
-#include <memory>
-
-#include "base/files/scoped_file.h"
-#include "base/process/launch.h"
-#include "base/process/process.h"
-#include "base/process/process_handle.h"
-#include "build/build_config.h"
-#include "content/common/content_export.h"
-
-namespace base {
-class CommandLine;
-}
-
-namespace sandbox {
-namespace bpf_dsl {
-class Policy;
-}  // namespace bpf_dsl
-namespace mojom {
-enum class Sandbox;
-}  // namespace mojom
-struct SandboxInterfaceInfo;
-enum ResultCode : int;
-}  // namespace sandbox
-
-namespace content {
-class SandboxedProcessLauncherDelegate;
-
-#if defined(OS_WIN)
-
-// Initialize the sandbox of the given |sandbox_type|. Although The browser
-// process is not sandboxed, this also needs to be called because it will
-// initialize the broker code.
-//
-// Returns true if the sandbox was initialized succesfully, false if an error
-// occurred.  If process_type isn't one that needs sandboxing true is always
-// returned.
-CONTENT_EXPORT bool InitializeSandbox(
-    sandbox::mojom::Sandbox sandbox_type,
-    sandbox::SandboxInterfaceInfo* sandbox_info);
-
-// Launch a sandboxed process. |delegate| may be NULL. If |delegate| is non-NULL
-// then it just has to outlive this method call. |handles_to_inherit| is a list
-// of handles for the child process to inherit. The caller retains ownership of
-// the handles.
-//
-// Note that calling this function does not always create a sandboxed process,
-// as the process might be unsandboxed depending on the behavior of the
-// delegate, the command line of the caller, and the command line of the target.
-CONTENT_EXPORT sandbox::ResultCode StartSandboxedProcess(
-    SandboxedProcessLauncherDelegate* delegate,
-    const base::CommandLine& target_command_line,
-    const base::HandlesToInheritVector& handles_to_inherit,
-    base::Process* process);
-
-#elif defined(OS_LINUX) || defined(OS_CHROMEOS)
-
-// Initialize a seccomp-bpf sandbox. |policy| may not be NULL.
-// If an existing layer of sandboxing is present that would prevent access to
-// /proc, |proc_fd| must be a valid file descriptor to /proc/.
-// Returns true if the sandbox has been properly engaged.
-CONTENT_EXPORT bool InitializeSandbox(
-    std::unique_ptr<sandbox::bpf_dsl::Policy> policy,
-    base::ScopedFD proc_fd);
-
-// Return a "baseline" policy. This is used by other modules to implement a
-// policy that is derived from the baseline.
-CONTENT_EXPORT std::unique_ptr<sandbox::bpf_dsl::Policy>
-GetBPFSandboxBaselinePolicy();
-#endif  // defined(OS_LINUX) || defined(OS_CHROMEOS)
-
-}  // namespace content
-
-#endif  // CONTENT_PUBLIC_COMMON_SANDBOX_INIT_H_
diff --git a/content/public/common/sandbox_init_win.h b/content/public/common/sandbox_init_win.h
new file mode 100644
index 0000000..ba4da80
--- /dev/null
+++ b/content/public/common/sandbox_init_win.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_COMMON_SANDBOX_INIT_WIN_H_
+#define CONTENT_PUBLIC_COMMON_SANDBOX_INIT_WIN_H_
+
+#include "base/process/launch.h"
+#include "content/common/content_export.h"
+#include "sandbox/win/src/sandbox_types.h"
+
+namespace base {
+class CommandLine;
+class Process;
+}  // namespace base
+
+namespace content {
+
+class SandboxedProcessLauncherDelegate;
+
+// Launch a sandboxed process. |delegate| may be NULL. If |delegate| is non-NULL
+// then it just has to outlive this method call. |handles_to_inherit| is a list
+// of handles for the child process to inherit. The caller retains ownership of
+// the handles.
+//
+// Note that calling this function does not always create a sandboxed process,
+// as the process might be unsandboxed depending on the behavior of the
+// delegate, the command line of the caller, and the command line of the target.
+CONTENT_EXPORT sandbox::ResultCode StartSandboxedProcess(
+    SandboxedProcessLauncherDelegate* delegate,
+    const base::CommandLine& target_command_line,
+    const base::HandlesToInheritVector& handles_to_inherit,
+    base::Process* process);
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_COMMON_SANDBOX_INIT_WIN_H_
diff --git a/content/public/test/test_launcher.cc b/content/public/test/test_launcher.cc
index 56878dc..ad0dbb8d 100644
--- a/content/public/test/test_launcher.cc
+++ b/content/public/test/test_launcher.cc
@@ -41,7 +41,6 @@
 #include "content/public/app/content_main_delegate.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/sandbox_init.h"
 #include "gpu/config/gpu_switches.h"
 #include "net/base/escape.h"
 #include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
diff --git a/content/renderer/renderer_main_platform_delegate_linux.cc b/content/renderer/renderer_main_platform_delegate_linux.cc
index 91b45bea..2db00ed 100644
--- a/content/renderer/renderer_main_platform_delegate_linux.cc
+++ b/content/renderer/renderer_main_platform_delegate_linux.cc
@@ -12,7 +12,6 @@
 #include "base/files/file_util.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/sandbox_init.h"
 #include "sandbox/policy/sandbox.h"
 #include "sandbox/policy/sandbox_type.h"
 
diff --git a/content/utility/utility_main.cc b/content/utility/utility_main.cc
index bac1019b..3c9d2bc 100644
--- a/content/utility/utility_main.cc
+++ b/content/utility/utility_main.cc
@@ -19,7 +19,6 @@
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/main_function_params.h"
-#include "content/public/common/sandbox_init.h"
 #include "content/public/utility/content_utility_client.h"
 #include "content/utility/utility_thread_impl.h"
 #include "printing/buildflags/buildflags.h"
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index e20bd47..c22f0f4 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -437,6 +437,8 @@
   if (force_clear_browsing_data || (is_managed && is_first_setup_complete)) {
     delegate_->ClearBrowsingData(completion);
   } else if (completion) {
+    // TODO(crbug.com/1277841): Needs to call completion on the next loop cycle
+    // to be more consistent with the clear browsing data completion.
     completion();
   }
 }
diff --git a/ios/chrome/browser/ui/authentication/signin/advanced_settings_signin/advanced_settings_signin_egtest.mm b/ios/chrome/browser/ui/authentication/signin/advanced_settings_signin/advanced_settings_signin_egtest.mm
index bdce35c..af423e81 100644
--- a/ios/chrome/browser/ui/authentication/signin/advanced_settings_signin/advanced_settings_signin_egtest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/advanced_settings_signin/advanced_settings_signin_egtest.mm
@@ -46,6 +46,20 @@
 // Timeout in seconds to wait for asynchronous sync operations.
 const NSTimeInterval kSyncOperationTimeout = 5.0;
 
+// Waits for the settings done button to be enabled.
+void WaitForSettingDoneButton() {
+  ConditionBlock condition = ^{
+    NSError* error = nil;
+    [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
+        assertWithMatcher:grey_sufficientlyVisible()
+                    error:&error];
+    return error == nil;
+  };
+  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
+                 base::test::ios::kWaitForClearBrowsingDataTimeout, condition),
+             @"Settings done button not visible");
+}
+
 }  // namespace
 
 // Interaction tests for advanced settings sign-in.
@@ -119,6 +133,7 @@
       selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
                                    IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON)]
       performAction:grey_tap()];
+  WaitForSettingDoneButton();
 
   // Verify signed out.
   [SigninEarlGrey verifySignedOut];
@@ -165,6 +180,7 @@
       selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
                                    IDS_IOS_SIGNOUT_DIALOG_KEEP_DATA_BUTTON)]
       performAction:grey_tap()];
+  WaitForSettingDoneButton();
 
   // Verify signed out.
   [SigninEarlGrey verifySignedOut];
diff --git a/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.h b/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.h
index 211e8bc..bf45917 100644
--- a/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.h
+++ b/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.h
@@ -11,18 +11,28 @@
 #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
 
 class Browser;
+@class SignoutActionSheetCoordinator;
 
 // Delegate that handles user interactions with sign-out action sheet.
 @protocol SignoutActionSheetCoordinatorDelegate
 
-// Called when the user has selected a data retention strategy, either to
-// clear or keep their data, before the strategy is implemented on signout.
-- (void)didSelectSignoutDataRetentionStrategy;
+// Called when the sign-out flow is in progress. The UI needs to be blocked
+// until the sign-out flow is done.
+- (void)signoutActionSheetCoordinatorPreventUserInteraction:
+    (SignoutActionSheetCoordinator*)coordinator;
+
+// Called when the sign-out flow is done. The UI can be unblocked.
+- (void)signoutActionSheetCoordinatorAllowUserInteraction:
+    (SignoutActionSheetCoordinator*)coordinator;
 
 @end
 
 // Displays sign-out action sheet with options to clear or keep user data
 // on the device. The user must be signed-in to use these actions.
+// The owner is responsible to block the UI, when the sign-out flow is in
+// progress.
+// The UI needs to be blocked and unblocked using methods from
+// SignoutActionSheetCoordinatorDelegate.
 @interface SignoutActionSheetCoordinator : ChromeCoordinator
 
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
diff --git a/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.mm b/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.mm
index e90c506..ab6baae0 100644
--- a/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.mm
@@ -283,16 +283,16 @@
     return;
   }
 
-  [self.delegate didSelectSignoutDataRetentionStrategy];
+  [self.delegate signoutActionSheetCoordinatorPreventUserInteraction:self];
 
-  [self.baseViewController.view setUserInteractionEnabled:NO];
   __weak SignoutActionSheetCoordinator* weakSelf = self;
   self.authenticationService->SignOut(
       signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, forceClearData, ^{
         if (!weakSelf) {
           return;
         }
-        [weakSelf.baseViewController.view setUserInteractionEnabled:YES];
+        [weakSelf.delegate
+            signoutActionSheetCoordinatorAllowUserInteraction:weakSelf];
         weakSelf.completion(YES);
       });
   // Get UMA metrics on the usage of different options for signout available
diff --git a/ios/chrome/browser/ui/settings/content_settings/BUILD.gn b/ios/chrome/browser/ui/settings/content_settings/BUILD.gn
index 3e6e6b61..15d863d9 100644
--- a/ios/chrome/browser/ui/settings/content_settings/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/content_settings/BUILD.gn
@@ -66,9 +66,12 @@
   sources = [
     "block_popups_table_view_controller_unittest.mm",
     "content_settings_table_view_controller_unittest.mm",
+    "default_page_mode_mediator_unittest.mm",
+    "default_page_mode_table_view_controller_unittest.mm",
   ]
   deps = [
     ":content_settings",
+    ":content_settings_ui",
     "//base/test:test_support",
     "//components/content_settings/core/browser",
     "//ios/chrome/app/strings",
@@ -79,6 +82,7 @@
     "//ios/chrome/browser/ui/table_view/cells",
     "//ios/web/public/test",
     "//testing/gtest",
+    "//third_party/ocmock",
     "//ui/base",
   ]
 }
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator_unittest.mm b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator_unittest.mm
new file mode 100644
index 0000000..11d8621
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator_unittest.mm
@@ -0,0 +1,71 @@
+// 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 "ios/chrome/browser/ui/settings/content_settings/default_page_mode_mediator.h"
+
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
+#import "ios/chrome/browser/ui/settings/content_settings/default_page_mode_consumer.h"
+#include "ios/web/public/test/web_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest_mac.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#include "third_party/ocmock/gtest_support.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+class DefaultPageModeMediatorTest : public PlatformTest {
+ protected:
+  DefaultPageModeMediatorTest() {
+    TestChromeBrowserState::Builder test_cbs_builder;
+    chrome_browser_state_ = test_cbs_builder.Build();
+  }
+
+  web::WebTaskEnvironment task_environment_;
+  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
+};
+
+// Tests that the pref and the mediator are playing nicely together.
+TEST_F(DefaultPageModeMediatorTest, TestPref) {
+  scoped_refptr<HostContentSettingsMap> settings_map(
+      ios::HostContentSettingsMapFactory::GetForBrowserState(
+          chrome_browser_state_.get()));
+  settings_map->SetContentSettingCustomScope(
+      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REQUEST_DESKTOP_SITE, CONTENT_SETTING_BLOCK);
+
+  DefaultPageModeMediator* mediator =
+      [[DefaultPageModeMediator alloc] initWithSettingsMap:settings_map.get()];
+
+  // Check that the consumer is correctly updated when set.
+  id consumer = OCMProtocolMock(@protocol(DefaultPageModeConsumer));
+  OCMExpect([consumer setDefaultPageMode:DefaultPageModeMobile]);
+
+  mediator.consumer = consumer;
+
+  EXPECT_OCMOCK_VERIFY(consumer);
+
+  // Check that the consumer is correctly updated when the pref is changed.
+  OCMExpect([consumer setDefaultPageMode:DefaultPageModeDesktop]);
+
+  settings_map->SetContentSettingCustomScope(
+      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REQUEST_DESKTOP_SITE, CONTENT_SETTING_ALLOW);
+
+  EXPECT_OCMOCK_VERIFY(consumer);
+
+  // Check that the pref is correctly updated when the mediator is asked.
+  ASSERT_EQ(CONTENT_SETTING_ALLOW,
+            settings_map->GetContentSetting(
+                GURL(), GURL(), ContentSettingsType::REQUEST_DESKTOP_SITE));
+  [mediator setDefaultMode:DefaultPageModeMobile];
+
+  EXPECT_EQ(CONTENT_SETTING_BLOCK,
+            settings_map->GetContentSetting(
+                GURL(), GURL(), ContentSettingsType::REQUEST_DESKTOP_SITE));
+}
diff --git a/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller_unittest.mm
new file mode 100644
index 0000000..c6c086e
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller_unittest.mm
@@ -0,0 +1,85 @@
+// 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 "ios/chrome/browser/ui/settings/content_settings/default_page_mode_table_view_controller.h"
+
+#import "base/mac/foundation_util.h"
+#import "ios/chrome/browser/ui/table_view/chrome_table_view_controller_test.h"
+#include "testing/gtest_mac.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+class DefaultPageModeTableViewControllerTest
+    : public ChromeTableViewControllerTest {
+ protected:
+  ChromeTableViewController* InstantiateController() override {
+    return [[DefaultPageModeTableViewController alloc]
+        initWithStyle:UITableViewStyleGrouped];
+  }
+};
+
+// Tests that there are 2 items in the Table View.
+TEST_F(DefaultPageModeTableViewControllerTest, TestItems) {
+  CreateController();
+  CheckController();
+  CheckTitle(nil);
+
+  ASSERT_EQ(1, NumberOfSections());
+  ASSERT_EQ(2, NumberOfItemsInSection(0));
+  CheckTextCellText(@"TEST - Mobile", 0, 0);
+  CheckTextCellText(@"TEST - Desktop", 0, 1);
+
+  CheckAccessoryType(UITableViewCellAccessoryNone, 0, 0);
+  CheckAccessoryType(UITableViewCellAccessoryNone, 0, 1);
+}
+
+// Tests that the checkmark gets correctly updated when set before the model is
+// loaded.
+TEST_F(DefaultPageModeTableViewControllerTest, TestCheckmarkAtLoad) {
+  // Load the controller manually as this is testing setting the DefaultPageMode
+  // before the model is loaded.
+  DefaultPageModeTableViewController* controller =
+      [[DefaultPageModeTableViewController alloc]
+          initWithStyle:UITableViewStyleGrouped];
+
+  [controller setDefaultPageMode:DefaultPageModeDesktop];
+
+  [controller loadModel];
+  // Force the tableView to be built.
+  ASSERT_TRUE([controller view]);
+
+  UITableViewCellAccessoryType first_accesory =
+      [controller.tableViewModel
+          itemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]
+          .accessoryType;
+  EXPECT_EQ(UITableViewCellAccessoryNone, first_accesory);
+  UITableViewCellAccessoryType second_accesory =
+      [controller.tableViewModel
+          itemAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]]
+          .accessoryType;
+  EXPECT_EQ(UITableViewCellAccessoryCheckmark, second_accesory);
+}
+
+// Tests that the checkmark gets correctly updated.
+TEST_F(DefaultPageModeTableViewControllerTest, TestCheckmark) {
+  ChromeTableViewController* chrome_controller = controller();
+  DefaultPageModeTableViewController* controller =
+      base::mac::ObjCCastStrict<DefaultPageModeTableViewController>(
+          chrome_controller);
+
+  CheckAccessoryType(UITableViewCellAccessoryNone, 0, 0);
+  CheckAccessoryType(UITableViewCellAccessoryNone, 0, 1);
+
+  [controller setDefaultPageMode:DefaultPageModeMobile];
+
+  CheckAccessoryType(UITableViewCellAccessoryCheckmark, 0, 0);
+  CheckAccessoryType(UITableViewCellAccessoryNone, 0, 1);
+
+  [controller setDefaultPageMode:DefaultPageModeDesktop];
+
+  CheckAccessoryType(UITableViewCellAccessoryNone, 0, 0);
+  CheckAccessoryType(UITableViewCellAccessoryCheckmark, 0, 1);
+}
diff --git a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
index 87c26e2d..d9f5db7 100644
--- a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
@@ -596,12 +596,6 @@
                             view:itemView];
   __weak AccountsTableViewController* weakSelf = self;
   self.signoutCoordinator.completion = ^(BOOL success) {
-    if (success) {
-      // Allow user interaction only didn't cancel the dialog.
-      // if -[<SignoutActionSheetCoordinatorDelegate>
-      // didSelectSignoutDataRetentionStrategy] has been called.
-      [weakSelf allowUserInteraction];
-    }
     [weakSelf.signoutCoordinator stop];
     weakSelf.signoutCoordinator = nil;
     if (success) {
@@ -765,11 +759,17 @@
 
 #pragma mark - SignoutActionSheetCoordinatorDelegate
 
-- (void)didSelectSignoutDataRetentionStrategy {
+- (void)signoutActionSheetCoordinatorPreventUserInteraction:
+    (SignoutActionSheetCoordinator*)coordinator {
   _authenticationOperationInProgress = YES;
   [self preventUserInteraction];
 }
 
+- (void)signoutActionSheetCoordinatorAllowUserInteraction:
+    (SignoutActionSheetCoordinator*)coordinator {
+  [self allowUserInteraction];
+}
+
 #pragma mark - TableViewLinkHeaderFooterItemDelegate
 
 - (void)view:(TableViewLinkHeaderFooterView*)view didTapLinkURL:(CrURL*)URL {
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.mm
index b6096005..c05abeb 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.mm
@@ -49,7 +49,8 @@
 
 @interface GoogleServicesSettingsCoordinator () <
     GoogleServicesSettingsCommandHandler,
-    GoogleServicesSettingsViewControllerPresentationDelegate>
+    GoogleServicesSettingsViewControllerPresentationDelegate,
+    SignoutActionSheetCoordinatorDelegate>
 
 // Google services settings mediator.
 @property(nonatomic, strong) GoogleServicesSettingsMediator* mediator;
@@ -65,7 +66,7 @@
 // Action sheets that provides options for sign out.
 @property(nonatomic, strong) ActionSheetCoordinator* signOutCoordinator;
 @property(nonatomic, strong)
-    SignoutActionSheetCoordinator* dataRetentionStrategyCoordinator;
+    SignoutActionSheetCoordinator* signoutActionSheetCoordinator;
 @end
 
 @implementation GoogleServicesSettingsCoordinator
@@ -202,24 +203,26 @@
                                     completion:(signin_ui::CompletionCallback)
                                                    completion {
   DCHECK(completion);
-  self.dataRetentionStrategyCoordinator = [[SignoutActionSheetCoordinator alloc]
+  self.signoutActionSheetCoordinator = [[SignoutActionSheetCoordinator alloc]
       initWithBaseViewController:self.viewController
                          browser:self.browser
                             rect:targetRect
                             view:self.viewController.view];
   __weak GoogleServicesSettingsCoordinator* weakSelf = self;
-  self.dataRetentionStrategyCoordinator.completion = ^(BOOL success) {
-    completion(success);
-    [weakSelf.dataRetentionStrategyCoordinator stop];
-    weakSelf.dataRetentionStrategyCoordinator = nil;
+  self.signoutActionSheetCoordinator.delegate = self;
+  self.signoutActionSheetCoordinator.completion = ^(BOOL success) {
+    if (completion)
+      completion(success);
+    [weakSelf.signoutActionSheetCoordinator stop];
+    weakSelf.signoutActionSheetCoordinator = nil;
   };
-  [self.dataRetentionStrategyCoordinator start];
+  [self.signoutActionSheetCoordinator start];
 }
 
 // Signs the user out of Chrome, only clears data for managed accounts.
 - (void)signOutWithCompletion:(signin_ui::CompletionCallback)completion {
   DCHECK(completion);
-  [self.baseViewController.view setUserInteractionEnabled:NO];
+  [self.googleServicesSettingsViewController preventUserInteraction];
   __weak GoogleServicesSettingsCoordinator* weakSelf = self;
   self.authService->SignOut(
       signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS,
@@ -227,7 +230,7 @@
         if (!weakSelf) {
           return;
         }
-        weakSelf.baseViewController.view.userInteractionEnabled = YES;
+        [weakSelf.googleServicesSettingsViewController allowUserInteraction];
         completion(YES);
       });
 }
@@ -240,4 +243,16 @@
   [self.delegate googleServicesSettingsCoordinatorDidRemove:self];
 }
 
+#pragma mark - SignoutActionSheetCoordinatorDelegate
+
+- (void)signoutActionSheetCoordinatorPreventUserInteraction:
+    (SignoutActionSheetCoordinator*)coordinator {
+  [self.googleServicesSettingsViewController preventUserInteraction];
+}
+
+- (void)signoutActionSheetCoordinatorAllowUserInteraction:
+    (SignoutActionSheetCoordinator*)coordinator {
+  [self.googleServicesSettingsViewController allowUserInteraction];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_egtest.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_egtest.mm
index 1a7faa20c..24ee3358 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_egtest.mm
@@ -63,6 +63,20 @@
   }
 }
 
+// Waits for the settings done button to be enabled.
+void WaitForSettingDoneButton() {
+  ConditionBlock condition = ^{
+    NSError* error = nil;
+    [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
+        assertWithMatcher:grey_sufficientlyVisible()
+                    error:&error];
+    return error == nil;
+  };
+  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
+                 base::test::ios::kWaitForClearBrowsingDataTimeout, condition),
+             @"Settings done button not visible");
+}
+
 }  // namespace
 
 // Integration tests using the Google services settings screen.
@@ -322,6 +336,7 @@
   [[EarlGrey
       selectElementWithMatcher:grey_accessibilityID(kSettingsSignInCellId)]
       assertWithMatcher:grey_notVisible()];
+  WaitForSettingDoneButton();
 
   // Verify signed out.
   [SigninEarlGrey verifySignedOut];
@@ -348,15 +363,7 @@
 // Tests that disabling the "Allow Chrome sign-in" > "Clear Data" option blocks
 // the user from signing in to Chrome through the promo sign-in until it is
 // re-enabled.
-// TODO(crbug.com/1277841): Flaky on iOS simulator.
-#if TARGET_IPHONE_SIMULATOR
-#define MAYBE_testToggleAllowChromeSigninWithPromoSigninClearData \
-  DISABLED_testToggleAllowChromeSigninWithPromoSigninClearData
-#else
-#define MAYBE_testToggleAllowChromeSigninWithPromoSigninClearData \
-  testToggleAllowChromeSigninWithPromoSigninClearData
-#endif
-- (void)MAYBE_testToggleAllowChromeSigninWithPromoSigninClearData {
+- (void)testToggleAllowChromeSigninWithPromoSigninClearData {
   // User is signed-in and syncing.
   FakeChromeIdentity* fakeIdentity = [SigninEarlGrey fakeIdentity1];
   [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity];
@@ -384,6 +391,7 @@
       selectElementWithMatcher:ButtonWithAccessibilityLabelId(
                                    IDS_IOS_SIGNOUT_DIALOG_CLEAR_DATA_BUTTON)]
       performAction:grey_tap()];
+  WaitForSettingDoneButton();
 
   // Verify that sign-in is disabled.
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
@@ -451,6 +459,7 @@
       selectElementWithMatcher:ButtonWithAccessibilityLabelId(
                                    IDS_IOS_SIGNOUT_DIALOG_KEEP_DATA_BUTTON)]
       performAction:grey_tap()];
+  WaitForSettingDoneButton();
 
   // Verify that sign-in is disabled.
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
diff --git a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
index 1ffe297..2d41d5e 100644
--- a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
@@ -49,8 +49,9 @@
 
 @interface ManageSyncSettingsCoordinator () <
     ManageSyncSettingsCommandHandler,
-    SyncErrorSettingsCommandHandler,
     ManageSyncSettingsTableViewControllerPresentationDelegate,
+    SignoutActionSheetCoordinatorDelegate,
+    SyncErrorSettingsCommandHandler,
     SyncObserverModelBridge> {
   // Sync observer.
   std::unique_ptr<SyncObserverBridge> _syncObserver;
@@ -69,7 +70,9 @@
 @property(nonatomic, copy) ios::DismissASMViewControllerBlock
     dismissWebAndAppSettingDetailsControllerBlock;
 // Displays the sign-out options for a syncing user.
-@property(nonatomic, strong) SignoutActionSheetCoordinator* signOutCoordinator;
+@property(nonatomic, strong)
+    SignoutActionSheetCoordinator* signoutActionSheetCoordinator;
+@property(nonatomic, assign) BOOL signOutFlowInProgress;
 
 @end
 
@@ -192,18 +195,33 @@
 }
 
 - (void)showTurnOffSyncOptionsFromTargetRect:(CGRect)targetRect {
-  self.signOutCoordinator = [[SignoutActionSheetCoordinator alloc]
+  self.signoutActionSheetCoordinator = [[SignoutActionSheetCoordinator alloc]
       initWithBaseViewController:self.viewController
                          browser:self.browser
                             rect:targetRect
                             view:self.viewController.view];
+  self.signoutActionSheetCoordinator.delegate = self;
   __weak ManageSyncSettingsCoordinator* weakSelf = self;
-  self.signOutCoordinator.completion = ^(BOOL success) {
+  self.signoutActionSheetCoordinator.completion = ^(BOOL success) {
     if (success) {
       [weakSelf closeManageSyncSettings];
     }
   };
-  [self.signOutCoordinator start];
+  [self.signoutActionSheetCoordinator start];
+}
+
+#pragma mark - SignoutActionSheetCoordinatorDelegate
+
+- (void)signoutActionSheetCoordinatorPreventUserInteraction:
+    (SignoutActionSheetCoordinator*)coordinator {
+  self.signOutFlowInProgress = YES;
+  [self.viewController preventUserInteraction];
+}
+
+- (void)signoutActionSheetCoordinatorAllowUserInteraction:
+    (SignoutActionSheetCoordinator*)coordinator {
+  [self.viewController allowUserInteraction];
+  self.signOutFlowInProgress = NO;
 }
 
 #pragma mark - SyncErrorSettingsCommandHandler
@@ -273,6 +291,9 @@
 #pragma mark - SyncObserverModelBridge
 
 - (void)onSyncStateChanged {
+  if (self.signOutFlowInProgress) {
+    return;
+  }
   syncer::SyncService::DisableReasonSet disableReasons =
       self.syncService->GetDisableReasons();
   syncer::SyncService::DisableReasonSet userChoiceDisableReason =
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_egtest.mm b/ios/chrome/browser/ui/toolbar/toolbar_egtest.mm
index 456e913..3ac408f 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_egtest.mm
+++ b/ios/chrome/browser/ui/toolbar/toolbar_egtest.mm
@@ -347,7 +347,8 @@
 }
 
 // Tests typing in the omnibox.
-- (void)testToolbarOmniboxTyping {
+// TODO(crbug.com/1283854): Fix test.
+- (void)DISABLED_testToolbarOmniboxTyping {
   // TODO(crbug.com/642559): Enable this test for iPad when typing bug is fixed.
   if ([ChromeEarlGrey isIPadIdiom]) {
     EARL_GREY_TEST_DISABLED(@"Disabled for iPad due to a simulator bug.");
diff --git a/ios/chrome/browser/web/BUILD.gn b/ios/chrome/browser/web/BUILD.gn
index 8cb9674..15be8c73 100644
--- a/ios/chrome/browser/web/BUILD.gn
+++ b/ios/chrome/browser/web/BUILD.gn
@@ -214,6 +214,7 @@
     "//base",
     "//components/autofill/ios/browser",
     "//components/autofill/ios/form_util:form_handler_feature",
+    "//components/content_settings/core/browser",
     "//components/dom_distiller/core",
     "//components/google/core/common",
     "//components/password_manager/core/common",
@@ -228,6 +229,7 @@
     "//ios/chrome/browser",
     "//ios/chrome/browser:browser_impl",
     "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/content_settings",
     "//ios/chrome/browser/link_to_text",
     "//ios/chrome/browser/ntp",
     "//ios/chrome/browser/passwords",
diff --git a/ios/chrome/browser/web/chrome_web_client.h b/ios/chrome/browser/web/chrome_web_client.h
index 71bc6f1..08e5a9e6 100644
--- a/ios/chrome/browser/web/chrome_web_client.h
+++ b/ios/chrome/browser/web/chrome_web_client.h
@@ -53,7 +53,7 @@
   UIView* GetWindowedContainer() override;
   bool EnableLongPressAndForceTouchHandling() const override;
   bool EnableLongPressUIContextMenu() const override;
-  web::UserAgentType GetDefaultUserAgent(id<UITraitEnvironment> web_view,
+  web::UserAgentType GetDefaultUserAgent(web::WebState* web_state,
                                          const GURL& url) override;
   bool RestoreSessionFromCache(web::WebState* web_state) const override;
   void CleanupNativeRestoreURLs(web::WebState* web_state) const override;
diff --git a/ios/chrome/browser/web/chrome_web_client.mm b/ios/chrome/browser/web/chrome_web_client.mm
index cee61542..97acf5e 100644
--- a/ios/chrome/browser/web/chrome_web_client.mm
+++ b/ios/chrome/browser/web/chrome_web_client.mm
@@ -17,6 +17,7 @@
 #import "components/autofill/ios/browser/autofill_java_script_feature.h"
 #import "components/autofill/ios/browser/suggestion_controller_java_script_feature.h"
 #import "components/autofill/ios/form_util/form_handlers_java_script_feature.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/dom_distiller/core/url_constants.h"
 #include "components/google/core/common/google_util.h"
 #include "components/password_manager/core/common/password_manager_features.h"
@@ -28,6 +29,7 @@
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/chrome_switches.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
+#include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "ios/chrome/browser/ios_chrome_main_parts.h"
 #import "ios/chrome/browser/link_to_text/link_to_text_java_script_feature.h"
 #import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
@@ -402,9 +404,16 @@
 }
 
 web::UserAgentType ChromeWebClient::GetDefaultUserAgent(
-    id<UITraitEnvironment> web_view,
+    web::WebState* web_state,
     const GURL& url) {
-  return web::UserAgentType::MOBILE;
+  ChromeBrowserState* browser_state =
+      ChromeBrowserState::FromBrowserState(web_state->GetBrowserState());
+  HostContentSettingsMap* settings_map =
+      ios::HostContentSettingsMapFactory::GetForBrowserState(browser_state);
+  ContentSetting setting = settings_map->GetContentSetting(
+      url, url, ContentSettingsType::REQUEST_DESKTOP_SITE);
+  return (setting == CONTENT_SETTING_ALLOW) ? web::UserAgentType::DESKTOP
+                                            : web::UserAgentType::MOBILE;
 }
 
 bool ChromeWebClient::RestoreSessionFromCache(web::WebState* web_state) const {
diff --git a/ios/chrome/browser/web/chrome_web_client_unittest.mm b/ios/chrome/browser/web/chrome_web_client_unittest.mm
index dc71264..721312ce2 100644
--- a/ios/chrome/browser/web/chrome_web_client_unittest.mm
+++ b/ios/chrome/browser/web/chrome_web_client_unittest.mm
@@ -17,6 +17,7 @@
 #include "base/test/task_environment.h"
 #include "base/version.h"
 #include "components/captive_portal/core/captive_portal_detector.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/lookalikes/core/lookalike_url_util.h"
 #import "components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h"
 #include "components/security_interstitials/core/unsafe_resource.h"
@@ -24,6 +25,7 @@
 #include "components/version_info/version_info.h"
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
+#include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
 #import "ios/chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
 #import "ios/chrome/browser/safe_browsing/safe_browsing_error.h"
 #import "ios/chrome/browser/safe_browsing/safe_browsing_unsafe_resource_container.h"
@@ -487,76 +489,22 @@
 // Tests the default user agent for different views.
 TEST_F(ChromeWebClientTest, DefaultUserAgent) {
   ChromeWebClient web_client;
-  const GURL google_url = GURL("https://www.google.com/search?q=test");
-  const GURL non_google_url = GURL("http://wikipedia.org");
+  web::FakeWebState web_state;
+  web_state.SetBrowserState(browser_state());
 
-  UITraitCollection* regular_vertical_size_class = [UITraitCollection
-      traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
-  UITraitCollection* regular_horizontal_size_class = [UITraitCollection
-      traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
-  UITraitCollection* compact_vertical_size_class = [UITraitCollection
-      traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
-  UITraitCollection* compact_horizontal_size_class = [UITraitCollection
-      traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
+  scoped_refptr<HostContentSettingsMap> settings_map(
+      ios::HostContentSettingsMapFactory::GetForBrowserState(browser_state()));
+  settings_map->SetContentSettingCustomScope(
+      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REQUEST_DESKTOP_SITE, CONTENT_SETTING_BLOCK);
 
-  UIView* view = [[UIView alloc] init];
-  UITraitCollection* original_traits = view.traitCollection;
-
-  UITraitCollection* regular_regular =
-      [UITraitCollection traitCollectionWithTraitsFromCollections:@[
-        original_traits, regular_vertical_size_class,
-        regular_horizontal_size_class
-      ]];
-  UITraitCollection* regular_compact =
-      [UITraitCollection traitCollectionWithTraitsFromCollections:@[
-        original_traits, regular_vertical_size_class,
-        compact_horizontal_size_class
-      ]];
-  UITraitCollection* compact_regular =
-      [UITraitCollection traitCollectionWithTraitsFromCollections:@[
-        original_traits, compact_vertical_size_class,
-        regular_horizontal_size_class
-      ]];
-  UITraitCollection* compact_compact =
-      [UITraitCollection traitCollectionWithTraitsFromCollections:@[
-        original_traits, compact_vertical_size_class,
-        compact_horizontal_size_class
-      ]];
-
-  // Check that desktop is returned for Regular x Regular on non-Google URLs.
-  id mock_regular_regular_view = OCMClassMock([UIView class]);
-  OCMStub([mock_regular_regular_view traitCollection])
-      .andReturn(regular_regular);
   EXPECT_EQ(web::UserAgentType::MOBILE,
-            web_client.GetDefaultUserAgent(mock_regular_regular_view,
-                                           non_google_url));
+            web_client.GetDefaultUserAgent(&web_state, GURL()));
 
-  EXPECT_EQ(
-      web::UserAgentType::MOBILE,
-      web_client.GetDefaultUserAgent(mock_regular_regular_view, google_url));
+  settings_map->SetContentSettingCustomScope(
+      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REQUEST_DESKTOP_SITE, CONTENT_SETTING_ALLOW);
 
-  // Check that mobile is returned for all other combinations.
-  id mock_regular_compact_view = OCMClassMock([UIView class]);
-  OCMStub([mock_regular_compact_view traitCollection])
-      .andReturn(regular_compact);
-  EXPECT_EQ(web::UserAgentType::MOBILE,
-            web_client.GetDefaultUserAgent(mock_regular_compact_view,
-                                           non_google_url));
-  EXPECT_EQ(
-      web::UserAgentType::MOBILE,
-      web_client.GetDefaultUserAgent(mock_regular_regular_view, google_url));
-
-  id mock_compact_regular_view = OCMClassMock([UIView class]);
-  OCMStub([mock_compact_regular_view traitCollection])
-      .andReturn(compact_regular);
-  EXPECT_EQ(web::UserAgentType::MOBILE,
-            web_client.GetDefaultUserAgent(mock_compact_regular_view,
-                                           non_google_url));
-
-  id mock_compact_compact_view = OCMClassMock([UIView class]);
-  OCMStub([mock_compact_compact_view traitCollection])
-      .andReturn(compact_compact);
-  EXPECT_EQ(web::UserAgentType::MOBILE,
-            web_client.GetDefaultUserAgent(mock_compact_compact_view,
-                                           non_google_url));
+  EXPECT_EQ(web::UserAgentType::DESKTOP,
+            web_client.GetDefaultUserAgent(&web_state, GURL()));
 }
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index d0ff8ac..e0bfa8a 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -994,7 +994,7 @@
 + (BOOL)isMobileModeByDefault {
   web::UserAgentType webClientUserAgent =
       web::GetWebClient()->GetDefaultUserAgent(
-          chrome_test_util::GetCurrentWebState()->GetView(), GURL());
+          chrome_test_util::GetCurrentWebState(), GURL());
 
   return webClientUserAgent == web::UserAgentType::MOBILE;
 }
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index e14430a..0612ab3 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-f63f2edb6874e0a4b3b702e65dbdb3ca6b4a0aea
\ No newline at end of file
+3ed76e1e7f68059116515ea3d5458fb25e957ffe
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 5556aa8..fe13f4fa4 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-ca8dcef7b32ce976d40eb19714d2dba9fe2c2c31
\ No newline at end of file
+818060cfda92576f4f5ed849ea96ee99129de793
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 22fc566..fcf52e9 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-1bf0118b3ef48151b3d96d2ffe9ddcd3e07637f6
\ No newline at end of file
+5f8c1fc176aabf392f901bd1e3251b443c9690dd
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index 879725a..445ff92a 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-1496aa3b5d9ab796178f0dc1641a1365cbbe26eb
\ No newline at end of file
+aa26907406759e80ae110dea2bd0bf245c46057d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index b80eba0..ddcadfb 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-82f546479df75c3e98b7016f70b86b2169a7007e
\ No newline at end of file
+3f476d4eba2040936d2b6ac10b97a2456ea2f99e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index f323bfa..efa6caf 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-e271c3a7cb6695efd181b6a3f66deace0ef8a5da
\ No newline at end of file
+c5d3a43140ada37eccf4d5cb45a13218b3599d18
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index a953b11..b0592a2 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-afdb1c5919c686a066cb4638c89d19dcaeaba608
\ No newline at end of file
+ed568768b955cfd4e4efddc053d0670cb3794987
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 564e67e..f281b7f 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-e4083586e406fd9a924e7b330018df5f5398a0fe
\ No newline at end of file
+7971b92e5311c2f4f2adfa2bf84bc32b9c74af70
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 04b2df9e..2b6cbaa 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-cfdda9f3e5f2a40da3635dcaa156efed323f5162
\ No newline at end of file
+d31b4918f05bd8b0ff6f56944cb662797556d136
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 5c3f49c6..39e445f 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-963f4d88e9ebeb0b2eb75c2e5ffe834af5e7e60f
\ No newline at end of file
+8ddab959893f3dddf1315d8decc5719744efbe4b
\ No newline at end of file
diff --git a/ios/web/public/test/fakes/fake_web_client.h b/ios/web/public/test/fakes/fake_web_client.h
index 19714c47..28e32f9d 100644
--- a/ios/web/public/test/fakes/fake_web_client.h
+++ b/ios/web/public/test/fakes/fake_web_client.h
@@ -52,7 +52,7 @@
                         int64_t navigation_id,
                         base::OnceCallback<void(NSString*)> callback) override;
   UIView* GetWindowedContainer() override;
-  UserAgentType GetDefaultUserAgent(id<UITraitEnvironment> web_view,
+  UserAgentType GetDefaultUserAgent(web::WebState* web_state,
                                     const GURL& url) override;
 
   // Sets |plugin_not_supported_text_|.
diff --git a/ios/web/public/test/fakes/fake_web_client.mm b/ios/web/public/test/fakes/fake_web_client.mm
index f74e649a..eb4ee70 100644
--- a/ios/web/public/test/fakes/fake_web_client.mm
+++ b/ios/web/public/test/fakes/fake_web_client.mm
@@ -99,9 +99,8 @@
   return GetAnyKeyWindow().rootViewController.view;
 }
 
-UserAgentType FakeWebClient::GetDefaultUserAgent(
-    id<UITraitEnvironment> web_view,
-    const GURL& url) {
+UserAgentType FakeWebClient::GetDefaultUserAgent(web::WebState* web_state,
+                                                 const GURL& url) {
   return default_user_agent_;
 }
 
diff --git a/ios/web/public/web_client.h b/ios/web/public/web_client.h
index f4d6c795..6627c66 100644
--- a/ios/web/public/web_client.h
+++ b/ios/web/public/web_client.h
@@ -172,8 +172,8 @@
   virtual bool EnableLongPressUIContextMenu() const;
 
   // Returns the UserAgentType that should be used by default for the web
-  // content, based on the size class of |web_view| and the |url|.
-  virtual UserAgentType GetDefaultUserAgent(id<UITraitEnvironment> web_view,
+  // content, based on the |web_state|.
+  virtual UserAgentType GetDefaultUserAgent(web::WebState* web_state,
                                             const GURL& url);
 
   // Returns true if URL was restored via session restoration cache.
diff --git a/ios/web/web_client.mm b/ios/web/web_client.mm
index f4f7b36..511ba9e 100644
--- a/ios/web/web_client.mm
+++ b/ios/web/web_client.mm
@@ -111,7 +111,7 @@
 
 void WebClient::CleanupNativeRestoreURLs(web::WebState* web_state) const {}
 
-UserAgentType WebClient::GetDefaultUserAgent(id<UITraitEnvironment> web_view,
+UserAgentType WebClient::GetDefaultUserAgent(web::WebState* web_state,
                                              const GURL& url) {
   return UserAgentType::MOBILE;
 }
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 7994638..3a1705a 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -1534,7 +1534,7 @@
       item ? item->GetUserAgentType() : defaultUserAgent;
   if (userAgentType == web::UserAgentType::AUTOMATIC) {
     userAgentType =
-        web::GetWebClient()->GetDefaultUserAgent(_containerView, GURL());
+        web::GetWebClient()->GetDefaultUserAgent(self.webStateImpl, GURL());
   }
 
   return web::BuildWKWebView(CGRectZero, config,
diff --git a/ios/web/web_state/web_state_impl_realized_web_state.mm b/ios/web/web_state/web_state_impl_realized_web_state.mm
index 09a988bd4..65a60c8 100644
--- a/ios/web/web_state/web_state_impl_realized_web_state.mm
+++ b/ios/web/web_state/web_state_impl_realized_web_state.mm
@@ -378,11 +378,7 @@
 UserAgentType WebStateImpl::RealizedWebState::GetUserAgentForNextNavigation(
     const GURL& url) {
   if (user_agent_type_ == UserAgentType::AUTOMATIC) {
-    UIView* container = GetWebViewContainer();
-    if (!container) {
-      container = GetView();
-    }
-    return GetWebClient()->GetDefaultUserAgent(container, url);
+    return GetWebClient()->GetDefaultUserAgent(owner_, url);
   }
   return user_agent_type_;
 }
diff --git a/sandbox/win/src/sandbox_types.h b/sandbox/win/src/sandbox_types.h
index 2bb42163..bfbfc15 100644
--- a/sandbox/win/src/sandbox_types.h
+++ b/sandbox/win/src/sandbox_types.h
@@ -15,7 +15,7 @@
 //
 // Note: These codes are listed in a histogram and any new codes should be added
 // at the end. If the underlying type is changed then the forward declaration in
-// sandbox_init.h must be updated.
+// sandbox_init_win.h must be updated.
 //
 enum ResultCode : int {
   SBOX_ALL_OK = 0,
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 47048e650..1769c1e 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -1713,7 +1713,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R99-14396.0.0",
+        "cros_img": "atlas-release/R99-14435.0.0",
         "name": "lacros_all_tast_tests_ATLAS_LKGM",
         "resultdb": {
           "enable": true,
@@ -1728,7 +1728,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R98-14388.1.0",
+        "cros_img": "atlas-release/R98-14388.8.0",
         "name": "lacros_all_tast_tests_ATLAS_DEV",
         "resultdb": {
           "enable": true,
@@ -1743,7 +1743,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R97-14324.33.0",
+        "cros_img": "atlas-release/R97-14324.49.0",
         "name": "lacros_all_tast_tests_ATLAS_BETA",
         "resultdb": {
           "enable": true,
@@ -1773,7 +1773,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R99-14396.0.0",
+        "cros_img": "eve-release/R99-14435.0.0",
         "name": "lacros_all_tast_tests_EVE_LKGM",
         "resultdb": {
           "enable": true,
@@ -1788,7 +1788,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R98-14388.1.0",
+        "cros_img": "eve-release/R98-14388.8.0",
         "name": "lacros_all_tast_tests_EVE_DEV",
         "resultdb": {
           "enable": true,
@@ -1803,7 +1803,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R97-14324.33.0",
+        "cros_img": "eve-release/R97-14324.49.0",
         "name": "lacros_all_tast_tests_EVE_BETA",
         "resultdb": {
           "enable": true,
@@ -1878,7 +1878,7 @@
       {
         "args": [],
         "cros_board": "kevin",
-        "cros_img": "kevin-release/R99-14396.0.0",
+        "cros_img": "kevin-release/R99-14435.0.0",
         "name": "lacros_all_tast_tests_KEVIN_LKGM",
         "resultdb": {
           "enable": true,
@@ -1893,7 +1893,7 @@
       {
         "args": [],
         "cros_board": "kevin",
-        "cros_img": "kevin-release/R99-14396.0.0",
+        "cros_img": "kevin-release/R99-14435.0.0",
         "name": "ozone_unittests_KEVIN_LKGM",
         "resultdb": {
           "enable": true,
@@ -1907,7 +1907,7 @@
       {
         "args": [],
         "cros_board": "kevin",
-        "cros_img": "kevin-release/R99-14396.0.0",
+        "cros_img": "kevin-release/R99-14435.0.0",
         "name": "viz_unittests_KEVIN_LKGM",
         "resultdb": {
           "enable": true,
diff --git a/testing/buildbot/filters/fuchsia.content_browsertests.filter b/testing/buildbot/filters/fuchsia.content_browsertests.filter
index 06ca5213..cd3ff7e 100644
--- a/testing/buildbot/filters/fuchsia.content_browsertests.filter
+++ b/testing/buildbot/filters/fuchsia.content_browsertests.filter
@@ -98,3 +98,7 @@
 -All/BlockedSchemeNavigationBrowserTest.PDF_NavigationFromFrame_Block/*
 -All/BlockedSchemeNavigationBrowserTest.PDF_NavigationFromFrame_TopFrameHasBlockedScheme_Block/*
 -All/BlockedSchemeNavigationBrowserTest.PDF_Navigation_Block/*
+
+# crbug.com/1280308: Tests are passing on a NUC but failing on the ARM64 bots.
+-MSE_ClearKey/EncryptedMediaTest.Playback_VideoOnly_WebM_VP9Profile2/0
+-MSE_ClearKey/EncryptedMediaTest.Playback_VideoOnly_MP4_VP9Profile2/0
diff --git a/testing/buildbot/internal.chromeos.fyi.json b/testing/buildbot/internal.chromeos.fyi.json
index a78fd1df..79b423c 100644
--- a/testing/buildbot/internal.chromeos.fyi.json
+++ b/testing/buildbot/internal.chromeos.fyi.json
@@ -1096,7 +1096,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R99-14396.0.0",
+        "cros_img": "octopus-release/R99-14435.0.0",
         "name": "lacros_fyi_tast_tests_OCTOPUS_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1107,7 +1107,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R98-14388.1.0",
+        "cros_img": "octopus-release/R98-14388.8.0",
         "name": "lacros_fyi_tast_tests_OCTOPUS_DEV",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1118,7 +1118,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R97-14324.33.0",
+        "cros_img": "octopus-release/R97-14324.49.0",
         "name": "lacros_fyi_tast_tests_OCTOPUS_BETA",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1140,7 +1140,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R99-14396.0.0",
+        "cros_img": "octopus-release/R99-14435.0.0",
         "name": "ozone_unittests_OCTOPUS_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1150,7 +1150,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R98-14388.1.0",
+        "cros_img": "octopus-release/R98-14388.8.0",
         "name": "ozone_unittests_OCTOPUS_DEV",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1160,7 +1160,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R97-14324.33.0",
+        "cros_img": "octopus-release/R97-14324.49.0",
         "name": "ozone_unittests_OCTOPUS_BETA",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1188,7 +1188,7 @@
       {
         "args": [],
         "cros_board": "kevin",
-        "cros_img": "kevin-release/R99-14396.0.0",
+        "cros_img": "kevin-release/R99-14435.0.0",
         "name": "lacros_all_tast_tests_KEVIN_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && \"dep:lacros_unstable\")",
@@ -1199,7 +1199,7 @@
       {
         "args": [],
         "cros_board": "kevin",
-        "cros_img": "kevin-release/R99-14396.0.0",
+        "cros_img": "kevin-release/R99-14435.0.0",
         "name": "ozone_unittests_KEVIN_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1209,7 +1209,7 @@
       {
         "args": [],
         "cros_board": "kevin",
-        "cros_img": "kevin-release/R99-14396.0.0",
+        "cros_img": "kevin-release/R99-14435.0.0",
         "name": "viz_unittests_KEVIN_LKGM",
         "swarming": {},
         "test": "viz_unittests",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index f9daaf6..2170493 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -563,8 +563,8 @@
   'CROS_ATLAS_LKGM': {
     'skylab': {
       'cros_board': 'atlas',
-      'cros_chrome_version': '99.0.4761.0',
-      'cros_img': 'atlas-release/R99-14396.0.0',
+      'cros_chrome_version': '99.0.4801.0',
+      'cros_img': 'atlas-release/R99-14435.0.0',
     },
     'enabled': True,
     'identifier': 'ATLAS_LKGM',
@@ -572,8 +572,8 @@
   'CROS_ATLAS_DEV': {
     'skylab': {
       'cros_board': 'atlas',
-      'cros_chrome_version': '98.0.4758.0',
-      'cros_img': 'atlas-release/R98-14388.1.0',
+      'cros_chrome_version': '98.0.4758.14',
+      'cros_img': 'atlas-release/R98-14388.8.0',
     },
     'enabled': True,
     'identifier': 'ATLAS_DEV',
@@ -581,8 +581,8 @@
   'CROS_ATLAS_BETA': {
     'skylab': {
       'cros_board': 'atlas',
-      'cros_chrome_version': '97.0.4692.27',
-      'cros_img': 'atlas-release/R97-14324.33.0',
+      'cros_chrome_version': '97.0.4692.63',
+      'cros_img': 'atlas-release/R97-14324.49.0',
     },
     'enabled': True,
     'identifier': 'ATLAS_BETA',
@@ -599,8 +599,8 @@
   'CROS_EVE_LKGM': {
     'skylab': {
       'cros_board': 'eve',
-      'cros_chrome_version': '99.0.4761.0',
-      'cros_img': 'eve-release/R99-14396.0.0',
+      'cros_chrome_version': '99.0.4801.0',
+      'cros_img': 'eve-release/R99-14435.0.0',
     },
     'enabled': True,
     'identifier': 'EVE_LKGM',
@@ -608,8 +608,8 @@
   'CROS_EVE_DEV': {
     'skylab': {
       'cros_board': 'eve',
-      'cros_chrome_version': '98.0.4758.0',
-      'cros_img': 'eve-release/R98-14388.1.0',
+      'cros_chrome_version': '98.0.4758.14',
+      'cros_img': 'eve-release/R98-14388.8.0',
     },
     'enabled': True,
     'identifier': 'EVE_DEV',
@@ -617,8 +617,8 @@
   'CROS_EVE_BETA': {
     'skylab': {
       'cros_board': 'eve',
-      'cros_chrome_version': '97.0.4692.27',
-      'cros_img': 'eve-release/R97-14324.33.0',
+      'cros_chrome_version': '97.0.4692.63',
+      'cros_img': 'eve-release/R97-14324.49.0',
     },
     'enabled': True,
     'identifier': 'EVE_BETA',
@@ -635,8 +635,8 @@
   'CROS_KEVIN_LKGM': {
     'skylab': {
       'cros_board': 'kevin',
-      'cros_chrome_version': '99.0.4761.0',
-      'cros_img': 'kevin-release/R99-14396.0.0',
+      'cros_chrome_version': '99.0.4801.0',
+      'cros_img': 'kevin-release/R99-14435.0.0',
     },
     'enabled': True,
     'identifier': 'KEVIN_LKGM',
@@ -644,8 +644,8 @@
   'CROS_OCTOPUS_LKGM': {
     'skylab': {
       'cros_board': 'octopus',
-      'cros_chrome_version': '99.0.4761.0',
-      'cros_img': 'octopus-release/R99-14396.0.0',
+      'cros_chrome_version': '99.0.4801.0',
+      'cros_img': 'octopus-release/R99-14435.0.0',
     },
     'enabled': True,
     'identifier': 'OCTOPUS_LKGM',
@@ -653,8 +653,8 @@
   'CROS_OCTOPUS_DEV': {
     'skylab': {
       'cros_board': 'octopus',
-      'cros_chrome_version': '98.0.4758.0',
-      'cros_img': 'octopus-release/R98-14388.1.0',
+      'cros_chrome_version': '98.0.4758.14',
+      'cros_img': 'octopus-release/R98-14388.8.0',
     },
     'enabled': True,
     'identifier': 'OCTOPUS_DEV',
@@ -662,8 +662,8 @@
   'CROS_OCTOPUS_BETA': {
     'skylab': {
       'cros_board': 'octopus',
-      'cros_chrome_version': '97.0.4692.27',
-      'cros_img': 'octopus-release/R97-14324.33.0',
+      'cros_chrome_version': '97.0.4692.63',
+      'cros_img': 'octopus-release/R97-14324.49.0',
     },
     'enabled': True,
     'identifier': 'OCTOPUS_BETA',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 543e69e..03c9e0b 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -6035,6 +6035,22 @@
             ]
         }
     ],
+    "PartitionAllocPCScanBlinkPartitions": [
+        {
+            "platforms": [
+                "windows",
+                "linux"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PartitionAllocPCScanBlinkPartitions"
+                    ]
+                }
+            ]
+        }
+    ],
     "PartitionConnectionsByNetworkIsolationKey": [
         {
             "platforms": [
diff --git a/third_party/blink/renderer/core/clipboard/data_transfer.cc b/third_party/blink/renderer/core/clipboard/data_transfer.cc
index ec90e1a..46fb5e0 100644
--- a/third_party/blink/renderer/core/clipboard/data_transfer.cc
+++ b/third_party/blink/renderer/core/clipboard/data_transfer.cc
@@ -125,17 +125,13 @@
         layer->GetLayoutObject()
             .AbsoluteToLocalQuad(FloatQuad(absolute_bounding_box))
             .BoundingBox();
-    absl::optional<OverriddenCullRectScope> cull_rect_scope;
-    if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
-      gfx::RectF cull_rect = bounding_box;
-      cull_rect.Offset(gfx::Vector2dF(
-          layer->GetLayoutObject().FirstFragment().PaintOffset()));
-      cull_rect_scope.emplace(*layer,
-                              CullRect(gfx::ToEnclosingRect(cull_rect)));
-    }
+    gfx::RectF cull_rect = bounding_box;
+    cull_rect.Offset(
+        gfx::Vector2dF(layer->GetLayoutObject().FirstFragment().PaintOffset()));
+    OverriddenCullRectScope cull_rect_scope(
+        *layer, CullRect(gfx::ToEnclosingRect(cull_rect)));
     PaintLayerPaintingInfo painting_info(
-        layer, CullRect(gfx::ToEnclosingRect(bounding_box)),
-        kGlobalPaintFlattenCompositingLayers, PhysicalOffset());
+        layer, kGlobalPaintFlattenCompositingLayers, PhysicalOffset());
     auto* builder = MakeGarbageCollected<PaintRecordBuilder>();
 
     dragged_layout_object->GetDocument().Lifecycle().AdvanceTo(
diff --git a/third_party/blink/renderer/core/css/build.gni b/third_party/blink/renderer/core/css/build.gni
index 57abf1e..6264fea0 100644
--- a/third_party/blink/renderer/core/css/build.gni
+++ b/third_party/blink/renderer/core/css/build.gni
@@ -611,8 +611,10 @@
   "style_property_serializer.cc",
   "style_property_serializer.h",
   "style_property_shorthand_custom.cc",
-  "style_recalc.cc",
-  "style_recalc.h",
+  "style_recalc_change.cc",
+  "style_recalc_change.h",
+  "style_recalc_context.cc",
+  "style_recalc_context.h",
   "style_recalc_root.cc",
   "style_recalc_root.h",
   "style_rule.cc",
@@ -750,7 +752,7 @@
   "selector_query_test.cc",
   "style_element_test.cc",
   "style_engine_test.cc",
-  "style_recalc_test.cc",
+  "style_recalc_change_test.cc",
   "style_environment_variables_test.cc",
   "style_rule_test.cc",
   "style_sheet_contents_test.cc",
diff --git a/third_party/blink/renderer/core/css/container_query_data.h b/third_party/blink/renderer/core/css/container_query_data.h
index f50790a..4936c92 100644
--- a/third_party/blink/renderer/core/css/container_query_data.h
+++ b/third_party/blink/renderer/core/css/container_query_data.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CONTAINER_QUERY_DATA_H_
 
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_change.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 
diff --git a/third_party/blink/renderer/core/css/container_query_evaluator.cc b/third_party/blink/renderer/core/css/container_query_evaluator.cc
index d9d781c..d4bea15 100644
--- a/third_party/blink/renderer/core/css/container_query_evaluator.cc
+++ b/third_party/blink/renderer/core/css/container_query_evaluator.cc
@@ -6,7 +6,7 @@
 #include "third_party/blink/renderer/core/css/container_query.h"
 #include "third_party/blink/renderer/core/css/css_container_values.h"
 #include "third_party/blink/renderer/core/css/resolver/match_result.h"
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_context.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.h b/third_party/blink/renderer/core/css/element_rule_collector.h
index 57eee15..b615a52 100644
--- a/third_party/blink/renderer/core/css/element_rule_collector.h
+++ b/third_party/blink/renderer/core/css/element_rule_collector.h
@@ -29,7 +29,7 @@
 #include "third_party/blink/renderer/core/css/resolver/match_request.h"
 #include "third_party/blink/renderer/core/css/resolver/match_result.h"
 #include "third_party/blink/renderer/core/css/selector_checker.h"
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_context.h"
 #include "third_party/blink/renderer/core/css/style_request.h"
 #include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_state.h b/third_party/blink/renderer/core/css/resolver/style_resolver_state.h
index 84ad87be..e7dfaca 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_state.h
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_state.h
@@ -33,6 +33,7 @@
 #include "third_party/blink/renderer/core/css/resolver/element_resolve_context.h"
 #include "third_party/blink/renderer/core/css/resolver/element_style_resources.h"
 #include "third_party/blink/renderer/core/css/resolver/font_builder.h"
+#include "third_party/blink/renderer/core/css/style_recalc_context.h"
 #include "third_party/blink/renderer/core/css/style_request.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element.h"
@@ -42,7 +43,6 @@
 class ComputedStyle;
 class FontDescription;
 class PseudoElement;
-class StyleRecalcContext;
 
 // A per-element object which wraps an ElementResolveContext. It collects state
 // throughout the process of computing the style. It also gives convenient
diff --git a/third_party/blink/renderer/core/css/style_recalc.cc b/third_party/blink/renderer/core/css/style_recalc_change.cc
similarity index 85%
rename from third_party/blink/renderer/core/css/style_recalc.cc
rename to third_party/blink/renderer/core/css/style_recalc_change.cc
index e08a6e9..3200a6f 100644
--- a/third_party/blink/renderer/core/css/style_recalc.cc
+++ b/third_party/blink/renderer/core/css/style_recalc_change.cc
@@ -1,9 +1,10 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_change.h"
 
+#include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/dom/pseudo_element.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
@@ -133,23 +134,4 @@
   return result;
 }
 
-StyleRecalcContext StyleRecalcContext::FromInclusiveAncestors(
-    Element& element) {
-  if (element.GetContainerQueryEvaluator())
-    return StyleRecalcContext{&element};
-  return FromAncestors(element);
-}
-
-StyleRecalcContext StyleRecalcContext::FromAncestors(Element& element) {
-  Element* ancestor = &element;
-  // TODO(crbug.com/1145970): Avoid this work if we're not inside a container.
-  while ((ancestor = DynamicTo<Element>(
-              LayoutTreeBuilderTraversal::Parent(*ancestor)))) {
-    if (ancestor->GetContainerQueryEvaluator())
-      return StyleRecalcContext{ancestor};
-  }
-
-  return StyleRecalcContext();
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/style_recalc.h b/third_party/blink/renderer/core/css/style_recalc_change.h
similarity index 79%
rename from third_party/blink/renderer/core/css/style_recalc.h
rename to third_party/blink/renderer/core/css/style_recalc_change.h
index d68da5b..cf7d507 100644
--- a/third_party/blink/renderer/core/css/style_recalc.h
+++ b/third_party/blink/renderer/core/css/style_recalc_change.h
@@ -1,11 +1,12 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_CHANGE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_CHANGE_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -130,30 +131,6 @@
   Flags flags_ = kNoFlags;
 };
 
-// StyleRecalcContext is an object that is passed on the stack during
-// the style recalc process.
-//
-// Its purpose is to hold context related to the style recalc process as
-// a whole, i.e. information not directly associated to the specific element
-// style is being calculated for.
-class StyleRecalcContext {
-  STACK_ALLOCATED();
-
- public:
-  // Using the ancestor chain, build a StyleRecalcContext suitable for
-  // resolving the style of the given Element.
-  static StyleRecalcContext FromAncestors(Element&);
-
-  // If the passed in StyleRecalcContext is nullptr, build a StyleRecalcContext
-  // suitable for resolving the style for child elements of the passed in
-  // element. Otherwise return the passed in context as a value.
-  static StyleRecalcContext FromInclusiveAncestors(Element&);
-
-  // Set to the nearest container (for container queries), if any.
-  // This is used to evaluate container queries in ElementRuleCollector.
-  Element* container = nullptr;
-};
-
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_CHANGE_H_
diff --git a/third_party/blink/renderer/core/css/style_recalc_test.cc b/third_party/blink/renderer/core/css/style_recalc_change_test.cc
similarity index 93%
rename from third_party/blink/renderer/core/css/style_recalc_test.cc
rename to third_party/blink/renderer/core/css/style_recalc_change_test.cc
index 06b3df3..765f9c5 100644
--- a/third_party/blink/renderer/core/css/style_recalc_test.cc
+++ b/third_party/blink/renderer/core/css/style_recalc_change_test.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_change.h"
 
 #include "third_party/blink/renderer/core/css/container_query_data.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -15,20 +15,21 @@
 
 namespace blink {
 
-class StyleRecalcTest : public PageTestBase {};
+class StyleRecalcChangeTest : public PageTestBase {};
 
-class StyleRecalcTestCQ : public StyleRecalcTest,
-                          private ScopedCSSContainerQueriesForTest,
-                          private ScopedCSSContainerSkipStyleRecalcForTest,
-                          private ScopedLayoutNGForTest {
+class StyleRecalcChangeTestCQ
+    : public StyleRecalcChangeTest,
+      private ScopedCSSContainerQueriesForTest,
+      private ScopedCSSContainerSkipStyleRecalcForTest,
+      private ScopedLayoutNGForTest {
  public:
-  StyleRecalcTestCQ()
+  StyleRecalcChangeTestCQ()
       : ScopedCSSContainerQueriesForTest(true),
         ScopedCSSContainerSkipStyleRecalcForTest(true),
         ScopedLayoutNGForTest(true) {}
 };
 
-TEST_F(StyleRecalcTest, SuppressRecalc) {
+TEST_F(StyleRecalcChangeTest, SuppressRecalc) {
   SetBodyInnerHTML(R"HTML(
     <style>
       .foo { color: green; }
@@ -50,7 +51,7 @@
                   .ShouldRecalcStyleFor(*element));
 }
 
-TEST_F(StyleRecalcTestCQ, SkipStyleRecalcForContainer) {
+TEST_F(StyleRecalcChangeTestCQ, SkipStyleRecalcForContainer) {
   UpdateAllLifecyclePhasesForTest();
 
   ASSERT_TRUE(GetDocument().body());
@@ -182,7 +183,7 @@
                 GetCSSPropertyColor()));
 }
 
-TEST_F(StyleRecalcTestCQ, SkipStyleRecalcForContainerCleanSubtree) {
+TEST_F(StyleRecalcChangeTestCQ, SkipStyleRecalcForContainerCleanSubtree) {
   UpdateAllLifecyclePhasesForTest();
 
   ASSERT_TRUE(GetDocument().body());
@@ -212,7 +213,7 @@
   EXPECT_FALSE(container->GetContainerQueryData()->SkippedStyleRecalc());
 }
 
-TEST_F(StyleRecalcTestCQ, SkipAttachLayoutTreeForContainer) {
+TEST_F(StyleRecalcChangeTestCQ, SkipAttachLayoutTreeForContainer) {
   GetDocument().body()->setInnerHTML(R"HTML(
     <style>
       #container { container-type: inline-size; }
diff --git a/third_party/blink/renderer/core/css/style_recalc_context.cc b/third_party/blink/renderer/core/css/style_recalc_context.cc
new file mode 100644
index 0000000..4f9c6bab
--- /dev/null
+++ b/third_party/blink/renderer/core/css/style_recalc_context.cc
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/css/style_recalc_context.h"
+
+#include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/html/html_slot_element.h"
+
+namespace blink {
+
+StyleRecalcContext StyleRecalcContext::FromInclusiveAncestors(
+    Element& element) {
+  if (element.GetContainerQueryEvaluator())
+    return StyleRecalcContext{&element};
+  return FromAncestors(element);
+}
+
+StyleRecalcContext StyleRecalcContext::FromAncestors(Element& element) {
+  Element* ancestor = &element;
+  // TODO(crbug.com/1145970): Avoid this work if we're not inside a container.
+  while ((ancestor = DynamicTo<Element>(
+              LayoutTreeBuilderTraversal::Parent(*ancestor)))) {
+    if (ancestor->GetContainerQueryEvaluator())
+      return StyleRecalcContext{ancestor};
+  }
+
+  return StyleRecalcContext();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/style_recalc_context.h b/third_party/blink/renderer/core/css/style_recalc_context.h
new file mode 100644
index 0000000..8240571
--- /dev/null
+++ b/third_party/blink/renderer/core/css/style_recalc_context.h
@@ -0,0 +1,40 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_CONTEXT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_CONTEXT_H_
+
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+
+namespace blink {
+
+class Element;
+
+// StyleRecalcContext is an object that is passed on the stack during
+// the style recalc process.
+//
+// Its purpose is to hold context related to the style recalc process as
+// a whole, i.e. information not directly associated to the specific element
+// style is being calculated for.
+class StyleRecalcContext {
+  STACK_ALLOCATED();
+
+ public:
+  // Using the ancestor chain, build a StyleRecalcContext suitable for
+  // resolving the style of the given Element.
+  static StyleRecalcContext FromAncestors(Element&);
+
+  // If the passed in StyleRecalcContext is nullptr, build a StyleRecalcContext
+  // suitable for resolving the style for child elements of the passed in
+  // element. Otherwise return the passed in context as a value.
+  static StyleRecalcContext FromInclusiveAncestors(Element&);
+
+  // Set to the nearest container (for container queries), if any.
+  // This is used to evaluate container queries in ElementRuleCollector.
+  Element* container = nullptr;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_RECALC_CONTEXT_H_
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.cc b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
index 803d7a06..d73fffb 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
@@ -12,7 +12,6 @@
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
-#include "third_party/blink/renderer/core/css/style_recalc.h"
 #include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h"
 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
 #include "third_party/blink/renderer/core/dom/document.h"
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.h b/third_party/blink/renderer/core/display_lock/display_lock_context.h
index fab56547..73481a4 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.h
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_change.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
 #include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
diff --git a/third_party/blink/renderer/core/dom/container_node.h b/third_party/blink/renderer/core/dom/container_node.h
index 4bb881c..9c00a385 100644
--- a/third_party/blink/renderer/core/dom/container_node.h
+++ b/third_party/blink/renderer/core/dom/container_node.h
@@ -27,7 +27,7 @@
 
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_change.h"
 #include "third_party/blink/renderer/core/dom/node.h"
 #include "third_party/blink/renderer/core/dom/static_node_list.h"
 #include "third_party/blink/renderer/core/html/collection_type.h"
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 60464aee..8dd7ed65 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -35,7 +35,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
 #include "third_party/blink/renderer/core/css/css_selector.h"
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_change.h"
 #include "third_party/blink/renderer/core/dom/container_node.h"
 #include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
 #include "third_party/blink/renderer/core/dom/element_data.h"
diff --git a/third_party/blink/renderer/core/dom/text.h b/third_party/blink/renderer/core/dom/text.h
index dc6dab3..0ec506c 100644
--- a/third_party/blink/renderer/core/dom/text.h
+++ b/third_party/blink/renderer/core/dom/text.h
@@ -25,7 +25,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TEXT_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/css/style_recalc.h"
+#include "third_party/blink/renderer/core/css/style_recalc_change.h"
 #include "third_party/blink/renderer/core/dom/character_data.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 42089dc..bdae12c 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -547,9 +547,8 @@
   // would.
   LocalFrameView* view = web_view_helper_.LocalMainFrame()->GetFrameView();
   PaintLayer* root_layer = view->GetLayoutView()->Layer();
-  CullRect paint_rect(gfx::Rect(0, 0, kWidth, kHeight));
-  PaintLayerPaintingInfo painting_info(
-      root_layer, paint_rect, kGlobalPaintNormalPhase, PhysicalOffset());
+  PaintLayerPaintingInfo painting_info(root_layer, kGlobalPaintNormalPhase,
+                                       PhysicalOffset());
 
   view->GetLayoutView()->GetDocument().Lifecycle().AdvanceTo(
       DocumentLifecycle::kInPaint);
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index a0f1607..d0cffaf 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2877,11 +2877,9 @@
     // Draw the overlay layer (video or WebXR DOM overlay) if present.
     if (PaintLayer* full_screen_layer = GetFullScreenOverlayLayer()) {
       PaintLayerPainter(*full_screen_layer)
-          .Paint(graphics_context, CullRect::Infinite(),
-                 kGlobalPaintNormalPhase, 0);
+          .Paint(graphics_context, kGlobalPaintNormalPhase, 0);
     } else {
-      PaintInternal(graphics_context, kGlobalPaintNormalPhase,
-                    CullRect::Infinite());
+      PaintFrame(graphics_context, kGlobalPaintNormalPhase);
 
       GetPage()->GetValidationMessageClient().PaintOverlay(graphics_context);
       ForAllNonThrottledLocalFrameViews(
@@ -3965,15 +3963,17 @@
     }
   }
 
+  if (!cull_rect.Rect().Intersects(FrameRect()))
+    return;
+
   // |paint_offset| is not used because paint properties of the contents will
   // ensure the correct location.
-  PaintInternal(context, global_paint_flags, cull_rect);
+  PaintFrame(context, global_paint_flags);
 }
 
-void LocalFrameView::PaintInternal(GraphicsContext& context,
-                                   const GlobalPaintFlags global_paint_flags,
-                                   const CullRect& cull_rect) const {
-  FramePainter(*this).Paint(context, global_paint_flags, cull_rect);
+void LocalFrameView::PaintFrame(GraphicsContext& context,
+                                GlobalPaintFlags global_paint_flags) const {
+  FramePainter(*this).Paint(context, global_paint_flags);
 }
 
 static bool PaintOutsideOfLifecycleIsAllowed(GraphicsContext& context,
@@ -4004,7 +4004,7 @@
   {
     OverriddenCullRectScope force_cull_rect(*GetLayoutView()->Layer(),
                                             cull_rect);
-    PaintInternal(context, global_paint_flags, cull_rect);
+    PaintFrame(context, global_paint_flags);
   }
 
   ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
@@ -4012,31 +4012,7 @@
   });
 }
 
-void LocalFrameView::PaintContentsOutsideOfLifecycle(
-    GraphicsContext& context,
-    const GlobalPaintFlags global_paint_flags,
-    const CullRect& cull_rect) {
-  DCHECK(PaintOutsideOfLifecycleIsAllowed(context, *this));
-
-  SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(),
-                           LocalFrameUkmAggregator::kPaint);
-
-  ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
-    frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kInPaint);
-  });
-
-  {
-    OverriddenCullRectScope force_cull_rect(*GetLayoutView()->Layer(),
-                                            cull_rect);
-    FramePainter(*this).PaintContents(context, global_paint_flags, cull_rect);
-  }
-
-  ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
-    frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean);
-  });
-}
-
-void LocalFrameView::PaintContentsForTest(const CullRect& cull_rect) {
+void LocalFrameView::PaintForTest(const CullRect& cull_rect) {
   AllowThrottlingScope allow_throttling(*this);
   Lifecycle().AdvanceTo(DocumentLifecycle::kInPaint);
   OverriddenCullRectScope force_cull_rect(*GetLayoutView()->Layer(), cull_rect);
@@ -4044,8 +4020,7 @@
   if (GetLayoutView()->Layer()->SelfOrDescendantNeedsRepaint()) {
     PaintController::CycleScope cycle_scope(paint_controller);
     GraphicsContext graphics_context(paint_controller);
-    Paint(graphics_context, kGlobalPaintNormalPhase, cull_rect,
-          gfx::Vector2d());
+    PaintFrame(graphics_context, kGlobalPaintNormalPhase);
     paint_controller.CommitNewDisplayItems();
   }
   Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean);
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index 85bbefe..daeca9f 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -554,13 +554,9 @@
       GraphicsContext&,
       const GlobalPaintFlags,
       const CullRect& cull_rect = CullRect::Infinite());
-  void PaintContentsOutsideOfLifecycle(GraphicsContext&,
-                                       const GlobalPaintFlags,
-                                       const CullRect&);
 
-  // For testing paint with an optional custom cull rect. In pre-CAP, this
-  // paints the contents of the main GraphicsLayer only.
-  void PaintContentsForTest(const CullRect&);
+  // For testing paint with a custom cull rect.
+  void PaintForTest(const CullRect&);
 
   // Get the PaintRecord based on the cached paint artifact generated during
   // the last paint in lifecycle update. For CompositeAfterPaint only.
@@ -851,9 +847,7 @@
              const CullRect&,
              const gfx::Vector2d&) const final;
 
-  void PaintInternal(GraphicsContext&,
-                     const GlobalPaintFlags,
-                     const CullRect&) const;
+  void PaintFrame(GraphicsContext&, GlobalPaintFlags) const;
 
   LayoutSVGRoot* EmbeddedReplacedContent() const;
 
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index edb4d72..45b7dd6 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -429,7 +429,7 @@
         frame_view->GetLayoutView()->FirstFragment().LocalBorderBoxProperties();
 
     auto* builder = MakeGarbageCollected<PaintRecordBuilder>(context);
-    frame_view->PaintContentsOutsideOfLifecycle(
+    frame_view->PaintOutsideOfLifecycle(
         builder->Context(),
         kGlobalPaintNormalPhase | kGlobalPaintFlattenCompositingLayers |
             kGlobalPaintAddUrlMetadata,
@@ -576,8 +576,8 @@
     if (include_linked_destinations)
       flags |= kGlobalPaintAddUrlMetadata;
 
-    frame_view->PaintContentsOutsideOfLifecycle(builder->Context(), flags,
-                                                CullRect(bounds));
+    frame_view->PaintOutsideOfLifecycle(builder->Context(), flags,
+                                        CullRect(bounds));
     if (include_linked_destinations) {
       // Add anchors.
       ScopedPaintChunkProperties scoped_paint_chunk_properties(
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 4288b86..eae5acd 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -97,7 +97,6 @@
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h"
-#include "third_party/blink/renderer/core/paint/background_image_geometry.h"
 #include "third_party/blink/renderer/core/paint/box_paint_invalidator.h"
 #include "third_party/blink/renderer/core/paint/box_painter.h"
 #include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
@@ -1542,59 +1541,77 @@
   if (rect_type == kBackgroundKnownOpaqueRect && BackgroundTransfersToView())
     return PhysicalRect();
 
-  EFillBox background_box = EFillBox::kText;
+  absl::optional<EFillBox> background_box;
+  Color background_color = ResolveColor(GetCSSPropertyBackgroundColor());
   // Find the largest background rect of the given opaqueness.
-  if (const FillLayer* current = &(StyleRef().BackgroundLayers())) {
-    do {
-      const FillLayer* cur = current;
-      current = current->Next();
-      if (rect_type == kBackgroundKnownOpaqueRect) {
-        if (cur->GetBlendMode() != BlendMode::kNormal ||
-            cur->Composite() != kCompositeSourceOver)
-          continue;
+  for (const FillLayer* cur = &(StyleRef().BackgroundLayers()); cur;
+       cur = cur->Next()) {
+    EFillBox current_clip = cur->Clip();
+    if (rect_type == kBackgroundKnownOpaqueRect) {
+      if (current_clip == EFillBox::kText)
+        continue;
 
-        bool layer_known_opaque = false;
-        // Check if the image is opaque and fills the clip.
-        if (const StyleImage* image = cur->GetImage()) {
-          if ((cur->RepeatX() == EFillRepeat::kRepeatFill ||
-               cur->RepeatX() == EFillRepeat::kRoundFill) &&
-              (cur->RepeatY() == EFillRepeat::kRepeatFill ||
-               cur->RepeatY() == EFillRepeat::kRoundFill) &&
-              image->KnownToBeOpaque(GetDocument(), StyleRef())) {
-            layer_known_opaque = true;
-          }
+      if (cur->GetBlendMode() != BlendMode::kNormal ||
+          cur->Composite() != kCompositeSourceOver)
+        continue;
+
+      bool layer_known_opaque = false;
+      // Check if the image is opaque and fills the clip.
+      if (const StyleImage* image = cur->GetImage()) {
+        if ((cur->RepeatX() == EFillRepeat::kRepeatFill ||
+             cur->RepeatX() == EFillRepeat::kRoundFill) &&
+            (cur->RepeatY() == EFillRepeat::kRepeatFill ||
+             cur->RepeatY() == EFillRepeat::kRoundFill) &&
+            image->KnownToBeOpaque(GetDocument(), StyleRef())) {
+          layer_known_opaque = true;
         }
-
-        // The background color is painted into the last layer.
-        if (!cur->Next()) {
-          Color background_color =
-              ResolveColor(GetCSSPropertyBackgroundColor());
-          if (!background_color.HasAlpha())
-            layer_known_opaque = true;
-        }
-
-        // If neither the image nor the color are opaque then skip this layer.
-        if (!layer_known_opaque)
-          continue;
       }
-      EFillBox current_clip = cur->Clip();
-      // Restrict clip if attachment is local.
-      if (current_clip == EFillBox::kBorder &&
-          cur->Attachment() == EFillAttachment::kLocal)
-        current_clip = EFillBox::kPadding;
 
-      // If we're asking for the clip rect, a content-box clipped fill layer can
-      // be scrolled into the padding box of the overflow container.
-      if (rect_type == kBackgroundClipRect &&
-          current_clip == EFillBox::kContent &&
+      // The background color is painted into the last layer.
+      if (!cur->Next() && !background_color.HasAlpha())
+        layer_known_opaque = true;
+
+      // If neither the image nor the color are opaque then skip this layer.
+      if (!layer_known_opaque)
+        continue;
+    } else {
+      // Ignore invisible background layers for kBackgroundPaintedExtent.
+      DCHECK_EQ(rect_type, kBackgroundPaintedExtent);
+      if (!cur->GetImage() && (cur->Next() || background_color.Alpha() == 0))
+        continue;
+      // A content-box clipped fill layer can be scrolled into the padding box
+      // of the overflow container.
+      if (current_clip == EFillBox::kContent &&
           cur->Attachment() == EFillAttachment::kLocal) {
         current_clip = EFillBox::kPadding;
       }
+    }
 
-      background_box = EnclosingFillBox(background_box, current_clip);
-    } while (current);
+    // Restrict clip if attachment is local.
+    if (current_clip == EFillBox::kBorder &&
+        cur->Attachment() == EFillAttachment::kLocal)
+      current_clip = EFillBox::kPadding;
+
+    background_box = background_box
+                         ? EnclosingFillBox(*background_box, current_clip)
+                         : current_clip;
   }
-  switch (background_box) {
+
+  if (!background_box)
+    return PhysicalRect();
+
+  if (*background_box == EFillBox::kText) {
+    DCHECK_NE(rect_type, kBackgroundKnownOpaqueRect);
+    *background_box = EFillBox::kBorder;
+  }
+
+  if (rect_type == kBackgroundPaintedExtent &&
+      *background_box == EFillBox::kBorder &&
+      BackgroundClipBorderBoxIsEquivalentToPaddingBox()) {
+    *background_box = EFillBox::kPadding;
+  }
+
+  switch (*background_box) {
     case EFillBox::kBorder:
       return PhysicalBorderBoxRect();
     case EFillBox::kPadding:
@@ -1602,7 +1619,7 @@
     case EFillBox::kContent:
       return PhysicalContentBoxRect();
     default:
-      break;
+      NOTREACHED();
   }
   return PhysicalRect();
 }
@@ -2618,39 +2635,9 @@
   BoxPainter(*this).PaintBoxDecorationBackground(paint_info, paint_offset);
 }
 
-bool LayoutBox::GetBackgroundPaintedExtent(PhysicalRect& painted_extent) const {
+PhysicalRect LayoutBox::BackgroundPaintedExtent() const {
   NOT_DESTROYED();
-  DCHECK(StyleRef().HasBackground());
-
-  // LayoutView is special in the sense that it expands to the whole canvas,
-  // thus can't be handled by this function.
-  DCHECK(!IsA<LayoutView>(this));
-
-  PhysicalRect background_rect(PhysicalBorderBoxRect());
-
-  Color background_color = ResolveColor(GetCSSPropertyBackgroundColor());
-  if (background_color.Alpha()) {
-    painted_extent = background_rect;
-    return true;
-  }
-
-  const FillLayer& fill_layer = StyleRef().BackgroundLayers();
-  if (!fill_layer.GetImage() || fill_layer.Next()) {
-    painted_extent = background_rect;
-    return true;
-  }
-
-  BackgroundImageGeometry geometry(*this);
-  // TODO(schenney): This function should be rethought as it's called during
-  // and outside of the paint phase. Potentially returning different results at
-  // different phases. crbug.com/732934
-  geometry.Calculate(nullptr, PaintPhase::kBlockBackground, fill_layer,
-                     background_rect);
-  if (!geometry.HasNonLocalGeometry()) {
-    painted_extent = geometry.SnappedDestRect();
-    return true;
-  }
-  return false;
+  return PhysicalBackgroundRect(kBackgroundPaintedExtent);
 }
 
 bool LayoutBox::BackgroundIsKnownToBeOpaqueInRect(
@@ -2758,10 +2745,7 @@
     return false;
   if (StyleRef().BoxShadow())
     return false;
-  PhysicalRect background_rect;
-  if (!GetBackgroundPaintedExtent(background_rect))
-    return false;
-  return ForegroundIsKnownToBeOpaqueInRect(background_rect,
+  return ForegroundIsKnownToBeOpaqueInRect(BackgroundPaintedExtent(),
                                            kBackgroundObscurationTestMaxDepth);
 }
 
@@ -8379,6 +8363,41 @@
   return false;
 }
 
+// If all borders and scrollbars are opaque, then background-clip: border-box
+// is equivalent to background-clip: padding-box.
+bool LayoutBox::BackgroundClipBorderBoxIsEquivalentToPaddingBox() const {
+  // Custom scrollbars may be translucent.
+  // TODO(flackr): Detect opaque custom scrollbars which would cover up a
+  // border-box background.
+  const auto* scrollable_area = GetScrollableArea();
+  if (scrollable_area &&
+      ((scrollable_area->HorizontalScrollbar() &&
+        scrollable_area->HorizontalScrollbar()->IsCustomScrollbar()) ||
+       (scrollable_area->VerticalScrollbar() &&
+        scrollable_area->VerticalScrollbar()->IsCustomScrollbar()))) {
+    return false;
+  }
+
+  if (StyleRef().BorderTopWidth() &&
+      (ResolveColor(GetCSSPropertyBorderTopColor()).HasAlpha() ||
+       StyleRef().BorderTopStyle() != EBorderStyle::kSolid))
+    return false;
+  if (StyleRef().BorderRightWidth() &&
+      (ResolveColor(GetCSSPropertyBorderRightColor()).HasAlpha() ||
+       StyleRef().BorderRightStyle() != EBorderStyle::kSolid))
+    return false;
+  if (StyleRef().BorderBottomWidth() &&
+      (ResolveColor(GetCSSPropertyBorderBottomColor()).HasAlpha() ||
+       StyleRef().BorderBottomStyle() != EBorderStyle::kSolid))
+    return false;
+  if (StyleRef().BorderLeftWidth() &&
+      (ResolveColor(GetCSSPropertyBorderLeftColor()).HasAlpha() ||
+       StyleRef().BorderLeftStyle() != EBorderStyle::kSolid))
+    return false;
+
+  return true;
+}
+
 BackgroundPaintLocation LayoutBox::ComputeBackgroundPaintLocationIfComposited()
     const {
   NOT_DESTROYED();
@@ -8401,15 +8420,6 @@
   if (HasInsetBoxShadow(StyleRef()))
     return kBackgroundPaintInBorderBoxSpace;
 
-  // TODO(flackr): Detect opaque custom scrollbars which would cover up a
-  // border-box background.
-  bool has_custom_scrollbars =
-      scrollable_area &&
-      ((scrollable_area->HorizontalScrollbar() &&
-        scrollable_area->HorizontalScrollbar()->IsCustomScrollbar()) ||
-       (scrollable_area->VerticalScrollbar() &&
-        scrollable_area->VerticalScrollbar()->IsCustomScrollbar()));
-
   // Assume optimistically that the background can be painted in the scrolling
   // contents until we find otherwise.
   BackgroundPaintLocation paint_location = kBackgroundPaintInContentsSpace;
@@ -8433,21 +8443,8 @@
       // A border box can be treated as a padding box if the border is opaque or
       // there is no border and we don't have custom scrollbars.
       if (clip == EFillBox::kBorder) {
-        if (!has_custom_scrollbars &&
-            (StyleRef().BorderTopWidth() == 0 ||
-             (!ResolveColor(GetCSSPropertyBorderTopColor()).HasAlpha() &&
-              StyleRef().BorderTopStyle() == EBorderStyle::kSolid)) &&
-            (StyleRef().BorderLeftWidth() == 0 ||
-             (!ResolveColor(GetCSSPropertyBorderLeftColor()).HasAlpha() &&
-              StyleRef().BorderLeftStyle() == EBorderStyle::kSolid)) &&
-            (StyleRef().BorderRightWidth() == 0 ||
-             (!ResolveColor(GetCSSPropertyBorderRightColor()).HasAlpha() &&
-              StyleRef().BorderRightStyle() == EBorderStyle::kSolid)) &&
-            (StyleRef().BorderBottomWidth() == 0 ||
-             (!ResolveColor(GetCSSPropertyBorderBottomColor()).HasAlpha() &&
-              StyleRef().BorderBottomStyle() == EBorderStyle::kSolid))) {
+        if (BackgroundClipBorderBoxIsEquivalentToPaddingBox())
           continue;
-        }
         // If we have an opaque background color, we can safely paint it into
         // both the scrolling contents layer and the graphics layer to preserve
         // LCD text. The background color is either the only background or
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index a9ee443..fdc36f4b 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -61,7 +61,11 @@
 // clipping behavior. During hit testing, overlay scrollbars behave like regular
 // scrollbars and should change how hit testing is clipped.
 enum MarginDirection { kBlockDirection, kInlineDirection };
-enum BackgroundRectType { kBackgroundClipRect, kBackgroundKnownOpaqueRect };
+
+enum BackgroundRectType {
+  kBackgroundPaintedExtent,
+  kBackgroundKnownOpaqueRect,
+};
 
 enum ShouldComputePreferred { kComputeActual, kComputePreferred };
 
@@ -2114,9 +2118,7 @@
     return ItemPosition::kStretch;
   }
 
-  // Returns false if it could not cheaply compute the extent (e.g. fixed
-  // background), in which case the returned rect may be incorrect.
-  bool GetBackgroundPaintedExtent(PhysicalRect&) const;
+  PhysicalRect BackgroundPaintedExtent() const;
   virtual bool ForegroundIsKnownToBeOpaqueInRect(
       const PhysicalRect& local_rect,
       unsigned max_depth_to_test) const;
@@ -2339,6 +2341,8 @@
         Location().Y());
   }
 
+  bool BackgroundClipBorderBoxIsEquivalentToPaddingBox() const;
+
   // The CSS border box rect for this box.
   //
   // The rectangle is in LocationContainer's physical coordinates in flipped
diff --git a/third_party/blink/renderer/core/layout/layout_box_test.cc b/third_party/blink/renderer/core/layout/layout_box_test.cc
index a598bd29..e90201cc 100644
--- a/third_party/blink/renderer/core/layout/layout_box_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_test.cc
@@ -187,31 +187,62 @@
 
 TEST_P(LayoutBoxTest, BackgroundRect) {
   SetBodyInnerHTML(R"HTML(
-    <style>div { position: absolute; width: 100px; height: 100px; padding:
-    10px; border: 10px solid black; overflow: scroll; }
-    #target1 { background:
-    url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg) border-box, green
-    content-box;}
-    #target2 { background:
-    url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg) content-box, green
-    local border-box;}
-    #target3 { background:
-    url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg) content-box, rgba(0,
-    255, 0, 0.5) border-box;}
-    #target4 { background-image:
-    url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg), none;
-               background-clip: content-box, border-box;
-               background-blend-mode: normal, multiply;
-               background-color: green; }
-    #target5 { background: none border-box, green content-box;}
-    #target6 { background: green content-box local; }
+    <style>
+      div { position: absolute; width: 100px; height: 100px;
+            padding: 10px; border: 10px solid black; overflow: scroll; }
+      #target1a, #target7a { border: 10px dashed black; }
+      #target1, #target1a {
+        background:
+            url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg) border-box,
+            green content-box;
+      }
+      #target1b {
+        background:
+            url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg) border-box;
+      }
+      #target2 {
+        background:
+            url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg) content-box,
+            green local border-box;
+      }
+      #target2b {
+        background:
+            url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg) content-box;
+      }
+      #target3 {
+        background:
+            url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg) content-box,
+            rgba(0, 255, 0, 0.5) border-box;
+      }
+      #target4 {
+        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg),
+                          none;
+        background-clip: content-box, border-box;
+        background-blend-mode: normal, multiply;
+        background-color: green;
+      }
+      #target5 { background: none border-box, green content-box;}
+      #target6 { background: green content-box local; }
+      #target7, #target7a {
+        background-color: green;
+        -webkit-background-clip: text;
+      }
+      #target8 { background: transparent; }
+      #target9 { background: none; }
     </style>
     <div id='target1'></div>
+    <div id='target1a'></div>
+    <div id='target1b'></div>
     <div id='target2'></div>
+    <div id='target2b'></div>
     <div id='target3'></div>
     <div id='target4'></div>
     <div id='target5'></div>
     <div id='target6'></div>
+    <div id='target7'></div>
+    <div id='target7a'></div>
+    <div id='target8'></div>
+    <div id='target9'></div>
   )HTML");
 
   // #target1's opaque background color only fills the content box but its
@@ -219,8 +250,23 @@
   LayoutBox* layout_box = GetLayoutBoxByElementId("target1");
   EXPECT_EQ(PhysicalRect(20, 20, 100, 100),
             layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect));
+  // The opaque border covers the translucent image outside of the padding box.
+  EXPECT_EQ(PhysicalRect(10, 10, 120, 120),
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
+
+  // #target1a is the same as #target1 except that the border is not opaque.
+  layout_box = GetLayoutBoxByElementId("target1a");
+  EXPECT_EQ(PhysicalRect(20, 20, 100, 100),
+            layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect));
   EXPECT_EQ(PhysicalRect(0, 0, 140, 140),
-            layout_box->PhysicalBackgroundRect(kBackgroundClipRect));
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
+
+  // #target1b is the same as #target1 except no background color.
+  layout_box = GetLayoutBoxByElementId("target1b");
+  EXPECT_TRUE(
+      layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).IsEmpty());
+  EXPECT_EQ(PhysicalRect(10, 10, 120, 120),
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
 
   // #target2's background color is opaque but only fills the padding-box
   // because it has local attachment. This eclipses the content-box image.
@@ -228,29 +274,36 @@
   EXPECT_EQ(PhysicalRect(10, 10, 120, 120),
             layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect));
   EXPECT_EQ(PhysicalRect(10, 10, 120, 120),
-            layout_box->PhysicalBackgroundRect(kBackgroundClipRect));
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
 
-  // #target3's background color is not opaque so we only have a clip rect.
+  // #target2b is the same as #target2 except no background color.
+  layout_box = GetLayoutBoxByElementId("target2b");
+  EXPECT_TRUE(
+      layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).IsEmpty());
+  EXPECT_EQ(PhysicalRect(20, 20, 100, 100),
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
+
+  // #target3's background color is not opaque.
   layout_box = GetLayoutBoxByElementId("target3");
   EXPECT_TRUE(
       layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).IsEmpty());
-  EXPECT_EQ(PhysicalRect(0, 0, 140, 140),
-            layout_box->PhysicalBackgroundRect(kBackgroundClipRect));
+  EXPECT_EQ(PhysicalRect(10, 10, 120, 120),
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
 
   // #target4's background color has a blend mode so it isn't opaque.
   layout_box = GetLayoutBoxByElementId("target4");
   EXPECT_TRUE(
       layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).IsEmpty());
-  EXPECT_EQ(PhysicalRect(0, 0, 140, 140),
-            layout_box->PhysicalBackgroundRect(kBackgroundClipRect));
+  EXPECT_EQ(PhysicalRect(10, 10, 120, 120),
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
 
   // #target5's solid background only covers the content-box but it has a "none"
   // background covering the border box.
   layout_box = GetLayoutBoxByElementId("target5");
   EXPECT_EQ(PhysicalRect(20, 20, 100, 100),
             layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect));
-  EXPECT_EQ(PhysicalRect(0, 0, 140, 140),
-            layout_box->PhysicalBackgroundRect(kBackgroundClipRect));
+  EXPECT_EQ(PhysicalRect(20, 20, 100, 100),
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
 
   // Because it can scroll due to local attachment, the opaque local background
   // in #target6 is treated as padding box for the clip rect, but remains the
@@ -259,7 +312,36 @@
   EXPECT_EQ(PhysicalRect(20, 20, 100, 100),
             layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect));
   EXPECT_EQ(PhysicalRect(10, 10, 120, 120),
-            layout_box->PhysicalBackgroundRect(kBackgroundClipRect));
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
+
+  // #target7 has background-clip:text. The background may extend to the border
+  // box.
+  layout_box = GetLayoutBoxByElementId("target7");
+  EXPECT_TRUE(
+      layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).IsEmpty());
+  EXPECT_EQ(PhysicalRect(10, 10, 120, 120),
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
+
+  // #target7a is the same as #target1 except that the border is not opaque.
+  layout_box = GetLayoutBoxByElementId("target7a");
+  EXPECT_TRUE(
+      layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).IsEmpty());
+  EXPECT_EQ(PhysicalRect(0, 0, 140, 140),
+            layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent));
+
+  // background: none
+  layout_box = GetLayoutBoxByElementId("target8");
+  EXPECT_TRUE(
+      layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).IsEmpty());
+  EXPECT_TRUE(
+      layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent).IsEmpty());
+
+  // background: transparent
+  layout_box = GetLayoutBoxByElementId("target9");
+  EXPECT_TRUE(
+      layout_box->PhysicalBackgroundRect(kBackgroundKnownOpaqueRect).IsEmpty());
+  EXPECT_TRUE(
+      layout_box->PhysicalBackgroundRect(kBackgroundPaintedExtent).IsEmpty());
 }
 
 TEST_P(LayoutBoxTest, LocationContainer) {
diff --git a/third_party/blink/renderer/core/layout/layout_html_canvas.cc b/third_party/blink/renderer/core/layout/layout_html_canvas.cc
index 1720206..59a0ea4 100644
--- a/third_party/blink/renderer/core/layout/layout_html_canvas.cc
+++ b/third_party/blink/renderer/core/layout/layout_html_canvas.cc
@@ -83,7 +83,7 @@
     return true;
   // Simple background that is contained within the contents rect.
   return ReplacedContentRect().Contains(
-      PhysicalBackgroundRect(kBackgroundClipRect));
+      PhysicalBackgroundRect(kBackgroundPaintedExtent));
 }
 
 void LayoutHTMLCanvas::InvalidatePaint(
diff --git a/third_party/blink/renderer/core/layout/layout_image.cc b/third_party/blink/renderer/core/layout/layout_image.cc
index 69164ca..a783f14 100644
--- a/third_party/blink/renderer/core/layout/layout_image.cc
+++ b/third_party/blink/renderer/core/layout/layout_image.cc
@@ -280,10 +280,7 @@
   if (!StyleRef().HasBackground())
     return false;
 
-  PhysicalRect painted_extent;
-  if (!GetBackgroundPaintedExtent(painted_extent))
-    return false;
-  return ForegroundIsKnownToBeOpaqueInRect(painted_extent, 0);
+  return ForegroundIsKnownToBeOpaqueInRect(BackgroundPaintedExtent(), 0);
 }
 
 LayoutUnit LayoutImage::MinimumReplacedHeight() const {
diff --git a/third_party/blink/renderer/core/layout/layout_tree_as_text.cc b/third_party/blink/renderer/core/layout/layout_tree_as_text.cc
index e9633a31..b495b8c 100644
--- a/third_party/blink/renderer/core/layout/layout_tree_as_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_tree_as_text.cc
@@ -707,11 +707,11 @@
         .CalculateRects(
             ClipRectsContext(root_layer,
                              &root_layer->GetLayoutObject().FirstFragment()),
-            &layer->GetLayoutObject().FirstFragment(), nullptr, layer_bounds,
+            &layer->GetLayoutObject().FirstFragment(), layer_bounds,
             damage_rect, clip_rect_to_apply);
   } else {
     layer->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
-        .CalculateRects(ClipRectsContext(root_layer, nullptr), nullptr, nullptr,
+        .CalculateRects(ClipRectsContext(root_layer, nullptr), nullptr,
                         layer_bounds, damage_rect, clip_rect_to_apply);
   }
 
diff --git a/third_party/blink/renderer/core/page/drag_controller.cc b/third_party/blink/renderer/core/page/drag_controller.cc
index c41c4778..80fb9bd 100644
--- a/third_party/blink/renderer/core/page/drag_controller.cc
+++ b/third_party/blink/renderer/core/page/drag_controller.cc
@@ -1175,7 +1175,7 @@
       kGlobalPaintSelectionDragImageOnly | kGlobalPaintFlattenCompositingLayers;
 
   auto* builder = MakeGarbageCollected<PaintRecordBuilder>();
-  frame.View()->PaintContentsOutsideOfLifecycle(
+  frame.View()->PaintOutsideOfLifecycle(
       builder->Context(), paint_flags,
       CullRect(gfx::ToEnclosingRect(painting_rect)));
 
diff --git a/third_party/blink/renderer/core/page/print_context_test.cc b/third_party/blink/renderer/core/page/print_context_test.cc
index 7b4ec23..0ee11ee 100644
--- a/third_party/blink/renderer/core/page/print_context_test.cc
+++ b/third_party/blink/renderer/core/page/print_context_test.cc
@@ -173,7 +173,7 @@
     auto* builder = MakeGarbageCollected<PaintRecordBuilder>();
     GraphicsContext& context = builder->Context();
     context.SetPrinting(true);
-    GetDocument().View()->PaintContentsOutsideOfLifecycle(
+    GetDocument().View()->PaintOutsideOfLifecycle(
         context, kGlobalPaintAddUrlMetadata, CullRect(page_rect));
     {
       DrawingRecorder recorder(
diff --git a/third_party/blink/renderer/core/paint/block_painter.cc b/third_party/blink/renderer/core/paint/block_painter.cc
index 4f9893e..0d0e8fe 100644
--- a/third_party/blink/renderer/core/paint/block_painter.cc
+++ b/third_party/blink/renderer/core/paint/block_painter.cc
@@ -56,26 +56,19 @@
     local_paint_info.phase = PaintPhase::kDescendantOutlinesOnly;
   } else if (ShouldPaintSelfBlockBackground(original_phase)) {
     local_paint_info.phase = PaintPhase::kSelfBlockBackgroundOnly;
-    // With CompositeAfterPaint we need to call PaintObject twice: once for the
-    // background painting that does not scroll, and a second time for the
-    // background painting that scrolls.
-    // Without CompositeAfterPaint, this happens as the main graphics layer
-    // paints the background, and then the scrolling contents graphics layer
-    // paints the background.
-    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-      auto paint_location = layout_block_.GetBackgroundPaintLocation();
-      if (!(paint_location & kBackgroundPaintInBorderBoxSpace))
-        local_paint_info.SetSkipsBackground(true);
-      layout_block_.PaintObject(local_paint_info, paint_offset);
-      local_paint_info.SetSkipsBackground(false);
+    // We need to call PaintObject twice: one for painting background in the
+    // border box space, and the other for painting background in the scrolling
+    // contents space.
+    auto paint_location = layout_block_.GetBackgroundPaintLocation();
+    if (!(paint_location & kBackgroundPaintInBorderBoxSpace))
+      local_paint_info.SetSkipsBackground(true);
+    layout_block_.PaintObject(local_paint_info, paint_offset);
+    local_paint_info.SetSkipsBackground(false);
 
-      if (paint_location & kBackgroundPaintInContentsSpace) {
-        local_paint_info.SetIsPaintingBackgroundInContentsSpace(true);
-        layout_block_.PaintObject(local_paint_info, paint_offset);
-        local_paint_info.SetIsPaintingBackgroundInContentsSpace(false);
-      }
-    } else {
+    if (paint_location & kBackgroundPaintInContentsSpace) {
+      local_paint_info.SetIsPaintingBackgroundInContentsSpace(true);
       layout_block_.PaintObject(local_paint_info, paint_offset);
+      local_paint_info.SetIsPaintingBackgroundInContentsSpace(false);
     }
     if (ShouldPaintDescendantBlockBackgrounds(original_phase))
       local_paint_info.phase = PaintPhase::kDescendantBlockBackgroundsOnly;
@@ -353,12 +346,7 @@
   }
   overflow_rect.Unite(layout_block_.PhysicalVisualOverflowRect());
 
-  bool include_layout_overflow =
-      layout_block_.ScrollsOverflow() &&
-      (layout_block_.UsesCompositedScrolling() ||
-       RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
-
-  if (include_layout_overflow) {
+  if (layout_block_.ScrollsOverflow()) {
     overflow_rect.Unite(layout_block_.PhysicalLayoutOverflowRect());
     overflow_rect.Move(
         -PhysicalOffset(layout_block_.PixelSnappedScrolledContentOffset()));
diff --git a/third_party/blink/renderer/core/paint/box_model_object_painter.cc b/third_party/blink/renderer/core/paint/box_model_object_painter.cc
index 87df967c..5da76243 100644
--- a/third_party/blink/renderer/core/paint/box_model_object_painter.cc
+++ b/third_party/blink/renderer/core/paint/box_model_object_painter.cc
@@ -145,12 +145,4 @@
       is_painting_background_in_contents_space);
 }
 
-bool BoxModelObjectPainter::IsPaintingBackgroundInContentsSpace(
-    const PaintInfo& paint_info) const {
-  if (!box_model_.IsBox())
-    return false;
-
-  return paint_info.IsPaintingBackgroundInContentsSpace();
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/box_model_object_painter.h b/third_party/blink/renderer/core/paint/box_model_object_painter.h
index 450f1cf..40eac500 100644
--- a/third_party/blink/renderer/core/paint/box_model_object_painter.h
+++ b/third_party/blink/renderer/core/paint/box_model_object_painter.h
@@ -35,7 +35,6 @@
       const FillLayer&,
       BackgroundBleedAvoidance,
       bool is_painting_background_in_contents_space) const override;
-  bool IsPaintingBackgroundInContentsSpace(const PaintInfo&) const override;
 
   void PaintTextClipMask(const PaintInfo&,
                          const gfx::Rect& mask_rect,
diff --git a/third_party/blink/renderer/core/paint/box_painter_base.cc b/third_party/blink/renderer/core/paint/box_painter_base.cc
index 73d8824..b5ba48e 100644
--- a/third_party/blink/renderer/core/paint/box_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/box_painter_base.cc
@@ -940,7 +940,7 @@
 
   const FillLayerInfo fill_layer_info =
       GetFillLayerInfo(color, bg_layer, bleed_avoidance,
-                       IsPaintingBackgroundInContentsSpace(paint_info));
+                       paint_info.IsPaintingBackgroundInContentsSpace());
   // If we're not actually going to paint anything, abort early.
   if (!fill_layer_info.should_paint_image &&
       !fill_layer_info.should_paint_color)
diff --git a/third_party/blink/renderer/core/paint/box_painter_base.h b/third_party/blink/renderer/core/paint/box_painter_base.h
index 0dc32940..5ebbb071 100644
--- a/third_party/blink/renderer/core/paint/box_painter_base.h
+++ b/third_party/blink/renderer/core/paint/box_painter_base.h
@@ -168,7 +168,6 @@
       const FillLayer&,
       BackgroundBleedAvoidance,
       bool is_painting_background_in_contents_space) const = 0;
-  virtual bool IsPaintingBackgroundInContentsSpace(const PaintInfo&) const = 0;
   static void PaintInsetBoxShadow(
       const PaintInfo&,
       const FloatRoundedRect&,
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
index d37067e..701d69c 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
@@ -2052,7 +2052,6 @@
           padding: 5px;
           background: lightblue;
         }
-      }
       </style>
       <div id="target">
         <div id="textContainer">
@@ -2085,6 +2084,39 @@
   EXPECT_FALSE(CcLayerByDOMElementId("target")->contents_opaque_for_text());
 }
 
+TEST_P(CompositingSimTest, ChangingDrawsContentRequiresFullUpdate) {
+  InitializeWithHTML(R"HTML(
+      <!DOCTYPE html>
+      <style>
+        #target {
+          width: 100px;
+          height: 100px;
+          will-change: transform;
+        }
+      </style>
+      <div id="target"></div>
+  )HTML");
+
+  Compositor().BeginFrame();
+
+  // Initially, no update is needed.
+  EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate());
+  EXPECT_FALSE(CcLayerByDOMElementId("target")->DrawsContent());
+
+  // Clear the previous update to ensure we record a new one in the next update.
+  paint_artifact_compositor()->ClearPreviousUpdateForTesting();
+
+  // A simple repaint change that causes Layer::DrawsContent to change still
+  // needs to cause a full update because it can affect whether mask layers are
+  // created.
+  auto* target = GetElementById("target");
+  target->setAttribute(html_names::kStyleAttr, "background: rgba(0,0,0,0.5)");
+  Compositor().BeginFrame();
+  EXPECT_EQ(paint_artifact_compositor()->PreviousUpdateForTesting(),
+            PaintArtifactCompositor::PreviousUpdateType::kFull);
+  EXPECT_TRUE(CcLayerByDOMElementId("target")->DrawsContent());
+}
+
 TEST_P(CompositingSimTest, ContentsOpaqueForTextWithSubpixelSizeSimpleBg) {
   InitializeWithHTML(R"HTML(
       <!DOCTYPE html>
diff --git a/third_party/blink/renderer/core/paint/frame_painter.cc b/third_party/blink/renderer/core/paint/frame_painter.cc
index 1af0d43..b70690d 100644
--- a/third_party/blink/renderer/core/paint/frame_painter.cc
+++ b/third_party/blink/renderer/core/paint/frame_painter.cc
@@ -14,7 +14,6 @@
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_painter.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
-#include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
 
@@ -38,31 +37,14 @@
 bool FramePainter::in_paint_contents_ = false;
 
 void FramePainter::Paint(GraphicsContext& context,
-                         const GlobalPaintFlags global_paint_flags,
-                         const CullRect& cull_rect) {
-  if (GetFrameView().ShouldThrottleRendering())
-    return;
-
-  GetFrameView().NotifyPageThatContentAreaWillPaint();
-
-  CullRect document_cull_rect(
-      gfx::IntersectRects(cull_rect.Rect(), GetFrameView().FrameRect()));
-  document_cull_rect.Move(-GetFrameView().Location().OffsetFromOrigin());
-
-  if (document_cull_rect.Rect().IsEmpty())
-    return;
-
-  PaintContents(context, global_paint_flags, document_cull_rect);
-}
-
-void FramePainter::PaintContents(GraphicsContext& context,
-                                 const GlobalPaintFlags global_paint_flags,
-                                 const CullRect& cull_rect) {
+                         const GlobalPaintFlags global_paint_flags) {
   Document* document = GetFrameView().GetFrame().GetDocument();
 
   if (GetFrameView().ShouldThrottleRendering() || !document->IsActive())
     return;
 
+  GetFrameView().NotifyPageThatContentAreaWillPaint();
+
   LayoutView* layout_view = GetFrameView().GetLayoutView();
   if (!layout_view) {
     DLOG(ERROR) << "called FramePainter::paint with nil layoutObject";
@@ -84,7 +66,9 @@
   DEVTOOLS_TIMELINE_TRACE_EVENT_WITH_CATEGORIES(
       "devtools.timeline,rail", "Paint", inspector_paint_event::Data,
       &GetFrameView().GetFrame(), layout_view,
-      GetQuadForTraceEvent(GetFrameView(), cull_rect), /*layer_id=*/0);
+      GetQuadForTraceEvent(GetFrameView(),
+                           layout_view->FirstFragment().GetCullRect()),
+      /*layer_id=*/0);
 
   bool is_top_level_painter = !in_paint_contents_;
   in_paint_contents_ = true;
@@ -110,8 +94,7 @@
       root_layer->GetLayoutObject().GetFrame());
   context.SetDeviceScaleFactor(device_scale_factor);
 
-  layer_painter.Paint(context, cull_rect, global_paint_flags,
-                      root_layer_paint_flags);
+  layer_painter.Paint(context, global_paint_flags, root_layer_paint_flags);
 
   // Regions may have changed as a result of the visibility/z-index of element
   // changing.
diff --git a/third_party/blink/renderer/core/paint/frame_painter.h b/third_party/blink/renderer/core/paint/frame_painter.h
index 9924515..6f6dd028 100644
--- a/third_party/blink/renderer/core/paint/frame_painter.h
+++ b/third_party/blink/renderer/core/paint/frame_painter.h
@@ -10,7 +10,6 @@
 
 namespace blink {
 
-class CullRect;
 class GraphicsContext;
 class LocalFrameView;
 
@@ -23,8 +22,7 @@
   FramePainter(const FramePainter&) = delete;
   FramePainter& operator=(const FramePainter&) = delete;
 
-  void Paint(GraphicsContext&, const GlobalPaintFlags, const CullRect&);
-  void PaintContents(GraphicsContext&, const GlobalPaintFlags, const CullRect&);
+  void Paint(GraphicsContext&, const GlobalPaintFlags);
 
  private:
   const LocalFrameView& GetFrameView();
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index a039ee1..e723f05 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -427,27 +427,20 @@
     info.phase = PaintPhase::kDescendantOutlinesOnly;
   } else if (ShouldPaintSelfBlockBackground(original_phase)) {
     info.phase = PaintPhase::kSelfBlockBackgroundOnly;
-    // With CompositeAfterPaint we need to call PaintObject twice: once for the
-    // background painting that does not scroll, and a second time for the
-    // background painting that scrolls.
-    // Without CompositeAfterPaint, this happens as the main graphics layer
-    // paints the background, and then the scrolling contents graphics layer
-    // paints the background.
-    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-      auto paint_location = To<LayoutBox>(*box_fragment_.GetLayoutObject())
-                                .GetBackgroundPaintLocation();
-      if (!(paint_location & kBackgroundPaintInBorderBoxSpace))
-        info.SetSkipsBackground(true);
-      PaintObject(info, paint_offset);
-      info.SetSkipsBackground(false);
+    // We need to call PaintObject twice: one for painting background in the
+    // border box space, and the other for painting background in the scrolling
+    // contents space.
+    auto paint_location = To<LayoutBox>(*box_fragment_.GetLayoutObject())
+                              .GetBackgroundPaintLocation();
+    if (!(paint_location & kBackgroundPaintInBorderBoxSpace))
+      info.SetSkipsBackground(true);
+    PaintObject(info, paint_offset);
+    info.SetSkipsBackground(false);
 
-      if (paint_location & kBackgroundPaintInContentsSpace) {
-        info.SetIsPaintingBackgroundInContentsSpace(true);
-        PaintObject(info, paint_offset);
-        info.SetIsPaintingBackgroundInContentsSpace(false);
-      }
-    } else {
+    if (paint_location & kBackgroundPaintInContentsSpace) {
+      info.SetIsPaintingBackgroundInContentsSpace(true);
       PaintObject(info, paint_offset);
+      info.SetIsPaintingBackgroundInContentsSpace(false);
     }
     if (ShouldPaintDescendantBlockBackgrounds(original_phase))
       info.phase = PaintPhase::kDescendantBlockBackgroundsOnly;
@@ -520,7 +513,7 @@
 
 bool NGBoxFragmentPainter::ShouldRecordHitTestData(
     const PaintInfo& paint_info) {
-  if (IsPaintingBackgroundInContentsSpace(paint_info) &&
+  if (paint_info.IsPaintingBackgroundInContentsSpace() &&
       PhysicalFragment().EffectiveAllowedTouchAction() == TouchAction::kAuto &&
       !PhysicalFragment().InsideBlockingWheelEventHandler()) {
     return false;
@@ -1006,10 +999,8 @@
   PhysicalRect paint_rect;
   const DisplayItemClient* background_client = nullptr;
   absl::optional<ScopedBoxContentsPaintState> contents_paint_state;
-  bool painting_background_in_contents_space =
-      IsPaintingBackgroundInContentsSpace(paint_info);
   gfx::Rect visual_rect;
-  if (painting_background_in_contents_space) {
+  if (paint_info.IsPaintingBackgroundInContentsSpace()) {
     // For the case where we are painting the background in the contents space,
     // we need to include the entire overflow rect.
     const LayoutBox& layout_box = To<LayoutBox>(layout_object);
@@ -1062,7 +1053,7 @@
   // Record the scroll hit test after the non-scrolling background so
   // background squashing is not affected. Hit test order would be equivalent
   // if this were immediately before the non-scrolling background.
-  if (!painting_background_in_contents_space)
+  if (!paint_info.IsPaintingBackgroundInContentsSpace())
     RecordScrollHitTestData(paint_info, *background_client);
 }
 
@@ -1741,19 +1732,6 @@
   PaintInlineItems(paint_info, paint_offset, parent_offset, &children);
 }
 
-bool NGBoxFragmentPainter::IsPaintingBackgroundInContentsSpace(
-    const PaintInfo& paint_info) const {
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    return paint_info.IsPaintingBackgroundInContentsSpace();
-
-  // TODO(layout-dev): Change paint_info.PaintContainer to accept fragments
-  // once LayoutNG supports scrolling containers.
-  return paint_info.PaintFlags() & kPaintLayerPaintingOverflowContents &&
-         !(paint_info.PaintFlags() &
-           kPaintLayerPaintingCompositingBackgroundPhase) &&
-         box_fragment_.GetLayoutObject() == paint_info.PaintContainer();
-}
-
 bool NGBoxFragmentPainter::ShouldPaint(
     const ScopedPaintState& paint_state) const {
   // TODO(layout-dev): Add support for scrolling, see BlockPainter::ShouldPaint.
@@ -1816,7 +1794,7 @@
 
   // Clip to the overflow area.
   if (info.is_clipped_with_local_scrolling &&
-      !IsPaintingBackgroundInContentsSpace(paint_info)) {
+      !paint_info.IsPaintingBackgroundInContentsSpace()) {
     context.Clip(gfx::RectF(physical.OverflowClipRect(rect.offset)));
 
     // Adjust the paint rect to reflect a scrolled content box with borders at
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h
index 6bdfef76..e6d9087 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h
@@ -80,7 +80,6 @@
       const FillLayer&,
       BackgroundBleedAvoidance,
       bool is_painting_background_in_contents_space) const override;
-  bool IsPaintingBackgroundInContentsSpace(const PaintInfo&) const override;
 
   void PaintTextClipMask(const PaintInfo&,
                          const gfx::Rect& mask_rect,
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter_test.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter_test.cc
index bb2abb17..711d533 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter_test.cc
@@ -120,7 +120,7 @@
   auto* builder = MakeGarbageCollected<PaintRecordBuilder>();
   builder->Context().SetPaintPreviewTracker(&tracker);
 
-  GetDocument().View()->PaintContentsOutsideOfLifecycle(
+  GetDocument().View()->PaintOutsideOfLifecycle(
       builder->Context(),
       kGlobalPaintNormalPhase | kGlobalPaintAddUrlMetadata |
           kGlobalPaintFlattenCompositingLayers,
@@ -179,7 +179,7 @@
   GetDocument().GetLayoutView()->CommitPendingSelection();
   UpdateAllLifecyclePhasesForTest();
   auto* builder = MakeGarbageCollected<PaintRecordBuilder>();
-  GetDocument().View()->PaintContentsOutsideOfLifecycle(
+  GetDocument().View()->PaintOutsideOfLifecycle(
       builder->Context(),
       kGlobalPaintSelectionDragImageOnly | kGlobalPaintFlattenCompositingLayers,
       CullRect::Infinite());
diff --git a/third_party/blink/renderer/core/paint/paint_controller_paint_test.h b/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
index 19e2bd3..105e1ee0 100644
--- a/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
+++ b/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
@@ -52,7 +52,7 @@
   }
 
   void PaintContents(const gfx::Rect& interest_rect) {
-    GetDocument().View()->PaintContentsForTest(CullRect(interest_rect));
+    GetDocument().View()->PaintForTest(CullRect(interest_rect));
   }
 
   void InvalidateAll() {
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index cca286c..5582faa9 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -120,7 +120,6 @@
   Member<void*> members1[5];
   LayoutUnit layout_units[4];
   gfx::Size size;
-  CullRect previous_cull_rect;
   Member<void*> members2[5];
 };
 
@@ -1220,7 +1219,7 @@
                                       kExcludeOverlayScrollbarSizeForHitTesting,
                                       respect_overflow_clip);
   Clipper(GeometryMapperOption::kUseGeometryMapper)
-      .CalculateRects(clip_rects_context, fragment.fragment_data, nullptr,
+      .CalculateRects(clip_rects_context, fragment.fragment_data,
                       fragment.layer_bounds, fragment.background_rect,
                       fragment.foreground_rect);
   if (GetLayoutObject().CanTraversePhysicalFragments()) {
@@ -1243,14 +1242,28 @@
   return !EnclosingCompositedScrollingLayerUnderPagination(kIncludeSelf);
 }
 
+const LayoutBox* PaintLayer::GetLayoutBoxWithBlockFragments() const {
+  const LayoutBox* layout_box = GetLayoutBox();
+  if (!layout_box)
+    return nullptr;
+  if (!layout_box->CanTraversePhysicalFragments())
+    return nullptr;
+  if (!layout_box->PhysicalFragmentCount()) {
+    NOTREACHED();
+    // TODO(crbug.com/1273068): The box has no fragments. This is
+    // unexpected, and we must have failed a bunch of DCHECKs (if enabled)
+    // on our way here. If the LayoutBox has never been laid out, it will
+    // have no fragments. But then we shouldn't really be here. Fall back to
+    // legacy LayoutObject tree traversal for this layer.
+    return nullptr;
+  }
+  return layout_box;
+}
+
 void PaintLayer::CollectFragments(
     PaintLayerFragments& fragments,
     const PaintLayer* root_layer,
-    const CullRect* painting_cull_rect,
-    OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior,
     ShouldRespectOverflowClipType respect_overflow_clip,
-    const PhysicalOffset* offset_from_root,
-    const PhysicalOffset& sub_pixel_accumulation,
     const FragmentData* root_fragment) const {
   PaintLayerFragment fragment;
   const auto& first_fragment_data = GetLayoutObject().FirstFragment();
@@ -1272,27 +1285,10 @@
       is_fragmented &&
       root_layer->EnclosingPaginationLayer() == EnclosingPaginationLayer();
 
-  const LayoutBox* layout_box_with_fragments = nullptr;
-  if (GetLayoutObject().CanTraversePhysicalFragments()) {
-    layout_box_with_fragments = GetLayoutBox();
-    if (layout_box_with_fragments) {
-      if (!layout_box_with_fragments->PhysicalFragmentCount()) {
-        NOTREACHED();
-        // TODO(crbug.com/1273068): The box has no fragments. This is
-        // unexpected, and we must have failed a bunch of DCHECKs (if enabled)
-        // on our way here. If the LayoutBox has never been laid out, it will
-        // have no fragments. But then we shouldn't really be here. Fall back to
-        // legacy LayoutObject tree traversal for this layer. There's code that
-        // requires that there be at least one PaintLayerFragment, so leaving
-        // empty-handed isn't an option.
-        layout_box_with_fragments = nullptr;
-      }
-    }
-  }
+  const LayoutBox* layout_box_with_fragments = GetLayoutBoxWithBlockFragments();
 
   // The inherited offset_from_root does not include any pagination offsets.
   // In the presence of fragmentation, we cannot use it.
-  bool offset_from_root_can_be_used = offset_from_root && !is_fragmented;
   wtf_size_t physical_fragment_idx = 0u;
   for (auto* fragment_data = &first_fragment_data; fragment_data;
        fragment_data = fragment_data->NextFragment(), physical_fragment_idx++) {
@@ -1302,63 +1298,46 @@
       continue;
     }
 
-    // If CullRectUpdateEnabled, skip fragment geometry logic if we are
-    // collecting fragments for painting.
-    if (!RuntimeEnabledFeatures::CullRectUpdateEnabled() ||
-        !painting_cull_rect) {
-      const FragmentData* root_fragment_data;
-      if (root_layer == this) {
-        root_fragment_data = fragment_data;
-      } else if (should_match_fragments) {
-        for (root_fragment_data = &first_root_fragment_data; root_fragment_data;
-             root_fragment_data = root_fragment_data->NextFragment()) {
-          if (root_fragment_data->FragmentID() == fragment_data->FragmentID())
-            break;
-        }
-      } else {
-        root_fragment_data = &first_root_fragment_data;
+    const FragmentData* root_fragment_data;
+    if (root_layer == this) {
+      root_fragment_data = fragment_data;
+    } else if (should_match_fragments) {
+      for (root_fragment_data = &first_root_fragment_data; root_fragment_data;
+           root_fragment_data = root_fragment_data->NextFragment()) {
+        if (root_fragment_data->FragmentID() == fragment_data->FragmentID())
+          break;
       }
-
-      bool cant_find_fragment = !root_fragment_data;
-      if (cant_find_fragment) {
-        DCHECK(should_match_fragments);
-        // Fall back to the first fragment, in order to have
-        // PaintLayerClipper at least compute |fragment.layer_bounds|.
-        root_fragment_data = &first_root_fragment_data;
-      }
-
-      ClipRectsContext clip_rects_context(
-          root_layer, root_fragment_data, overlay_scrollbar_clip_behavior,
-          respect_overflow_clip, sub_pixel_accumulation);
-
-      absl::optional<CullRect> fragment_cull_rect;
-      if (painting_cull_rect) {
-        // |cull_rect| is in the coordinate space of |root_layer| (i.e. the
-        // space of |root_layer|'s first fragment). Map the rect to the space of
-        // the current root fragment.
-        auto rect = painting_cull_rect->Rect();
-        first_root_fragment_data.MapRectToFragment(*root_fragment_data, rect);
-        fragment_cull_rect.emplace(rect);
-      }
-
-      Clipper(GeometryMapperOption::kUseGeometryMapper)
-          .CalculateRects(
-              clip_rects_context, fragment_data,
-              fragment_cull_rect ? &*fragment_cull_rect : nullptr,
-              fragment.layer_bounds, fragment.background_rect,
-              fragment.foreground_rect,
-              offset_from_root_can_be_used ? offset_from_root : nullptr);
-
-      if (cant_find_fragment) {
-        // If we couldn't find a matching fragment when |should_match_fragments|
-        // was true, then fall back to no clip.
-        fragment.background_rect.Reset();
-        fragment.foreground_rect.Reset();
-      }
-
-      fragment.root_fragment_data = root_fragment_data;
+    } else {
+      root_fragment_data = &first_root_fragment_data;
     }
 
+    bool cant_find_fragment = !root_fragment_data;
+    if (cant_find_fragment) {
+      DCHECK(should_match_fragments);
+      // Fall back to the first fragment, in order to have
+      // PaintLayerClipper at least compute |fragment.layer_bounds|.
+      root_fragment_data = &first_root_fragment_data;
+    }
+
+    ClipRectsContext clip_rects_context(
+        root_layer, root_fragment_data,
+        kExcludeOverlayScrollbarSizeForHitTesting, respect_overflow_clip,
+        PhysicalOffset());
+
+    Clipper(GeometryMapperOption::kUseGeometryMapper)
+        .CalculateRects(clip_rects_context, fragment_data,
+                        fragment.layer_bounds, fragment.background_rect,
+                        fragment.foreground_rect);
+
+    if (cant_find_fragment) {
+      // If we couldn't find a matching fragment when |should_match_fragments|
+      // was true, then fall back to no clip.
+      fragment.background_rect.Reset();
+      fragment.foreground_rect.Reset();
+    }
+
+    fragment.root_fragment_data = root_fragment_data;
+
     fragment.fragment_data = fragment_data;
 
     if (layout_box_with_fragments) {
@@ -1759,9 +1738,8 @@
       AppendSingleFragmentIgnoringPaginationForHitTesting(layer_fragments,
                                                           clip_behavior);
     } else {
-      CollectFragments(layer_fragments, &transform_container, nullptr,
-                       kExcludeOverlayScrollbarSizeForHitTesting, clip_behavior,
-                       nullptr, PhysicalOffset(), container_fragment);
+      CollectFragments(layer_fragments, &transform_container, clip_behavior,
+                       container_fragment);
     }
 
     // See if the hit test pos is inside the resizer of current layer. This
@@ -1947,9 +1925,8 @@
   PaintLayerFragments fragments;
   ClearCollectionScope<PaintLayerFragments> scope(&fragments);
 
-  CollectFragments(fragments, &transform_container, nullptr,
-                   kExcludeOverlayScrollbarSizeForHitTesting, clip_behavior,
-                   nullptr, PhysicalOffset(), container_fragment);
+  CollectFragments(fragments, &transform_container, clip_behavior,
+                   container_fragment);
 
   for (const auto& fragment : fragments) {
     // Apply any clips established by layers in between us and the root layer.
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index 383fdd1..085c5d5 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -59,7 +59,6 @@
 #include "third_party/blink/renderer/core/paint/paint_layer_stacking_node.h"
 #include "third_party/blink/renderer/core/paint/paint_result.h"
 #include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
-#include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
@@ -208,6 +207,9 @@
   LayoutBox* GetLayoutBox() const {
     return DynamicTo<LayoutBox>(layout_object_.Get());
   }
+  // Returns |GetLayoutBox()| if it exists and has fragments.
+  const LayoutBox* GetLayoutBoxWithBlockFragments() const;
+
   PaintLayer* Parent() const { return parent_; }
   PaintLayer* PreviousSibling() const { return previous_; }
   PaintLayer* NextSibling() const { return next_; }
@@ -583,16 +585,6 @@
 
   void DidUpdateScrollsOverflow();
 
-  void CollectFragments(
-      PaintLayerFragments&,
-      const PaintLayer* root_layer,
-      const CullRect* painting_cull_rect,
-      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize,
-      ShouldRespectOverflowClipType = kRespectOverflowClip,
-      const PhysicalOffset* offset_from_root = nullptr,
-      const PhysicalOffset& sub_pixel_accumulation = PhysicalOffset(),
-      const FragmentData* root_fragment = nullptr) const;
-
   bool SelfNeedsRepaint() const { return self_needs_repaint_; }
   bool DescendantNeedsRepaint() const { return descendant_needs_repaint_; }
   bool SelfOrDescendantNeedsRepaint() const {
@@ -624,17 +616,10 @@
     descendant_needs_cull_rect_update_ = false;
   }
 
-  // These previousXXX() functions are for subsequence caching. They save the
-  // painting status of the layer during the previous painting with subsequence.
-  // A painting without subsequence [1] doesn't change this status.  [1] See
-  // shouldCreateSubsequence() in PaintLayerPainter.cpp for the cases we use
-  // subsequence when painting a PaintLayer.
-
-  // TODO(wangxianzhu): Remove these functions when we use the cull rects
-  // in FragmentData updated during PrePaint.
-  const CullRect& PreviousCullRect() const { return previous_cull_rect_; }
-  void SetPreviousCullRect(const CullRect& rect) { previous_cull_rect_ = rect; }
-
+  // The paint result of this layer during the previous painting with
+  // subsequence. A painting without subsequence [1] doesn't change this flag.
+  // [1] See ShouldCreateSubsequence() in paint_layer_painter.cc for the cases
+  // we use subsequence when painting a PaintLayer.
   PaintResult PreviousPaintResult() const {
     return static_cast<PaintResult>(previous_paint_result_);
   }
@@ -716,6 +701,11 @@
       PaintLayerFragments&,
       ShouldRespectOverflowClipType) const;
 
+  void CollectFragments(PaintLayerFragments&,
+                        const PaintLayer* root_layer,
+                        ShouldRespectOverflowClipType = kRespectOverflowClip,
+                        const FragmentData* root_fragment = nullptr) const;
+
   struct HitTestRecursionData {
     const PhysicalRect& rect;
     // Whether location.Intersects(rect) returns true.
@@ -944,8 +934,6 @@
   LayoutUnit static_inline_position_;
   LayoutUnit static_block_position_;
 
-  CullRect previous_cull_rect_;
-
   // The first ancestor that is a scroll container. This is not a member of
   // AncestorDependentCompositingInputs as it is needed when
   // |needs_descendant_dependent_flags_update_| is true. In other words, it is
diff --git a/third_party/blink/renderer/core/paint/paint_layer_clipper.cc b/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
index 8115b1e..f345c94 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
@@ -165,36 +165,28 @@
 void PaintLayerClipper::CalculateRectsWithGeometryMapper(
     const ClipRectsContext& context,
     const FragmentData& fragment_data,
-    const CullRect* cull_rect,
     PhysicalRect& layer_bounds,
     ClipRect& background_rect,
-    ClipRect& foreground_rect,
-    const PhysicalOffset* offset_from_root) const {
+    ClipRect& foreground_rect) const {
   layer_bounds.size = PhysicalSize(layer_->PixelSnappedSize());
-  if (offset_from_root) {
-    layer_bounds.offset = *offset_from_root;
+  layer_bounds.offset = context.sub_pixel_accumulation;
+  if (layer_ == context.root_layer) {
+    DCHECK_EQ(&fragment_data, context.root_fragment);
   } else {
-    layer_bounds.offset = context.sub_pixel_accumulation;
-    if (layer_ == context.root_layer) {
-      DCHECK_EQ(&fragment_data, context.root_fragment);
-    } else {
-      layer_bounds.Move(fragment_data.PaintOffset());
-      gfx::RectF float_bounds(layer_bounds);
-      GeometryMapper::SourceToDestinationRect(
-          fragment_data.PreTransform(),
-          context.root_fragment->LocalBorderBoxProperties().Transform(),
-          float_bounds);
-      layer_bounds = PhysicalRect::FastAndLossyFromRectF(float_bounds);
-      layer_bounds.offset -= context.root_fragment->PaintOffset();
-    }
+    layer_bounds.Move(fragment_data.PaintOffset());
+    gfx::RectF float_bounds(layer_bounds);
+    GeometryMapper::SourceToDestinationRect(
+        fragment_data.PreTransform(),
+        context.root_fragment->LocalBorderBoxProperties().Transform(),
+        float_bounds);
+    layer_bounds = PhysicalRect::FastAndLossyFromRectF(float_bounds);
+    layer_bounds.offset -= context.root_fragment->PaintOffset();
   }
 
   CalculateBackgroundClipRectWithGeometryMapper(
       context, fragment_data, kRespectOverflowClip, background_rect);
 
   foreground_rect.Reset();
-  if (cull_rect)
-    background_rect.Intersect(PhysicalRect(cull_rect->Rect()));
 
   if (ShouldClipOverflowAlongEitherAxis(context)) {
     LayoutBoxModelObject& layout_object = layer_->GetLayoutObject();
@@ -210,23 +202,19 @@
   }
 }
 
-void PaintLayerClipper::CalculateRects(
-    const ClipRectsContext& context,
-    const FragmentData* fragment_data,
-    const CullRect* cull_rect,
-    PhysicalRect& layer_bounds,
-    ClipRect& background_rect,
-    ClipRect& foreground_rect,
-    const PhysicalOffset* offset_from_root) const {
+void PaintLayerClipper::CalculateRects(const ClipRectsContext& context,
+                                       const FragmentData* fragment_data,
+                                       PhysicalRect& layer_bounds,
+                                       ClipRect& background_rect,
+                                       ClipRect& foreground_rect) const {
   if (use_geometry_mapper_) {
     DCHECK(fragment_data);
     DCHECK(fragment_data->HasLocalBorderBoxProperties());
     // TODO(chrishtr): find the root cause of not having a fragment and fix it.
     if (!fragment_data->HasLocalBorderBoxProperties())
       return;
-    CalculateRectsWithGeometryMapper(context, *fragment_data, cull_rect,
-                                     layer_bounds, background_rect,
-                                     foreground_rect, offset_from_root);
+    CalculateRectsWithGeometryMapper(context, *fragment_data, layer_bounds,
+                                     background_rect, foreground_rect);
     return;
   }
   DCHECK(!fragment_data);
@@ -238,16 +226,11 @@
     CalculateBackgroundClipRect(context, background_rect);
     background_rect.Move(context.sub_pixel_accumulation);
   }
-  if (cull_rect)
-    background_rect.Intersect(PhysicalRect(cull_rect->Rect()));
 
   foreground_rect = background_rect;
 
   PhysicalOffset offset(context.sub_pixel_accumulation);
-  if (offset_from_root)
-    offset = *offset_from_root;
-  else
-    layer_->ConvertToLayerCoords(context.root_layer, offset);
+  layer_->ConvertToLayerCoords(context.root_layer, offset);
   layer_bounds = PhysicalRect(offset, PhysicalSize(layer_->PixelSnappedSize()));
 
   // Update the clip rects that will be passed to child layers.
diff --git a/third_party/blink/renderer/core/paint/paint_layer_clipper.h b/third_party/blink/renderer/core/paint/paint_layer_clipper.h
index f010ff0..a138b4b 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_clipper.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_clipper.h
@@ -49,7 +49,6 @@
 #include "third_party/blink/renderer/core/layout/geometry/physical_offset.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 #include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
-#include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
@@ -171,27 +170,20 @@
   // space. Only looks for clips up to the given ancestor.
   PhysicalRect LocalClipRect(const PaintLayer& ancestor_layer) const;
 
-  // Computes the same thing as backgroundRect in calculateRects(), but skips
-  // applying CSS clip and the visualOverflowRect() of |m_layer|.
+  // Computes the same thing as |background_rect| in CalculateRects(), but
+  // skips applying CSS clip and the VisualOverflowRect() of |layer_|.
   void CalculateBackgroundClipRect(const ClipRectsContext&,
                                    ClipRect& output) const;
 
   // This method figures out our layerBounds in coordinates relative to
   // |root_layer|. It also computes our background and foreground clip rects
   // for painting/event handling. Pass offsetFromRoot if known.
-  // If provided, |offset_from_root| is not changed and assumed to already
-  // include subpixel accumualation. Otherwise it is set to the offset from
-  // |layer_| to |root_layer|, plus |context.sub_pixel_accumuation|.
   // |fragment_data| is only used in kUseGeometryMapper mode.
-  // If |cull_rect| is provided, intersects |background_rect| and
-  // |foreground_rect| with it.
   void CalculateRects(const ClipRectsContext&,
                       const FragmentData*,
-                      const CullRect* cull_rect,
                       PhysicalRect& layer_bounds,
                       ClipRect& background_rect,
-                      ClipRect& foreground_rect,
-                      const PhysicalOffset* offset_from_root = nullptr) const;
+                      ClipRect& foreground_rect) const;
 
  private:
   void CalculateClipRects(const ClipRectsContext&, ClipRects&) const;
@@ -210,11 +202,9 @@
   ALWAYS_INLINE void CalculateRectsWithGeometryMapper(
       const ClipRectsContext&,
       const FragmentData&,
-      const CullRect* cull_rect,
       PhysicalRect& layer_bounds,
       ClipRect& background_rect,
-      ClipRect& foreground_rect,
-      const PhysicalOffset* offset_from_root = nullptr) const;
+      ClipRect& foreground_rect) const;
 
   // Returns the visual rect of |layer_| in local space. This includes
   // filter effects if needed.
diff --git a/third_party/blink/renderer/core/paint/paint_layer_clipper_test.cc b/third_party/blink/renderer/core/paint/paint_layer_clipper_test.cc
index cb666c2..903e56c 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_clipper_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_clipper_test.cc
@@ -135,7 +135,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &target_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   EXPECT_EQ(PhysicalRect(LayoutUnit(8.25), LayoutUnit(8.35), LayoutUnit(200),
                          LayoutUnit(300)),
@@ -168,7 +168,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &target_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
   // If the PaintLayer clips overflow, the background rect is intersected with
   // the PaintLayer bounds...
   EXPECT_EQ(PhysicalRect(8, 8, 200, 300), background_rect.Rect());
@@ -197,7 +197,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &target_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   // Only the foreground rect gets hasRadius set for overflow clipping
   // of descendants.
@@ -234,7 +234,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &child_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   EXPECT_EQ(PhysicalRect(0, 0, 200, 300), background_rect.Rect());
   EXPECT_TRUE(background_rect.HasRadius());
@@ -265,7 +265,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &target_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   PhysicalRect content_box_rect = target->PhysicalContentBoxRect();
   EXPECT_GT(foreground_rect.Rect().X(),
@@ -293,7 +293,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &target_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
   EXPECT_EQ(PhysicalRect(8, 8, 200, 300), background_rect.Rect());
   EXPECT_EQ(PhysicalRect(8, 8, 200, 300), foreground_rect.Rect());
   EXPECT_EQ(PhysicalRect(8, 8, 400, 0), layer_bounds);
@@ -315,7 +315,7 @@
 
   layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context, &layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
   EXPECT_GE(background_rect.Rect().Width().ToInt(), 33554422);
   EXPECT_GE(background_rect.Rect().Height().ToInt(), 33554422);
   EXPECT_EQ(background_rect.Rect(), foreground_rect.Rect());
@@ -326,7 +326,7 @@
 
   layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context_clip, &layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
   EXPECT_EQ(PhysicalRect(0, 0, 200, 200), background_rect.Rect());
   EXPECT_EQ(PhysicalRect(0, 0, 200, 200), foreground_rect.Rect());
   EXPECT_EQ(PhysicalRect(0, 0, 200, 200), layer_bounds);
@@ -350,7 +350,7 @@
 
   layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context, &layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
   EXPECT_EQ(PhysicalRect(0, 0, 200, 400), background_rect.Rect());
   EXPECT_EQ(PhysicalRect(0, 0, 200, 400), foreground_rect.Rect());
   EXPECT_EQ(PhysicalRect(0, 0, 200, 400), layer_bounds);
@@ -360,7 +360,7 @@
 
   layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context_clip, &layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
   EXPECT_EQ(PhysicalRect(0, 0, 200, 200), background_rect.Rect());
   EXPECT_EQ(PhysicalRect(0, 0, 200, 200), foreground_rect.Rect());
   EXPECT_EQ(PhysicalRect(0, 0, 200, 400), layer_bounds);
@@ -411,7 +411,7 @@
   ClipRect foreground_rect(infinite_rect);
   target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   EXPECT_EQ(PhysicalRect(0, 0, 50, 100), background_rect.Rect());
   EXPECT_EQ(PhysicalRect(0, 0, 50, 100), foreground_rect.Rect());
@@ -439,7 +439,7 @@
   ClipRect foreground_rect(infinite_rect);
   target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   // The background rect is used to clip stacking context (layer) output.
   // In this case, nothing is above us, thus the infinite rect. However we do
@@ -453,7 +453,7 @@
   background_rect = infinite_rect;
   foreground_rect = infinite_rect;
   target->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
-      .CalculateRects(context, nullptr, nullptr, layer_bounds, background_rect,
+      .CalculateRects(context, nullptr, layer_bounds, background_rect,
                       foreground_rect);
   // The non-GeometryMapper path applies the immediate filter effect in
   // background rect.
@@ -467,7 +467,7 @@
   foreground_rect = infinite_rect;
   target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(root_context, &target->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
   // This includes the filter effect because it's applied before mapping the
   // background rect to the root layer.
   EXPECT_EQ(PhysicalRect(38, 41, 204, 304), background_rect.Rect());
@@ -477,8 +477,8 @@
   background_rect = infinite_rect;
   foreground_rect = infinite_rect;
   target->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
-      .CalculateRects(root_context, nullptr, nullptr, layer_bounds,
-                      background_rect, foreground_rect);
+      .CalculateRects(root_context, nullptr, layer_bounds, background_rect,
+                      foreground_rect);
   EXPECT_EQ(PhysicalRect(38, 41, 204, 304), background_rect.Rect());
   EXPECT_EQ(PhysicalRect(90, 90, 100, 200), foreground_rect.Rect());
 }
@@ -516,7 +516,7 @@
   ClipRect foreground_rect(infinite_rect);
   target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   EXPECT_TRUE(IsInfinite(background_rect.Rect()));
   EXPECT_TRUE(IsInfinite(foreground_rect.Rect()));
@@ -548,7 +548,7 @@
   ClipRect foreground_rect(infinite_rect);
   target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   EXPECT_TRUE(IsInfinite(background_rect.Rect()));
   EXPECT_TRUE(IsInfinite(foreground_rect.Rect()));
@@ -581,7 +581,7 @@
   ClipRect foreground_rect(infinite_rect);
   target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   EXPECT_TRUE(IsInfinite(background_rect.Rect()));
   EXPECT_TRUE(IsInfinite(foreground_rect.Rect()));
@@ -617,7 +617,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &target_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   if (RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) {
     EXPECT_TRUE(IsInfinite(background_rect.Rect()));
@@ -635,7 +635,7 @@
       .CalculateRects(
           context,
           target_paint_layer->GetLayoutObject().FirstFragment().NextFragment(),
-          nullptr, layer_bounds, background_rect, foreground_rect);
+          layer_bounds, background_rect, foreground_rect);
 
   if (RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) {
     EXPECT_TRUE(IsInfinite(background_rect.Rect()));
@@ -676,7 +676,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &child_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   // The background and foreground rect are clipped by the scrollbar size.
   EXPECT_EQ(PhysicalRect(0, 0, 193, 293), background_rect.Rect());
@@ -685,7 +685,7 @@
 
   child_paint_layer
       ->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
-      .CalculateRects(context, nullptr, nullptr, layer_bounds, background_rect,
+      .CalculateRects(context, nullptr, layer_bounds, background_rect,
                       foreground_rect);
 
   // The background and foreground rect are clipped by the scrollbar size.
@@ -720,7 +720,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &child_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   // The background and foreground rect are clipped by the scrollbar size.
   EXPECT_EQ(PhysicalRect(8, 8, 193, 293), background_rect.Rect());
@@ -729,7 +729,7 @@
 
   child_paint_layer
       ->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
-      .CalculateRects(context, nullptr, nullptr, layer_bounds, background_rect,
+      .CalculateRects(context, nullptr, layer_bounds, background_rect,
                       foreground_rect);
 
   // The background and foreground rect are clipped by the scrollbar size.
@@ -762,7 +762,7 @@
       ->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
       .CalculateRects(context,
                       &parent_paint_layer->GetLayoutObject().FirstFragment(),
-                      nullptr, layer_bounds, background_rect, foreground_rect);
+                      layer_bounds, background_rect, foreground_rect);
 
   // Only the foreground is clipped by the scrollbar size, because we
   // called CalculateRects on the root layer.
@@ -772,7 +772,7 @@
 
   parent_paint_layer
       ->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
-      .CalculateRects(context, nullptr, nullptr, layer_bounds, background_rect,
+      .CalculateRects(context, nullptr, layer_bounds, background_rect,
                       foreground_rect);
 
   // Only the foreground is clipped by the scrollbar size, because we
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter.cc b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
index 229733b..b19d90c 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
@@ -31,22 +31,13 @@
 namespace blink {
 
 void PaintLayerPainter::Paint(GraphicsContext& context,
-                              const CullRect& cull_rect,
                               const GlobalPaintFlags global_paint_flags,
                               PaintLayerFlags paint_flags) {
-  PaintLayerPaintingInfo painting_info(&paint_layer_, cull_rect,
-                                       global_paint_flags, PhysicalOffset());
+  PaintLayerPaintingInfo painting_info(&paint_layer_, global_paint_flags,
+                                       PhysicalOffset());
   Paint(context, painting_info, paint_flags);
 }
 
-static ShouldRespectOverflowClipType ShouldRespectOverflowClip(
-    PaintLayerFlags paint_flags,
-    const LayoutObject& layout_object) {
-  return (paint_flags & kPaintLayerPaintingOverflowContents)
-             ? kIgnoreOverflowClip
-             : kRespectOverflowClip;
-}
-
 bool PaintLayerPainter::PaintedOutputInvisible(const ComputedStyle& style) {
   if (style.HasNonInitialBackdropFilter())
     return false;
@@ -156,30 +147,6 @@
   return true;
 }
 
-static bool ShouldRepaintSubsequence(
-    PaintLayer& paint_layer,
-    const PaintLayerPaintingInfo& painting_info) {
-  // Repaint subsequence if the layer is marked for needing repaint.
-  if (paint_layer.SelfOrDescendantNeedsRepaint())
-    return true;
-
-  // Repaint if previously the layer may be clipped by cull rect, and cull rect
-  // changes.
-  if (!RuntimeEnabledFeatures::CullRectUpdateEnabled() &&
-      paint_layer.PreviousCullRect() != painting_info.cull_rect) {
-    if (paint_layer.PreviousPaintResult() == kMayBeClippedByCullRect)
-      return true;
-    // When PaintUnderInvalidationChecking is enabled, always repaint the
-    // subsequence when the paint rect changes because we will strictly match
-    // new and cached subsequences. Normally we can reuse the cached fully
-    // painted subsequence even if we would partially paint this time.
-    if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled())
-      return true;
-  }
-
-  return false;
-}
-
 static bool IsUnclippedLayoutView(const PaintLayer& layer) {
   // If MainFrameClipsContent is false which means that WebPreferences::
   // record_whole_document is true, we should not cull the scrolling contents
@@ -193,7 +160,6 @@
 }
 
 bool PaintLayerPainter::ShouldUseInfiniteCullRect() {
-  DCHECK(RuntimeEnabledFeatures::CullRectUpdateEnabled());
   return ShouldUseInfiniteCullRectInternal(kGlobalPaintNormalPhase,
                                            /*for_cull_rect_update*/ true);
 }
@@ -208,8 +174,8 @@
   // Cull rects and clips can't be propagated across a filter which moves
   // pixels, since the input of the filter may be outside the cull rect /
   // clips yet still result in painted output.
-  // TODO(wangxianzhu): With CullRectUpdate, we can let CullRect support
-  // mapping for pixel moving filters to avoid this infinite cull rect.
+  // TODO(wangxianzhu): We can let CullRect support mapping for pixel moving
+  // filters to avoid this infinite cull rect.
   if (paint_layer_.HasFilterThatMovesPixels() &&
       // However during printing, we don't want filter outset to cross page
       // boundaries. This also avoids performance issue because the PDF renderer
@@ -265,7 +231,7 @@
   //   2) Complexity: Difficulty updating clips when ancestor transforms
   //      change.
   // For these reasons, we use an infinite dirty rect here.
-  // The reasons don't apply for CullRectUpdate.
+  // The reasons don't apply for CullRectUpdater.
   if (!for_cull_rect_update && paint_layer_.PaintsWithTransform(global_flags) &&
       // The reasons don't apply for printing though, because when we enter and
       // leaving printing mode, full invalidations occur.
@@ -285,7 +251,6 @@
       ShouldUseInfiniteCullRectInternal(painting_info.GetGlobalPaintFlags(),
                                         /*for_cull_rect_update*/ false);
   if (should_use_infinite_cull_rect) {
-    painting_info.cull_rect = CullRect::Infinite();
     // Avoid clipping during CollectFragments.
     if (IsUnclippedLayoutView(paint_layer_))
       paint_flags |= kPaintLayerPaintingOverflowContents;
@@ -310,20 +275,6 @@
         first_fragment.LocalBorderBoxProperties().Transform();
     if (source_transform == &destination_transform)
       return;
-
-    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-      DCHECK(RuntimeEnabledFeatures::CullRectUpdateEnabled());
-    } else if (!painting_info.cull_rect.IsInfinite()) {
-      auto rect = painting_info.cull_rect.Rect();
-      const FragmentData& primary_stitching_fragment =
-          paint_layer_.GetLayoutObject().PrimaryStitchingFragment();
-      const FragmentData& primary_root_stitching_fragment =
-          painting_info.root_layer->GetLayoutObject()
-              .PrimaryStitchingFragment();
-      primary_root_stitching_fragment.MapRectToFragment(
-          primary_stitching_fragment, rect);
-      painting_info.cull_rect = CullRect(rect);
-    }
   }
 
   // We reach here if the layer requires infinite cull rect or has different
@@ -368,8 +319,6 @@
     // TODO(crbug.com/848056): This can happen e.g. when we paint a filter
     // referencing a SVG foreign object through feImage, especially when there
     // is circular references. Should find a better solution.
-    if (!RuntimeEnabledFeatures::CullRectUpdateEnabled())
-      paint_layer_.SetPreviousCullRect(CullRect());
     return kMayBeClippedByCullRect;
   }
 
@@ -420,9 +369,6 @@
       is_self_painting_layer && !is_painting_overlay_overflow_controls &&
       is_painting_composited_decoration && object.StyleRef().HasOutline();
 
-  ShouldRespectOverflowClipType respect_overflow_clip =
-      ShouldRespectOverflowClip(paint_flags, object);
-
   bool should_paint_content =
       paint_layer_.HasVisibleContent() &&
       // Content under a LayoutSVGHiddenContainer is auxiliary resources for
@@ -438,89 +384,50 @@
     offset_from_root -= root->GetLayoutObject().FirstFragment().PaintOffset();
   offset_from_root += painting_info.sub_pixel_accumulation;
 
-  if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
-    if (object.FirstFragment().NextFragment() ||
-        IsUnclippedLayoutView(paint_layer_)) {
-      result = kMayBeClippedByCullRect;
-    } else {
-      gfx::Rect visual_rect = FirstFragmentVisualRect(object);
-      gfx::Rect cull_rect = object.FirstFragment().GetCullRect().Rect();
-      bool cull_rect_intersects_self = cull_rect.Intersects(visual_rect);
-      if (!cull_rect.Contains(visual_rect))
-        result = kMayBeClippedByCullRect;
-
-      bool cull_rect_intersects_contents = true;
-      if (const auto* box = DynamicTo<LayoutBox>(object)) {
-        PhysicalRect contents_visual_rect(
-            ContentsVisualRect(object.FirstFragment(), *box));
-        PhysicalRect contents_cull_rect(
-            object.FirstFragment().GetContentsCullRect().Rect());
-        cull_rect_intersects_contents =
-            contents_cull_rect.Intersects(contents_visual_rect);
-        if (!contents_cull_rect.Contains(contents_visual_rect))
-          result = kMayBeClippedByCullRect;
-      } else {
-        cull_rect_intersects_contents = cull_rect_intersects_self;
-      }
-
-      if (!cull_rect_intersects_self && !cull_rect_intersects_contents) {
-        if (!is_painting_overflow_contents &&
-            paint_layer_.KnownToClipSubtree()) {
-          paint_layer_.SetPreviousPaintResult(kMayBeClippedByCullRect);
-          return kMayBeClippedByCullRect;
-        }
-        should_paint_content = false;
-      }
-
-      // The above doesn't consider clips on non-self-painting contents.
-      // Will update in ScopedBoxContentsPaintState.
-    }
+  if (object.FirstFragment().NextFragment() ||
+      IsUnclippedLayoutView(paint_layer_)) {
+    result = kMayBeClippedByCullRect;
   } else {
-    PhysicalRect bounds = paint_layer_.PhysicalBoundingBox(offset_from_root);
-    if (!PhysicalRect(painting_info.cull_rect.Rect()).Contains(bounds))
+    gfx::Rect visual_rect = FirstFragmentVisualRect(object);
+    gfx::Rect cull_rect = object.FirstFragment().GetCullRect().Rect();
+    bool cull_rect_intersects_self = cull_rect.Intersects(visual_rect);
+    if (!cull_rect.Contains(visual_rect))
       result = kMayBeClippedByCullRect;
+
+    bool cull_rect_intersects_contents = true;
+    if (const auto* box = DynamicTo<LayoutBox>(object)) {
+      PhysicalRect contents_visual_rect(
+          ContentsVisualRect(object.FirstFragment(), *box));
+      PhysicalRect contents_cull_rect(
+          object.FirstFragment().GetContentsCullRect().Rect());
+      cull_rect_intersects_contents =
+          contents_cull_rect.Intersects(contents_visual_rect);
+      if (!contents_cull_rect.Contains(contents_visual_rect))
+        result = kMayBeClippedByCullRect;
+    } else {
+      cull_rect_intersects_contents = cull_rect_intersects_self;
+    }
+
+    if (!cull_rect_intersects_self && !cull_rect_intersects_contents) {
+      if (!is_painting_overflow_contents && paint_layer_.KnownToClipSubtree()) {
+        paint_layer_.SetPreviousPaintResult(kMayBeClippedByCullRect);
+        return kMayBeClippedByCullRect;
+      }
+      should_paint_content = false;
+    }
+
+    // The above doesn't consider clips on non-self-painting contents.
+    // Will update in ScopedBoxContentsPaintState.
   }
 
   PaintLayerPaintingInfo local_painting_info(painting_info);
 
-  PaintLayerFragments layer_fragments;
-  ClearCollectionScope<PaintLayerFragments> scope(&layer_fragments);
-
-  if (should_paint_content || should_paint_self_outline ||
-      is_painting_overlay_overflow_controls) {
-    // Collect the fragments. If CullRectUpdate is enabled, this will just
-    // create a light-weight adapter from FragmentData to PaintLayerFragment
-    // and we'll remove the adapter in the future. Otherwise this will compute
-    // the clip rectangles and paint offsets for each layer fragment.
-    paint_layer_.CollectFragments(
-        layer_fragments, local_painting_info.root_layer,
-        &local_painting_info.cull_rect, kIgnoreOverlayScrollbarSize,
-        respect_overflow_clip, &offset_from_root,
-        local_painting_info.sub_pixel_accumulation);
-
-    if (!RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
-      // PaintLayer::CollectFragments depends on the paint dirty rect in
-      // complicated ways. For now, always assume a partially painted output
-      // for fragmented content.
-      if (layer_fragments.size() > 1)
-        result = kMayBeClippedByCullRect;
-
-      if (should_paint_content) {
-        should_paint_content = AtLeastOneFragmentIntersectsDamageRect(
-            layer_fragments, local_painting_info, paint_flags,
-            offset_from_root);
-        if (!should_paint_content)
-          result = kMayBeClippedByCullRect;
-      }
-    }
-  }
-
   bool should_create_subsequence =
       should_paint_content &&
       ShouldCreateSubsequence(paint_layer_, context, painting_info);
   absl::optional<SubsequenceRecorder> subsequence_recorder;
   if (should_create_subsequence) {
-    if (!ShouldRepaintSubsequence(paint_layer_, painting_info) &&
+    if (!paint_layer_.SelfOrDescendantNeedsRepaint() &&
         SubsequenceRecorder::UseCachedSubsequenceIfPossible(context,
                                                             paint_layer_)) {
       return paint_layer_.PreviousPaintResult();
@@ -562,9 +469,8 @@
   }
 
   if (should_paint_background) {
-    PaintBackgroundForFragmentsWithPhase(PaintPhase::kSelfBlockBackgroundOnly,
-                                         layer_fragments, context,
-                                         local_painting_info, paint_flags);
+    PaintWithPhase(PaintPhase::kSelfBlockBackgroundOnly, context,
+                   local_painting_info, paint_flags);
   }
 
   if (should_paint_neg_z_order_list) {
@@ -582,19 +488,16 @@
         DisplayItem::kLayerChunkForeground);
 
     if (selection_drag_image_only) {
-      PaintForegroundForFragmentsWithPhase(PaintPhase::kSelectionDragImage,
-                                           layer_fragments, context,
-                                           local_painting_info, paint_flags);
+      PaintWithPhase(PaintPhase::kSelectionDragImage, context,
+                     local_painting_info, paint_flags);
     } else {
-      PaintForegroundForFragments(layer_fragments, context, local_painting_info,
-                                  paint_flags);
+      PaintForegroundPhases(context, local_painting_info, paint_flags);
     }
   }
 
   if (!is_video && should_paint_self_outline) {
-    PaintBackgroundForFragmentsWithPhase(PaintPhase::kSelfOutlineOnly,
-                                         layer_fragments, context,
-                                         local_painting_info, paint_flags);
+    PaintWithPhase(PaintPhase::kSelfOutlineOnly, context, local_painting_info,
+                   paint_flags);
   }
 
   if (should_paint_normal_flow_and_pos_z_order_lists) {
@@ -608,25 +511,22 @@
           ->ShouldOverflowControlsPaintAsOverlay()) {
     if (is_painting_overlay_overflow_controls ||
         !paint_layer_.NeedsReorderOverlayOverflowControls()) {
-      PaintOverlayOverflowControlsForFragments(
-          layer_fragments, context, local_painting_info, paint_flags);
+      PaintOverlayOverflowControls(context, local_painting_info, paint_flags);
     }
   }
 
   if (is_video && should_paint_self_outline) {
     // We paint outlines for video later so that they aren't obscured by the
     // video controls.
-    PaintBackgroundForFragmentsWithPhase(PaintPhase::kSelfOutlineOnly,
-                                         layer_fragments, context,
-                                         local_painting_info, paint_flags);
+    PaintWithPhase(PaintPhase::kSelfOutlineOnly, context, local_painting_info,
+                   paint_flags);
   }
 
   if (is_painting_mask && should_paint_content && !selection_drag_image_only) {
     if (const auto* properties = object.FirstFragment().PaintProperties()) {
       if (properties->Mask()) {
-        PaintBackgroundForFragmentsWithPhase(PaintPhase::kMask, layer_fragments,
-                                             context, local_painting_info,
-                                             paint_flags);
+        PaintWithPhase(PaintPhase::kMask, context, local_painting_info,
+                       paint_flags);
       }
       if (properties->ClipPathMask()) {
         PhysicalOffset visual_offset_from_root =
@@ -642,45 +542,9 @@
   }
 
   paint_layer_.SetPreviousPaintResult(result);
-  if (!RuntimeEnabledFeatures::CullRectUpdateEnabled())
-    paint_layer_.SetPreviousCullRect(local_painting_info.cull_rect);
   return result;
 }
 
-bool PaintLayerPainter::AtLeastOneFragmentIntersectsDamageRect(
-    PaintLayerFragments& fragments,
-    const PaintLayerPaintingInfo& local_painting_info,
-    PaintLayerFlags local_paint_flags,
-    const PhysicalOffset& offset_from_root) {
-  DCHECK(!RuntimeEnabledFeatures::CullRectUpdateEnabled());
-
-  if (&paint_layer_ == local_painting_info.root_layer &&
-      (local_paint_flags & kPaintLayerPaintingOverflowContents))
-    return true;
-
-  // Skip the optimization if the layer is fragmented to avoid complexity
-  // about overflows in fragments. LayoutObject painters will do cull rect
-  // optimization later.
-  if (paint_layer_.EnclosingPaginationLayer() || fragments.size() > 1)
-    return true;
-
-  return paint_layer_.IntersectsDamageRect(fragments[0].layer_bounds,
-                                           fragments[0].background_rect.Rect(),
-                                           offset_from_root);
-}
-
-template <typename Function>
-static void ForAllFragments(GraphicsContext& context,
-                            const PaintLayerFragments& fragments,
-                            const Function& function) {
-  for (wtf_size_t i = 0; i < fragments.size(); ++i) {
-    absl::optional<ScopedDisplayItemFragment> scoped_display_item_fragment;
-    if (i)
-      scoped_display_item_fragment.emplace(context, i);
-    function(fragments[i]);
-  }
-}
-
 PaintResult PaintLayerPainter::PaintChildren(
     PaintLayerIteration children_to_visit,
     GraphicsContext& context,
@@ -720,8 +584,7 @@
   return result;
 }
 
-void PaintLayerPainter::PaintOverlayOverflowControlsForFragments(
-    const PaintLayerFragments& layer_fragments,
+void PaintLayerPainter::PaintOverlayOverflowControls(
     GraphicsContext& context,
     const PaintLayerPaintingInfo& painting_info,
     PaintLayerFlags paint_flags) {
@@ -735,23 +598,26 @@
       paint_layer_.GetScrollableArea()->HasLayerForScrollCorner())
     return;
 
-  PaintBackgroundForFragmentsWithPhase(PaintPhase::kOverlayOverflowControls,
-                                       layer_fragments, context, painting_info,
-                                       paint_flags);
+  PaintWithPhase(PaintPhase::kOverlayOverflowControls, context, painting_info,
+                 paint_flags);
 }
 
 void PaintLayerPainter::PaintFragmentWithPhase(
     PaintPhase phase,
-    const PaintLayerFragment& fragment,
+    const FragmentData& fragment_data,
+    const NGPhysicalBoxFragment* physical_fragment,
     GraphicsContext& context,
-    const CullRect& cull_rect,
     const PaintLayerPaintingInfo& painting_info,
     PaintLayerFlags paint_flags) {
   DCHECK(paint_layer_.IsSelfPaintingLayer());
 
-  auto chunk_properties = fragment.fragment_data->LocalBorderBoxProperties();
+  CullRect cull_rect = fragment_data.GetCullRect();
+  if (cull_rect.Rect().IsEmpty())
+    return;
+
+  auto chunk_properties = fragment_data.LocalBorderBoxProperties();
   if (phase == PaintPhase::kMask) {
-    const auto* properties = fragment.fragment_data->PaintProperties();
+    const auto* properties = fragment_data.PaintProperties();
     DCHECK(properties);
     DCHECK(properties->Mask());
     chunk_properties.SetEffect(*properties->Mask());
@@ -766,111 +632,66 @@
   if (paint_layer_.GetLayoutObject().ChildPaintBlockedByDisplayLock())
     paint_info.SetDescendantPaintingBlocked(true);
 
-  if (fragment.physical_fragment) {
-    NGBoxFragmentPainter(*fragment.physical_fragment).Paint(paint_info);
+  if (physical_fragment) {
+    NGBoxFragmentPainter(*physical_fragment).Paint(paint_info);
   } else {
-    if (fragment.fragment_data)
-      paint_info.SetFragmentID(fragment.fragment_data->FragmentID());
+    paint_info.SetFragmentID(fragment_data.FragmentID());
     paint_layer_.GetLayoutObject().Paint(paint_info);
   }
 }
 
-static CullRect LegacyCullRect(const PaintLayerFragment& fragment,
-                               const ClipRect& clip_rect) {
-  DCHECK(!RuntimeEnabledFeatures::CullRectUpdateEnabled());
-  PhysicalRect new_cull_rect(clip_rect.Rect());
-  // Now |new_cull_rect| is in the pixel-snapped border box space of
-  // |fragment.root_fragment_data|. Adjust it to the containing transform node's
-  // space in which we will paint.
-  new_cull_rect.Move(PhysicalOffset(
-      ToRoundedPoint(fragment.root_fragment_data->PaintOffset())));
-  return CullRect(ToPixelSnappedRect(new_cull_rect));
-}
-
-void PaintLayerPainter::PaintBackgroundForFragmentsWithPhase(
+void PaintLayerPainter::PaintWithPhase(
     PaintPhase phase,
-    const PaintLayerFragments& layer_fragments,
     GraphicsContext& context,
     const PaintLayerPaintingInfo& local_painting_info,
     PaintLayerFlags paint_flags) {
-  ForAllFragments(
-      context, layer_fragments, [&](const PaintLayerFragment& fragment) {
-        CullRect cull_rect =
-            RuntimeEnabledFeatures::CullRectUpdateEnabled()
-                ? fragment.fragment_data->GetCullRect()
-                : LegacyCullRect(fragment, fragment.background_rect);
-        if (!cull_rect.Rect().IsEmpty()) {
-          PaintFragmentWithPhase(phase, fragment, context, cull_rect,
-                                 local_painting_info, paint_flags);
-        }
-      });
+  const auto* layout_box_with_fragments =
+      paint_layer_.GetLayoutBoxWithBlockFragments();
+  wtf_size_t fragment_idx = 0u;
+  for (const auto* fragment = &paint_layer_.GetLayoutObject().FirstFragment();
+       fragment; fragment = fragment->NextFragment(), ++fragment_idx) {
+    const NGPhysicalBoxFragment* physical_fragment = nullptr;
+    if (layout_box_with_fragments) {
+      physical_fragment =
+          layout_box_with_fragments->GetPhysicalFragment(fragment_idx);
+      DCHECK(physical_fragment);
+    }
+
+    absl::optional<ScopedDisplayItemFragment> scoped_display_item_fragment;
+    if (fragment_idx)
+      scoped_display_item_fragment.emplace(context, fragment_idx);
+
+    PaintFragmentWithPhase(phase, *fragment, physical_fragment, context,
+                           local_painting_info, paint_flags);
+  }
 }
 
-void PaintLayerPainter::PaintForegroundForFragments(
-    const PaintLayerFragments& layer_fragments,
+void PaintLayerPainter::PaintForegroundPhases(
     GraphicsContext& context,
     const PaintLayerPaintingInfo& local_painting_info,
     PaintLayerFlags paint_flags) {
-  PaintForegroundForFragmentsWithPhase(
-      PaintPhase::kDescendantBlockBackgroundsOnly, layer_fragments, context,
-      local_painting_info, paint_flags);
+  PaintWithPhase(PaintPhase::kDescendantBlockBackgroundsOnly, context,
+                 local_painting_info, paint_flags);
 
   if (paint_layer_.GetLayoutObject().GetDocument().InForcedColorsMode()) {
-    PaintForegroundForFragmentsWithPhase(PaintPhase::kForcedColorsModeBackplate,
-                                         layer_fragments, context,
-                                         local_painting_info, paint_flags);
+    PaintWithPhase(PaintPhase::kForcedColorsModeBackplate, context,
+                   local_painting_info, paint_flags);
   }
 
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() ||
       paint_layer_.NeedsPaintPhaseFloat()) {
-    PaintForegroundForFragmentsWithPhase(PaintPhase::kFloat, layer_fragments,
-                                         context, local_painting_info,
-                                         paint_flags);
+    PaintWithPhase(PaintPhase::kFloat, context, local_painting_info,
+                   paint_flags);
   }
 
-  PaintForegroundForFragmentsWithPhase(PaintPhase::kForeground, layer_fragments,
-                                       context, local_painting_info,
-                                       paint_flags);
+  PaintWithPhase(PaintPhase::kForeground, context, local_painting_info,
+                 paint_flags);
 
   if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() ||
       paint_layer_.NeedsPaintPhaseDescendantOutlines()) {
-    PaintForegroundForFragmentsWithPhase(PaintPhase::kDescendantOutlinesOnly,
-                                         layer_fragments, context,
-                                         local_painting_info, paint_flags);
+    PaintWithPhase(PaintPhase::kDescendantOutlinesOnly, context,
+                   local_painting_info, paint_flags);
   }
 }
 
-void PaintLayerPainter::PaintForegroundForFragmentsWithPhase(
-    PaintPhase phase,
-    const PaintLayerFragments& layer_fragments,
-    GraphicsContext& context,
-    const PaintLayerPaintingInfo& local_painting_info,
-    PaintLayerFlags paint_flags) {
-  ForAllFragments(
-      context, layer_fragments, [&](const PaintLayerFragment& fragment) {
-        CullRect cull_rect;
-        if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
-          // In CullRectUpdate, this function is the same as
-          // PaintBackgroundForFragmentsWithPhase(). The contents cull rect will
-          // be applied by ScopedBoxContentsPaintState.
-          cull_rect = fragment.fragment_data->GetCullRect();
-        } else {
-          cull_rect = LegacyCullRect(fragment, fragment.foreground_rect);
-        }
-        if (!cull_rect.Rect().IsEmpty()) {
-          PaintFragmentWithPhase(phase, fragment, context, cull_rect,
-                                 local_painting_info, paint_flags);
-        }
-      });
-}
-
-void PaintLayerPainter::PaintOverlayOverflowControls(
-    GraphicsContext& context,
-    const CullRect& cull_rect,
-    const GlobalPaintFlags paint_flags) {
-  PaintLayerPaintingInfo painting_info(&paint_layer_, cull_rect, paint_flags,
-                                       PhysicalOffset());
-  Paint(context, painting_info, kPaintLayerPaintingOverlayOverflowControls);
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter.h b/third_party/blink/renderer/core/paint/paint_layer_painter.h
index ab163a2..a16f0f0 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter.h
@@ -14,11 +14,10 @@
 
 namespace blink {
 
-class CullRect;
 class ComputedStyle;
 class FragmentData;
 class GraphicsContext;
-struct PhysicalOffset;
+class NGPhysicalBoxFragment;
 
 // This class is responsible for painting self-painting PaintLayer.
 //
@@ -30,11 +29,9 @@
  public:
   PaintLayerPainter(PaintLayer& paint_layer) : paint_layer_(paint_layer) {}
 
-  // The Paint() method paints the layers that intersect the cull rect from
-  // back to front.  paint() assumes that the caller will clip to the bounds of
-  // damageRect if necessary.
+  // Paints the layers from back to front. It assumes that the caller will
+  // clip to the bounds of damage rect if necessary.
   void Paint(GraphicsContext&,
-             const CullRect&,
              const GlobalPaintFlags = kGlobalPaintNormalPhase,
              PaintLayerFlags = kPaintLayerNoFlag);
   // Paint() assumes that the caller will clip to the bounds of the painting
@@ -48,10 +45,6 @@
                                  const PaintLayerPaintingInfo&,
                                  PaintLayerFlags);
 
-  void PaintOverlayOverflowControls(GraphicsContext&,
-                                    const CullRect&,
-                                    const GlobalPaintFlags);
-
   // Returns true if the painted output of this PaintLayer and its children is
   // invisible and therefore can't impact painted output.
   static bool PaintedOutputInvisible(const ComputedStyle&);
@@ -70,35 +63,22 @@
                             GraphicsContext&,
                             const PaintLayerPaintingInfo&,
                             PaintLayerFlags);
-  bool AtLeastOneFragmentIntersectsDamageRect(
-      PaintLayerFragments&,
-      const PaintLayerPaintingInfo&,
-      PaintLayerFlags,
-      const PhysicalOffset& offset_from_root);
   void PaintFragmentWithPhase(PaintPhase,
-                              const PaintLayerFragment&,
+                              const FragmentData&,
+                              const NGPhysicalBoxFragment*,
                               GraphicsContext&,
-                              const CullRect&,
                               const PaintLayerPaintingInfo&,
                               PaintLayerFlags);
-  void PaintBackgroundForFragmentsWithPhase(PaintPhase,
-                                            const PaintLayerFragments&,
-                                            GraphicsContext&,
-                                            const PaintLayerPaintingInfo&,
-                                            PaintLayerFlags);
-  void PaintForegroundForFragments(const PaintLayerFragments&,
-                                   GraphicsContext&,
-                                   const PaintLayerPaintingInfo&,
-                                   PaintLayerFlags);
-  void PaintForegroundForFragmentsWithPhase(PaintPhase,
-                                            const PaintLayerFragments&,
-                                            GraphicsContext&,
-                                            const PaintLayerPaintingInfo&,
-                                            PaintLayerFlags);
-  void PaintOverlayOverflowControlsForFragments(const PaintLayerFragments&,
-                                                GraphicsContext&,
-                                                const PaintLayerPaintingInfo&,
-                                                PaintLayerFlags);
+  void PaintWithPhase(PaintPhase,
+                      GraphicsContext&,
+                      const PaintLayerPaintingInfo&,
+                      PaintLayerFlags);
+  void PaintForegroundPhases(GraphicsContext&,
+                             const PaintLayerPaintingInfo&,
+                             PaintLayerFlags);
+  void PaintOverlayOverflowControls(GraphicsContext&,
+                                    const PaintLayerPaintingInfo&,
+                                    PaintLayerFlags);
 
   bool ShouldUseInfiniteCullRectInternal(GlobalPaintFlags,
                                          bool for_cull_rect_update);
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc b/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
index 201e74e3..f2eec31 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
@@ -23,9 +23,7 @@
 
  public:
   CullRect GetCullRect(const PaintLayer& layer) {
-    if (RuntimeEnabledFeatures::CullRectUpdateEnabled())
-      return layer.GetLayoutObject().FirstFragment().GetCullRect();
-    return layer.PreviousCullRect();
+    return layer.GetLayoutObject().FirstFragment().GetCullRect();
   }
 };
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painting_info.h b/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
index b0bb26c..e719b87 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
@@ -87,11 +87,9 @@
 
  public:
   PaintLayerPaintingInfo(PaintLayer* root_layer,
-                         const CullRect& cull_rect,
                          GlobalPaintFlags global_paint_flags,
                          const PhysicalOffset& sub_pixel_accumulation)
       : root_layer(root_layer),
-        cull_rect(cull_rect),
         sub_pixel_accumulation(sub_pixel_accumulation),
         global_paint_flags_(global_paint_flags) {}
 
@@ -99,7 +97,6 @@
 
   // TODO(jchaffraix): We should encapsulate all these fields.
   const PaintLayer* root_layer;
-  CullRect cull_rect;  // relative to rootLayer;
   PhysicalOffset sub_pixel_accumulation;
 
  private:
diff --git a/third_party/blink/renderer/core/paint/scoped_paint_state.cc b/third_party/blink/renderer/core/paint/scoped_paint_state.cc
index 8f438af..844fea2 100644
--- a/third_party/blink/renderer/core/paint/scoped_paint_state.cc
+++ b/third_party/blink/renderer/core/paint/scoped_paint_state.cc
@@ -55,47 +55,27 @@
                             fragment_to_paint_->ContentsProperties(), box,
                             input_paint_info_.DisplayItemTypeForClipping());
 
-  const auto* properties = fragment_to_paint_->PaintProperties();
-  const auto* scroll_translation =
-      properties ? properties->ScrollTranslation() : nullptr;
-
-  // See comments for ScrollTranslation in object_paint_properties.h
-  // for the reason of adding ScrollOrigin(). The paint offset will
-  // be used only for the scrolling contents that are not painted through
-  // descendant objects' Paint() method, e.g. inline boxes.
-  if (scroll_translation)
-    paint_offset_ += PhysicalOffset(box.ScrollOrigin());
-
-  if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
-    // We calculated cull rects for PaintLayers only.
-    if (!box.HasLayer())
-      return;
-    adjusted_paint_info_.emplace(input_paint_info_);
-    adjusted_paint_info_->SetCullRect(
-        fragment_to_paint_->GetContentsCullRect());
-    if (box.Layer()->PreviousPaintResult() == kFullyPainted) {
-      PhysicalRect contents_visual_rect =
-          PaintLayerPainter::ContentsVisualRect(*fragment_to_paint_, box);
-      if (!PhysicalRect(fragment_to_paint_->GetContentsCullRect().Rect())
-               .Contains(contents_visual_rect)) {
-        box.Layer()->SetPreviousPaintResult(kMayBeClippedByCullRect);
-      }
-    }
-    return;
+  if (const auto* properties = fragment_to_paint_->PaintProperties()) {
+    // See comments for ScrollTranslation in object_paint_properties.h
+    // for the reason of adding ScrollOrigin(). The paint offset will
+    // be used only for the scrolling contents that are not painted through
+    // descendant objects' Paint() method, e.g. inline boxes.
+    if (properties->ScrollTranslation())
+      paint_offset_ += PhysicalOffset(box.ScrollOrigin());
   }
 
-  // If a LayoutView is using infinite cull rect, we are painting with viewport
-  // clip disabled, so don't cull the scrolling contents. This is just for
-  // completeness because we always paint the whole scrolling background even
-  // with a smaller cull rect, and the scrolling document contents are under the
-  // layer of document element which will use infinite cull rect calculated in
-  // PaintLayerPainter::AdjustForPaintProperties().
-  if (IsA<LayoutView>(box) && input_paint_info_.GetCullRect().IsInfinite())
+  // We calculated cull rects for PaintLayers only.
+  if (!box.HasLayer())
     return;
-
-  if (scroll_translation) {
-    adjusted_paint_info_.emplace(input_paint_info_);
-    adjusted_paint_info_->TransformCullRect(*scroll_translation);
+  adjusted_paint_info_.emplace(input_paint_info_);
+  adjusted_paint_info_->SetCullRect(fragment_to_paint_->GetContentsCullRect());
+  if (box.Layer()->PreviousPaintResult() == kFullyPainted) {
+    PhysicalRect contents_visual_rect =
+        PaintLayerPainter::ContentsVisualRect(*fragment_to_paint_, box);
+    if (!PhysicalRect(fragment_to_paint_->GetContentsCullRect().Rect())
+             .Contains(contents_visual_rect)) {
+      box.Layer()->SetPreviousPaintResult(kMayBeClippedByCullRect);
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/core/paint/svg_foreign_object_painter.cc b/third_party/blink/renderer/core/paint/svg_foreign_object_painter.cc
index 0c2535c2..d16fe98 100644
--- a/third_party/blink/renderer/core/paint/svg_foreign_object_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_foreign_object_painter.cc
@@ -37,15 +37,9 @@
 
   // <foreignObject> is a replaced normal-flow stacking element.
   // See IsReplacedNormalFlowStacking in paint_layer_painter.cc.
-  PaintLayerPaintingInfo layer_painting_info(
-      layout_svg_foreign_object_.Layer(),
-      // Reset to an infinite cull rect, for simplicity. Otherwise
-      // an adjustment would be needed for ancestor scrolling, and any
-      // SVG transforms would have to be taken into account. Further,
-      // cull rects under transform are intentionally reset to infinity,
-      // to improve cache invalidation performance in the pre-paint tree
-      // walk (see https://http://crrev.com/482854).
-      CullRect::Infinite(), paint_info.GetGlobalPaintFlags(), PhysicalOffset());
+  PaintLayerPaintingInfo layer_painting_info(layout_svg_foreign_object_.Layer(),
+                                             paint_info.GetGlobalPaintFlags(),
+                                             PhysicalOffset());
   PaintLayerPainter(*layout_svg_foreign_object_.Layer())
       .Paint(paint_info.context, layer_painting_info, paint_info.PaintFlags());
 }
diff --git a/third_party/blink/renderer/core/style/computed_style_constants.h b/third_party/blink/renderer/core/style/computed_style_constants.h
index 9d324c12..5bce0b2 100644
--- a/third_party/blink/renderer/core/style/computed_style_constants.h
+++ b/third_party/blink/renderer/core/style/computed_style_constants.h
@@ -30,6 +30,7 @@
 
 #include <cstddef>
 #include <cstdint>
+#include "base/check_op.h"
 #include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
 
 namespace blink {
@@ -149,13 +150,15 @@
 enum class EFillBox : unsigned { kBorder, kPadding, kContent, kText };
 
 inline EFillBox EnclosingFillBox(EFillBox box_a, EFillBox box_b) {
-  if (box_a == EFillBox::kBorder || box_b == EFillBox::kBorder)
+  // background-clip:text is clipped to the border box.
+  if (box_a == EFillBox::kBorder || box_a == EFillBox::kText ||
+      box_b == EFillBox::kBorder || box_b == EFillBox::kText)
     return EFillBox::kBorder;
   if (box_a == EFillBox::kPadding || box_b == EFillBox::kPadding)
     return EFillBox::kPadding;
-  if (box_a == EFillBox::kContent || box_b == EFillBox::kContent)
-    return EFillBox::kContent;
-  return EFillBox::kText;
+  DCHECK_EQ(box_a, EFillBox::kContent);
+  DCHECK_EQ(box_b, EFillBox::kContent);
+  return EFillBox::kContent;
 }
 
 enum class EFillRepeat : unsigned {
diff --git a/third_party/blink/renderer/modules/peerconnection/mock_rtc_peer_connection_handler_platform.cc b/third_party/blink/renderer/modules/peerconnection/mock_rtc_peer_connection_handler_platform.cc
index f54bca0..949b6026 100644
--- a/third_party/blink/renderer/modules/peerconnection/mock_rtc_peer_connection_handler_platform.cc
+++ b/third_party/blink/renderer/modules/peerconnection/mock_rtc_peer_connection_handler_platform.cc
@@ -24,6 +24,12 @@
 
 namespace {
 
+webrtc::PeerConnectionInterface::RTCConfiguration DefaultConfiguration() {
+  webrtc::PeerConnectionInterface::RTCConfiguration config;
+  config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
+  return config;
+}
+
 // Having a refcounted helper class allows multiple DummyRTCRtpSenderPlatform to
 // share the same internal states.
 class DummyRtpSenderInternal
@@ -302,7 +308,8 @@
 
 const webrtc::PeerConnectionInterface::RTCConfiguration&
 MockRTCPeerConnectionHandlerPlatform::GetConfiguration() const {
-  static const webrtc::PeerConnectionInterface::RTCConfiguration configuration;
+  static const webrtc::PeerConnectionInterface::RTCConfiguration configuration =
+      DefaultConfiguration();
   return configuration;
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.cc b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.cc
index d573266f..749acae 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.cc
@@ -64,7 +64,8 @@
     const PaintChunkSubset& paint_chunks,
     const gfx::Vector2dF& layer_offset,
     const gfx::Size& layer_bounds,
-    const PropertyTreeState& layer_state) {
+    const PropertyTreeState& layer_state,
+    bool draws_content) {
   if (paint_chunks.begin()->is_cacheable)
     id_.emplace(paint_chunks.begin()->id);
   else
@@ -127,7 +128,7 @@
   cc_picture_layer_->SetBounds(layer_bounds);
   cc_picture_layer_->SetHitTestable(true);
   cc_picture_layer_->SetIsDrawable(
-      (!layer_bounds.IsEmpty() && cc_display_item_list_->TotalOpCount()) ||
+      (!layer_bounds.IsEmpty() && draws_content) ||
       // Backdrop effects and filters require the layer to be drawable even if
       // the layer draws nothing.
       layer_state.Effect().HasBackdropEffect() ||
diff --git a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
index e91eef7..3d33244 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h
@@ -55,7 +55,8 @@
       const PaintChunkSubset&,
       const gfx::Vector2dF& layer_offset,
       const gfx::Size& layer_bounds,
-      const PropertyTreeState&);
+      const PropertyTreeState&,
+      bool draws_content);
 
   RasterInvalidator& GetRasterInvalidator() { return raster_invalidator_; }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index b4a4de31..d0c04e85 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -266,7 +266,7 @@
   gfx::Size layer_bounds = pending_layer.LayerBounds();
   auto cc_layer = content_layer_client->UpdateCcPictureLayer(
       pending_layer.Chunks(), layer_offset, layer_bounds,
-      pending_layer.GetPropertyTreeState());
+      pending_layer.GetPropertyTreeState(), pending_layer.DrawsContent());
 
   new_content_layer_clients.push_back(std::move(content_layer_client));
 
@@ -311,6 +311,7 @@
 
   if (repainted.is_moved_from_cached_subsequence) {
     DCHECK_EQ(previous.bounds, repainted.bounds);
+    DCHECK_EQ(previous.DrawsContent(), repainted.DrawsContent());
     DCHECK_EQ(previous.rect_known_to_be_opaque,
               repainted.rect_known_to_be_opaque);
     DCHECK_EQ(previous.text_known_to_be_on_opaque_background,
@@ -361,6 +362,12 @@
   if (previous.has_text != repainted.has_text)
     return true;
 
+  // |PaintChunk::DrawsContent()| affects whether a layer draws content which
+  // affects whether mask layers are created (see:
+  // |SwitchToEffectNodeWithSynthesizedClip|).
+  if (previous.DrawsContent() != repainted.DrawsContent())
+    return true;
+
   // Debugging for https://crbug.com/1237389 and https://crbug.com/1230104.
   // Before returning that a full update is not needed, check that the
   // properties are changed, which would indicate a missing call to
@@ -472,10 +479,10 @@
       if (num_previous_siblings > 2)
         return false;
       if (num_previous_siblings == 2 &&
-          pending_layers_[first_layer_in_parent_group_index].MayDrawContent())
+          pending_layers_[first_layer_in_parent_group_index].DrawsContent())
         return false;
       const auto& previous_sibling = pending_layers_[layer_index - 1];
-      if (previous_sibling.MayDrawContent() &&
+      if (previous_sibling.DrawsContent() &&
           !previous_sibling.CanMerge(layer, *upcast_state, prefers_lcd_text_))
         return false;
     }
@@ -864,12 +871,12 @@
       static_cast<wtf_size_t>(new_end - synthesized_clip_cache_.begin()));
 
   // This should be done before
-  // property_tree_manager.AddConditionalRenderSurfaceReasons() for which to
+  // property_tree_manager.UpdateConditionalRenderSurfaceReasons() for which to
   // get property tree node ids from the layers.
   host->property_trees()->sequence_number = g_s_property_tree_sequence_number;
 
   auto layers = layer_list_builder.Finalize();
-  property_tree_manager.AddConditionalRenderSurfaceReasons(layers);
+  property_tree_manager.UpdateConditionalRenderSurfaceReasons(layers);
   root_layer_->SetChildLayerList(std::move(layers));
 
   // Update the host's active registered elements from the new property tree.
@@ -928,7 +935,8 @@
   } else {
     content_layer_client.UpdateCcPictureLayer(
         pending_layer.Chunks(), pending_layer.LayerOffset(),
-        pending_layer.LayerBounds(), pending_layer.GetPropertyTreeState());
+        pending_layer.LayerBounds(), pending_layer.GetPropertyTreeState(),
+        pending_layer.DrawsContent());
   }
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
index 52052b0d..0ced152 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
@@ -81,6 +81,7 @@
     : bounds_(first_chunk.bounds),
       rect_known_to_be_opaque_(first_chunk.rect_known_to_be_opaque),
       has_text_(first_chunk.has_text),
+      draws_content_(first_chunk.DrawsContent()),
       text_known_to_be_on_opaque_background_(
           first_chunk.text_known_to_be_on_opaque_background),
       chunks_(&chunks.GetPaintArtifact(), first_chunk_index_in_paint_artifact),
@@ -198,10 +199,6 @@
   return *chunks_.begin().DisplayItems().begin();
 }
 
-bool PendingLayer::MayDrawContent() const {
-  return Chunks().size() > 1 || FirstPaintChunk().size() > 0;
-}
-
 // We will only allow merging if
 // merged_area - (home_area + guest_area) <= kMergeSparsityAreaTolerance
 static constexpr float kMergeSparsityAreaTolerance = 10000;
@@ -235,6 +232,7 @@
       rect_known_to_be_opaque_.Contains(bounds_)) {
     if (!dry_run) {
       chunks_.Merge(guest.Chunks());
+      draws_content_ |= guest.draws_content_;
       text_known_to_be_on_opaque_background_ = true;
       has_text_ |= guest.has_text_;
       change_of_decomposited_transforms_ =
@@ -289,6 +287,7 @@
     chunks_.Merge(guest.Chunks());
     bounds_ = merged_bounds;
     property_tree_state_ = *merged_state;
+    draws_content_ |= guest.draws_content_;
     rect_known_to_be_opaque_ = merged_rect_known_to_be_opaque;
     text_known_to_be_on_opaque_background_ =
         merged_text_known_to_be_on_opaque_background;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.h b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.h
index b3fe83774..c046bef 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.h
@@ -93,7 +93,7 @@
 
   std::unique_ptr<JSONObject> ToJSON() const;
 
-  bool MayDrawContent() const;
+  bool DrawsContent() const { return draws_content_; }
 
   bool RequiresOwnLayer() const {
     return compositing_type_ != kOverlap && compositing_type_ != kOther;
@@ -124,6 +124,7 @@
   gfx::RectF bounds_;
   gfx::RectF rect_known_to_be_opaque_;
   bool has_text_ = false;
+  bool draws_content_ = false;
   bool text_known_to_be_on_opaque_background_ = false;
   PaintChunkSubset chunks_;
   PropertyTreeState property_tree_state_;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 083d98ee..e816f5d 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -900,6 +900,16 @@
   return true;
 }
 
+// A reason is conditional if it can be omitted if it controls less than two
+// composited layers or render surfaces. We set the reason on an effect node
+// when updating the cc effect property tree, and remove unnecessary ones in
+// UpdateConditionalRenderSurfaceReasons() after layerirzation.
+static bool IsConditionalRenderSurfaceReason(cc::RenderSurfaceReason reason) {
+  return reason == cc::RenderSurfaceReason::kBlendModeDstIn ||
+         reason == cc::RenderSurfaceReason::kOpacity ||
+         reason == cc::RenderSurfaceReason::kOpacityAnimation;
+}
+
 int PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded(
     const ClipPaintPropertyNode& target_clip,
     const EffectPaintPropertyNode* next_effect) {
@@ -1014,8 +1024,11 @@
           for (auto effect_it = effect_stack_.rbegin();
                effect_it != effect_stack_.rend(); ++effect_it) {
             auto& effect_node = *GetEffectTree().Node(effect_it->effect_id);
-            if (effect_node.HasRenderSurface())
+            if (effect_node.HasRenderSurface() &&
+                !IsConditionalRenderSurfaceReason(
+                    effect_node.render_surface_reason)) {
               break;
+            }
             ForceRenderSurfaceIfSyntheticRoundedCornerClip(*effect_it);
           }
         }
@@ -1123,12 +1136,6 @@
     }
   } else {
     next_effect.SetCcNodeId(new_sequence_number_, real_effect_node_id);
-    if (cc_effect_id_to_blink_effect_node_.size() <=
-        static_cast<wtf_size_t>(real_effect_node_id)) {
-      cc_effect_id_to_blink_effect_node_.resize(real_effect_node_id + 1);
-    }
-    DCHECK(!cc_effect_id_to_blink_effect_node_[real_effect_node_id]);
-    cc_effect_id_to_blink_effect_node_[real_effect_node_id] = &next_effect;
   }
 
   CompositorElementId compositor_element_id =
@@ -1145,6 +1152,21 @@
                         *output_clip, transform);
 }
 
+// See IsConditionalRenderSurfaceReason() for the definition of conditional
+// render surface.
+static cc::RenderSurfaceReason ConditionalRenderSurfaceReasonForEffect(
+    const EffectPaintPropertyNode& effect) {
+  if (effect.BlendMode() == SkBlendMode::kDstIn)
+    return cc::RenderSurfaceReason::kBlendModeDstIn;
+  if (effect.Opacity() != 1.f ||
+      effect.RequiresCompositingForWillChangeOpacity()) {
+    return cc::RenderSurfaceReason::kOpacity;
+  }
+  if (effect.HasActiveOpacityAnimation())
+    return cc::RenderSurfaceReason::kOpacityAnimation;
+  return cc::RenderSurfaceReason::kNone;
+}
+
 static cc::RenderSurfaceReason RenderSurfaceReasonForEffect(
     const EffectPaintPropertyNode& effect) {
   if (!effect.Filter().IsEmpty() ||
@@ -1160,9 +1182,7 @@
   if (effect.HasActiveBackdropFilterAnimation())
     return cc::RenderSurfaceReason::kBackdropFilterAnimation;
   if (effect.BlendMode() != SkBlendMode::kSrcOver &&
-      // For optimization, we will set render surface reason for DstIn later in
-      // PaintArtifactCompositor::UpdateRenderSurfaceForEffects() if it controls
-      // more than one layer.
+      // The render surface for kDstIn is conditional. See above functions.
       effect.BlendMode() != SkBlendMode::kDstIn) {
     return cc::RenderSurfaceReason::kBlendMode;
   }
@@ -1175,7 +1195,10 @@
   if (effect.FlattensAtLeafOf3DScene())
     return cc::RenderSurfaceReason::k3dTransformFlattening;
 
-  return cc::RenderSurfaceReason::kNone;
+  auto conditional_reason = ConditionalRenderSurfaceReasonForEffect(effect);
+  DCHECK(conditional_reason == cc::RenderSurfaceReason::kNone ||
+         IsConditionalRenderSurfaceReason(conditional_reason));
+  return conditional_reason;
 }
 
 void PropertyTreeManager::PopulateCcEffectNode(
@@ -1207,44 +1230,7 @@
   effect_node.shared_element_resource_id = effect.SharedElementResourceId();
 }
 
-cc::RenderSurfaceReason
-PropertyTreeManager::ConditionalRenderSurfaceReasonForEffect(
-    const cc::EffectNode& effect) const {
-  if (effect.HasRenderSurface())
-    return cc::RenderSurfaceReason::kNone;
-  if (effect.blend_mode != SkBlendMode::kSrcOver)
-    return cc::RenderSurfaceReason::kBlendModeDstIn;
-  if (effect.opacity != 1.f)
-    return cc::RenderSurfaceReason::kOpacity;
-  if (static_cast<wtf_size_t>(effect.id) <
-      cc_effect_id_to_blink_effect_node_.size()) {
-    if (const auto* blink_effect_node =
-            cc_effect_id_to_blink_effect_node_[effect.id]) {
-      if (blink_effect_node->RequiresCompositingForWillChangeOpacity())
-        return cc::RenderSurfaceReason::kOpacity;
-      if (blink_effect_node->HasActiveOpacityAnimation())
-        return cc::RenderSurfaceReason::kOpacityAnimation;
-    }
-  }
-  // Applying a rounded corner clip to more than one layer descendant
-  // with highest quality requires a render surface, due to the possibility
-  // of antialiasing issues on the rounded corner edges.
-  // is_fast_rounded_corner means to intentionally prefer faster compositing
-  // and less memory over highest quality.
-  if (effect.mask_filter_info.HasRoundedCorners() &&
-      !effect.is_fast_rounded_corner)
-    return cc::RenderSurfaceReason::kRoundedCorner;
-  return cc::RenderSurfaceReason::kNone;
-}
-
-// Every effect is supposed to have render surface enabled for grouping, but we
-// can omit one if the effect is opacity or blend-mode only, render surface is
-// not forced, and the effect has only one layer or render surface. This is both
-// for optimization and not introducing sub-pixel differences in web tests.
-// TODO(crbug.com/504464): There is ongoing work in cc to delay render surface
-// decision until later phase of the pipeline. Remove premature optimization
-// here once the work is ready.
-void PropertyTreeManager::AddConditionalRenderSurfaceReasons(
+void PropertyTreeManager::UpdateConditionalRenderSurfaceReasons(
     const cc::LayerList& layers) {
   auto& effect_tree = GetEffectTree();
   // This vector is indexed by effect node id. The value is the number of
@@ -1262,13 +1248,11 @@
   for (int id = static_cast<int>(effect_tree.size() - 1);
        id > cc::EffectTree::kSecondaryRootNodeId; id--) {
     auto* effect = effect_tree.Node(id);
-    if (effect_layer_counts[id] > 1) {
-      auto reason = ConditionalRenderSurfaceReasonForEffect(*effect);
-      if (reason != cc::RenderSurfaceReason::kNone) {
-        // The render surface candidate needs a render surface because it
-        // controls more than 1 layer.
-        effect->render_surface_reason = reason;
-      }
+    if (effect_layer_counts[id] < 2 &&
+        IsConditionalRenderSurfaceReason(effect->render_surface_reason)) {
+      // The conditional render surface can be omitted because it controls less
+      // than two layers or render surfaces.
+      effect->render_surface_reason = cc::RenderSurfaceReason::kNone;
     }
 
     // We should not have visited the parent.
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
index ae4dce96..30f7f28e 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
@@ -161,10 +161,17 @@
   // |cc_node_id|.
   void SetCcScrollNodeIsComposited(int cc_node_id);
 
-  // Sets additional reasons on |cc::EffectNode::render_surface_reason| for
-  // all effect nodes in |GetEffectTree| based on
-  // |ConditionalRenderSurfaceReasonForEffect|.
-  void AddConditionalRenderSurfaceReasons(const cc::LayerList& layers);
+  // Updates conditional render surface reasons for all effect nodes in
+  // |GetEffectTree|. Every effect is supposed to have render surface enabled
+  // for grouping, but we can omit a conditional render surface if it controls
+  // less than two composited layers or render surfaces.
+  // See ConditionalRenderSurfaceReasonForEffect() in property_tree_manager.cc
+  // for which reasons are conditional. This is both for optimization and not
+  // introducing sub-pixel differences in web tests.
+  // TODO(crbug.com/504464): There is ongoing work in cc to delay render surface
+  // decision until later phase of the pipeline. Remove premature optimization
+  // here once the work is ready.
+  void UpdateConditionalRenderSurfaceReasons(const cc::LayerList& layers);
 
  private:
   void SetupRootTransformNode();
@@ -294,11 +301,6 @@
       const ScrollPaintPropertyNode&,
       const cc::TransformNode& scroll_offset_translation);
 
-  // Render surface reasons added by |UpdateConditionalRenderSurfaces| that are
-  // conditional on having more than one affected layer/surface.
-  cc::RenderSurfaceReason ConditionalRenderSurfaceReasonForEffect(
-      const cc::EffectNode&) const;
-
   PropertyTreeManagerClient& client_;
 
   // Property trees which should be updated by the manager.
@@ -327,8 +329,6 @@
   // A set of synthetic clips masks which will be applied if a layer under them
   // is encountered which draws content (and thus necessitates the mask).
   HashSet<int> pending_synthetic_mask_layers_;
-
-  Vector<const EffectPaintPropertyNode*> cc_effect_id_to_blink_effect_node_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/display_item.h b/third_party/blink/renderer/platform/graphics/paint/display_item.h
index cca0e2d7..c3c9690 100644
--- a/third_party/blink/renderer/platform/graphics/paint/display_item.h
+++ b/third_party/blink/renderer/platform/graphics/paint/display_item.h
@@ -296,7 +296,7 @@
               const gfx::Rect& visual_rect,
               RasterEffectOutset raster_effect_outset,
               PaintInvalidationReason paint_invalidation_reason,
-              bool draws_content = false)
+              bool draws_content)
       : client_id_(client_id),
         visual_rect_(visual_rect),
         fragment_(0),
diff --git a/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
index b6f3350d..194ea0c 100644
--- a/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
@@ -25,7 +25,8 @@
                   type,
                   gfx::Rect(origin, layer->bounds()),
                   outset,
-                  paint_invalidation_reason),
+                  paint_invalidation_reason,
+                  /*draws_content*/ true),
       layer_(std::move(layer)) {
   DCHECK(IsForeignLayerType(type));
 }
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunk.h b/third_party/blink/renderer/platform/graphics/paint/paint_chunk.h
index a7ea8f3a..4de8a024 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunk.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunk.h
@@ -112,6 +112,10 @@
     return *layer_selection_data;
   }
 
+  bool DrawsContent() const {
+    return !effectively_invisible && !drawable_bounds.IsEmpty();
+  }
+
   size_t MemoryUsageInBytes() const;
 
   // The no-argument version is for operator<< which is used in DCHECK and unit
diff --git a/third_party/blink/renderer/platform/graphics/paint/raster_invalidator_test.cc b/third_party/blink/renderer/platform/graphics/paint/raster_invalidator_test.cc
index 811896e..c5a7e57 100644
--- a/third_party/blink/renderer/platform/graphics/paint/raster_invalidator_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/raster_invalidator_test.cc
@@ -182,6 +182,7 @@
                                   .Chunk(2)
                                   .Chunk(1)
                                   .Bounds(gfx::Rect(11, 22, 33, 44))
+                                  .DrawableBounds(gfx::Rect(11, 22, 33, 44))
                                   .Build());
   invalidator_.Generate(base::DoNothing(), new_chunks, kDefaultLayerOffset,
                         kDefaultLayerBounds, DefaultPropertyTreeState());
@@ -213,6 +214,7 @@
                                   .IsMovedFromCachedSubsequence()
                                   .Chunk(1)
                                   .Bounds(gfx::Rect(11, 22, 33, 44))
+                                  .DrawableBounds(gfx::Rect(11, 22, 33, 44))
                                   .Chunk(2)
                                   .Build());
   invalidator_.Generate(base::DoNothing(), new_chunks, kDefaultLayerOffset,
@@ -399,14 +401,17 @@
   auto clip1 = CreateClip(*clip0, t0(), clip_rect);
 
   PropertyTreeState layer_state = PropertyTreeState::Root();
-  PaintChunkSubset chunks(TestPaintArtifact()
-                              .Chunk(0)
-                              .Properties(t0(), *clip0, e0())
-                              .Bounds(gfx::ToEnclosingRect(clip_rect.Rect()))
-                              .Chunk(1)
-                              .Properties(t0(), *clip1, e0())
-                              .Bounds(gfx::ToEnclosingRect(clip_rect.Rect()))
-                              .Build());
+  PaintChunkSubset chunks(
+      TestPaintArtifact()
+          .Chunk(0)
+          .Properties(t0(), *clip0, e0())
+          .Bounds(gfx::ToEnclosingRect(clip_rect.Rect()))
+          .DrawableBounds(gfx::ToEnclosingRect(clip_rect.Rect()))
+          .Chunk(1)
+          .Properties(t0(), *clip1, e0())
+          .Bounds(gfx::ToEnclosingRect(clip_rect.Rect()))
+          .DrawableBounds(gfx::ToEnclosingRect(clip_rect.Rect()))
+          .Build());
 
   invalidator_.Generate(base::DoNothing(), chunks, kDefaultLayerOffset,
                         kDefaultLayerBounds, layer_state);
@@ -462,12 +467,14 @@
   auto c1 = CreateClip(c0(), t0(), clip_rect);
 
   PropertyTreeState layer_state = PropertyTreeState::Root();
-  PaintChunkSubset chunks(TestPaintArtifact()
-                              .Chunk(0)
-                              .Properties(t0(), *c1, e0())
-                              .Bounds(gfx::ToEnclosingRect(clip_rect.Rect()))
-                              .IsMovedFromCachedSubsequence()
-                              .Build());
+  PaintChunkSubset chunks(
+      TestPaintArtifact()
+          .Chunk(0)
+          .Properties(t0(), *c1, e0())
+          .Bounds(gfx::ToEnclosingRect(clip_rect.Rect()))
+          .DrawableBounds(gfx::ToEnclosingRect(clip_rect.Rect()))
+          .IsMovedFromCachedSubsequence()
+          .Build());
 
   invalidator_.Generate(base::DoNothing(), chunks, kDefaultLayerOffset,
                         kDefaultLayerBounds, layer_state);
@@ -504,6 +511,7 @@
           .Chunk(0)
           .Properties(t0(), *clip, e0())
           .Bounds(gfx::ToEnclosingRect(clip_rect.Rect()))
+          .DrawableBounds(gfx::ToEnclosingRect(clip_rect.Rect()))
           .SetRasterEffectOutset(RasterEffectOutset::kWholePixel)
           .Build());
 
@@ -725,6 +733,7 @@
                               .Chunk(0)
                               .Properties(*chunk_transform, c0(), e0())
                               .Bounds(chunk_bounds)
+                              .DrawableBounds(chunk_bounds)
                               .Build());
 
   invalidator_.Generate(base::DoNothing(), chunks, kDefaultLayerOffset,
diff --git a/third_party/blink/renderer/platform/testing/test_paint_artifact.cc b/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
index 19d39eb..0a7774c 100644
--- a/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
+++ b/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
@@ -33,7 +33,9 @@
   // invalidation rects of chunks. The actual values don't matter. If the chunk
   // has display items, we will recalculate the bounds from the display items
   // when constructing the PaintArtifact.
-  Bounds(gfx::Rect(id * 110, id * 220, id * 220 + 200, id * 110 + 200));
+  gfx::Rect bounds(id * 110, id * 220, id * 220 + 200, id * 110 + 200);
+  Bounds(bounds);
+  DrawableBounds(bounds);
   return *this;
 }
 
@@ -153,7 +155,6 @@
 TestPaintArtifact& TestPaintArtifact::Bounds(const gfx::Rect& bounds) {
   auto& chunk = paint_artifact_->PaintChunks().back();
   chunk.bounds = bounds;
-  chunk.drawable_bounds = bounds;
   return *this;
 }
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 5f07f6a..07768bb 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -7981,3 +7981,7 @@
 crbug.com/1229095 [ Linux ] virtual/gpu/fast/canvas/canvas-getImageData.html [ Failure Pass ]
 crbug.com/1280736 [ Win10.20h2 ] http/tests/inspector-protocol/network/blocked-setcookie-same-site-strict.js [ Pass Timeout ]
 crbug.com/1280736 [ Win10.20h2 ] http/tests/inspector-protocol/network/blocked-setcookie-same-site-lax.js [ Pass Timeout ]
+
+# Sheriff 2022-01-03
+crbug.com/1283865 [ Mac11 ] external/wpt/webmessaging/without-ports/020.html [ Failure Pass ]
+
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 6c99e4c8..54a8299 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
@@ -83743,7 +83743,7 @@
       ]
      ],
      "predefined-016.html": [
-      "c8a666164a92a8e5d192da971bf65450fa86b0bc",
+      "f80e60537015c34235bc7b707a65c1acc14304d7",
       [
        null,
        [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/predefined-016.html b/third_party/blink/web_tests/external/wpt/css/css-color/predefined-016.html
index c8a6661..f80e6053 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-color/predefined-016.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/predefined-016.html
@@ -4,11 +4,11 @@
 <link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
 <link rel="help" href="https://drafts.csswg.org/css-color-4/#predefined">
 <link rel="match" href="greensquare-090-ref.html">
-<meta name="assert" content="Color function with explicit XYZ value matches sRGB #009900">
+<meta name="assert" content="Color function with explicit xyz-d50 value matches sRGB #009900">
 <style>
     .test { background-color: red; width: 12em; height: 6em; margin-top:0}
     .ref { background-color: #009900; width: 12em; height: 6em; margin-bottom: 0}
-    .test {background-color: color(xyz 0.12266 0.22836 0.03093)}
+    .test {background-color: color(xyz-d50 0.12266 0.22836 0.03093)}
 </style>
 <body>
     <p>Test passes if you see a green square, and no red.</p>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.tentative-expected.txt
index 5d5c990f..c68cca2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.tentative-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.tentative-expected.txt
@@ -5,7 +5,7 @@
 PASS Match container in outer tree for :host
 FAIL Match container in ::part selector's originating element tree assert_equals: expected "rgb(0, 128, 0)" but got "rgb(0, 0, 0)"
 FAIL Match container for ::before in ::slotted selector's originating element tree assert_equals: expected "rgb(0, 128, 0)" but got "rgb(0, 0, 0)"
-FAIL Match container in outer tree for :host::before assert_equals: expected "rgb(0, 128, 0)" but got "rgb(0, 0, 0)"
+PASS Match container in outer tree for :host::before
 FAIL Match container for ::before in ::part selector's originating element tree assert_equals: expected "rgb(0, 128, 0)" but got "rgb(0, 0, 0)"
 FAIL Match container for ::part selector's originating element tree for exportparts assert_equals: expected "rgb(0, 128, 0)" but got "rgb(0, 0, 0)"
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.tentative.html
index aee25f86..24d52da 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.tentative.html
@@ -128,7 +128,7 @@
     </template>
     <style>
       #t6 {
-        width: 200px;
+        width: 400px;
         container-type: inline-size;
       }
     </style>
@@ -156,6 +156,14 @@
 </div>
 
 <div id="inclusive-ancestor-part-before">
+  <style>
+    @container size(width = 400px) {
+      #inclusive-ancestor-part-before > div::part(part)::before {
+        content: "X";
+        color: green;
+      }
+    }
+  </style>
   <div>
     <template shadowroot="open">
       <style>
@@ -168,21 +176,13 @@
         <span id="t8" part="part"></span>
       </div>
     </template>
-    <style>
-      @container size(width = 400px) {
-        #inclusive-ancestor-part > div::part(part)::before {
-          content: "X";
-          color: green;
-        }
-      }
-    </style>
   </div>
 </div>
 
 <div id="inclusive-ancestor-inner-part">
   <style>
     @container size(width = 400px) {
-      #inclusive-ancestor-part > div::part(inner-part) { color: green; }
+      #inclusive-ancestor-inner-part > div::part(inner-part) { color: green; }
     }
   </style>
   <div>
@@ -242,17 +242,17 @@
 
   test(() => {
     const t6 = document.querySelector("#t6");
-    assert_equals(getComputedStyle(t6).color, green);
+    assert_equals(getComputedStyle(t6, "::before").color, green);
   }, "Match container for ::before in ::slotted selector's originating element tree");
 
   test(() => {
     const t7 = document.querySelector("#t7");
-    assert_equals(getComputedStyle(t7).color, green);
+    assert_equals(getComputedStyle(t7, "::before").color, green);
   }, "Match container in outer tree for :host::before");
 
   test(() => {
     const t8 = document.querySelector("#inclusive-ancestor-part-before > div").shadowRoot.querySelector("#t8");
-    assert_equals(getComputedStyle(t8).color, green);
+    assert_equals(getComputedStyle(t8, "::before").color, green);
   }, "Match container for ::before in ::part selector's originating element tree");
 
   test(() => {
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/preflight.py b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/preflight.py
index 4181210..8accc68 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/preflight.py
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/preflight.py
@@ -23,6 +23,10 @@
 #   - unspecified: this endpoint responds with no CORS headers to non-preflight
 #     requests. This should fail CORS-enabled requests, but be sufficient for
 #     no-CORS requests.
+# - mime-type: Optional. If set, the final response's `Content-Type` header is
+#   set to this parameter's value.
+# - body: Optional. If set, the final response's body is set to this parameter's
+#   value.
 #
 
 _ACAO = ("Access-Control-Allow-Origin", "*")
@@ -58,12 +62,18 @@
 def _handle_final_request(request, response):
   uuid = _get_uuid(request)
   if uuid is not None and request.server.stash.take(uuid) is None:
-    raise Exception("no matching preflight request for {}".format(uuid))
+    return (405, [], "no preflight received for {}".format(uuid))
 
   mode = request.GET.get(b"final-headers")
   headers = _get_response_headers(request.method, mode)
 
-  return (headers, "success")
+  mime_type = request.GET.get(b"mime-type")
+  if mime_type is not None:
+    headers.append(("Content-Type", mime_type),)
+
+  body = request.GET.get(b"body") or "success"
+
+  return (headers, body)
 
 def main(request, response):
   try:
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/service-worker-fetcher.html b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/service-worker-fetcher.html
new file mode 100644
index 0000000..d9c0df30
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/service-worker-fetcher.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>ServiceWorker Fetcher</title>
+<script>
+  window.addEventListener("message", async (evt) => {
+    let { url, uuid } = evt.data;
+
+    try {
+      const registration = await navigator.serviceWorker.register(url, {
+        scope: `./${uuid}`,
+      });
+      const unregistered = await registration.unregister();
+      parent.postMessage({ loaded: true, unregistered }, "*");
+    } catch (e) {
+      parent.postMessage({ loaded: false, error: e.name }, "*");
+    }
+  });
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/shared-worker-fetcher.html b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/shared-worker-fetcher.html
new file mode 100644
index 0000000..8bea2497
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/shared-worker-fetcher.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SharedWorker Fetcher</title>
+<script>
+  window.addEventListener("message", function (evt) {
+    let { url } = evt.data;
+
+    const worker = new SharedWorker(url);
+
+    worker.onerror = (evt) => {
+      parent.postMessage({ loaded: false }, "*");
+    };
+
+    worker.port.addEventListener("message", (evt) => {
+      const message = {
+        loaded: true,
+        message: evt.data,
+      };
+      parent.postMessage(message, "*");
+    });
+    worker.port.start();
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/support.sub.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/support.sub.js
index 63c5d87..1dba6d5 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/support.sub.js
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/support.sub.js
@@ -337,3 +337,48 @@
 
   assert_equals(await reply, expected);
 }
+
+const WorkerScriptTestResult = {
+  FAILURE: { loaded: false },
+  SUCCESS: { loaded: true, message: "success" },
+};
+
+async function workerScriptTest(t, { source, target, expected }) {
+  const sourceUrl =
+      resolveUrl("resources/worker-fetcher.html", sourceResolveOptions(source));
+
+  const targetUrl =
+      resolveUrl("resources/preflight.py", targetResolveOptions(target));
+  targetUrl.searchParams.append("body", "postMessage('success')")
+  targetUrl.searchParams.append("mime-type", "application/javascript")
+
+  const iframe = await appendIframe(t, document, sourceUrl);
+  const reply = futureMessage();
+
+  iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+  const { loaded, message } = await reply;
+
+  assert_equals(loaded, expected.loaded, "response loaded");
+  assert_equals(message, expected.message, "response message");
+}
+
+async function sharedWorkerScriptTest(t, { source, target, expected }) {
+  const sourceUrl = resolveUrl("resources/shared-worker-fetcher.html",
+                               sourceResolveOptions(source));
+
+  const targetUrl =
+      resolveUrl("resources/preflight.py", targetResolveOptions(target));
+  targetUrl.searchParams.append(
+      "body", "onconnect = (e) => e.ports[0].postMessage('success')")
+
+  const iframe = await appendIframe(t, document, sourceUrl);
+  const reply = futureMessage();
+
+  iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+  const { loaded, message } = await reply;
+
+  assert_equals(loaded, expected.loaded, "response loaded");
+  assert_equals(message, expected.message, "response message");
+}
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/worker-fetcher.html b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/worker-fetcher.html
new file mode 100644
index 0000000..9031e6a5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/worker-fetcher.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Worker Fetcher</title>
+<script>
+  window.addEventListener("message", function (evt) {
+    let { url } = evt.data;
+
+    const worker = new Worker(url);
+
+    worker.addEventListener("message", (evt) => {
+      const message = {
+        loaded: true,
+        message: evt.data,
+      };
+      parent.postMessage(message, "*");
+    });
+
+    worker.addEventListener("error", (evt) => {
+      parent.postMessage({ loaded: false }, "*");
+    });
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker.https.window-expected.txt
new file mode 100644
index 0000000..3339c8a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker.https.window-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+FAIL treat-as-public to local: failure. assert_equals: error expected (string) "TypeError" but got (undefined) undefined
+FAIL treat-as-public to private: failure. assert_equals: error expected (string) "TypeError" but got (undefined) undefined
+PASS public to public: success.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker.https.window.js
new file mode 100644
index 0000000..13cd04b6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker.https.window.js
@@ -0,0 +1,68 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that initial `ServiceWorker` script fetches are subject to
+// Private Network Access checks, just like a regular `fetch()`.
+// See also: worker.https.window.js
+
+const TestResult = {
+  SUCCESS: {
+    loaded: true,
+    unregistered: true,
+  },
+  FAILURE: {
+    loaded: false,
+    error: "TypeError",
+  },
+};
+
+async function serviceWorkerScriptTest(t, { source, target, expected }) {
+  const sourceUrl = resolveUrl("resources/service-worker-fetcher.html",
+                               sourceResolveOptions(source));
+
+  const targetUrl =
+      resolveUrl("resources/preflight.py", targetResolveOptions(target));
+  targetUrl.searchParams.append("body", "undefined");
+  targetUrl.searchParams.append("mime-type", "application/javascript");
+
+  const iframe = await appendIframe(t, document, sourceUrl);
+  const reply = futureMessage();
+
+  const message = {
+    url: targetUrl.href,
+    uuid: token(),
+  };
+  iframe.contentWindow.postMessage(message, "*");
+
+  const { error, loaded, unregistered } = await reply;
+
+  assert_equals(error, expected.error, "error");
+  assert_equals(loaded, expected.loaded, "response loaded");
+  assert_equals(unregistered, expected.unregistered, "worker unregistered");
+}
+
+promise_test(t => serviceWorkerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_LOCAL,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTPS_LOCAL },
+  expected: TestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => serviceWorkerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_PRIVATE,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTPS_PRIVATE },
+  expected: TestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => serviceWorkerScriptTest(t, {
+  source: { server: Server.HTTPS_PUBLIC },
+  target: { server: Server.HTTPS_PUBLIC },
+  expected: TestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.https.window-expected.txt
new file mode 100644
index 0000000..6181be5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.https.window-expected.txt
@@ -0,0 +1,8 @@
+This is a testharness.js-based test.
+FAIL treat-as-public to local: failed preflight. assert_equals: response loaded expected false but got true
+FAIL treat-as-public to local: success. assert_equals: response loaded expected true but got false
+FAIL treat-as-public to private: failed preflight. assert_equals: response loaded expected false but got true
+FAIL treat-as-public to private: success. assert_equals: response loaded expected true but got false
+PASS public to public: success.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.https.window.js
new file mode 100644
index 0000000..91d9186
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.https.window.js
@@ -0,0 +1,58 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests mirror `Worker` tests, except using `SharedWorker`.
+// See also: worker.https.window.js
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: shared-worker.window.js
+
+promise_test(t => sharedWorkerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_LOCAL,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTPS_LOCAL },
+  expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to local: failed preflight.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_LOCAL,
+    treatAsPublic: true,
+  },
+  target: {
+    server: Server.HTTPS_LOCAL,
+    behavior: { preflight: PreflightBehavior.success(token()) },
+  },
+  expected: WorkerScriptTestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_PRIVATE,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTPS_PRIVATE },
+  expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to private: failed preflight.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_PRIVATE,
+    treatAsPublic: true,
+  },
+  target: {
+    server: Server.HTTPS_PRIVATE,
+    behavior: { preflight: PreflightBehavior.success(token()) },
+  },
+  expected: WorkerScriptTestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+  source: { server: Server.HTTPS_PUBLIC },
+  target: { server: Server.HTTPS_PUBLIC },
+  expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.window-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.window-expected.txt
new file mode 100644
index 0000000..5afa07e7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.window-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+FAIL treat-as-public to local: failure. assert_equals: response loaded expected false but got true
+FAIL treat-as-public to private: failure. assert_equals: response loaded expected false but got true
+PASS public to public: success.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.window.js
new file mode 100644
index 0000000..ffa8a36
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/shared-worker.window.js
@@ -0,0 +1,34 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests mirror `Worker` tests, except using `SharedWorker`.
+// See also: shared-worker.window.js
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: shared-worker.https.window.js
+
+promise_test(t => sharedWorkerScriptTest(t, {
+  source: {
+    server: Server.HTTP_LOCAL,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTP_LOCAL },
+  expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+  source: {
+    server: Server.HTTP_PRIVATE,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTP_PRIVATE },
+  expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+  source: { server: Server.HTTP_PUBLIC },
+  target: { server: Server.HTTP_PUBLIC },
+  expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/worker.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/worker.https.window.js
new file mode 100644
index 0000000..fd38f7cf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/worker.https.window.js
@@ -0,0 +1,61 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that initial `Worker` script fetches are subject to Private
+// Network Access checks, just like a regular `fetch()`. The main difference is
+// that workers can only be fetched same-origin, so the only way to test this
+// is using the `treat-as-public` CSP directive to artificially place the parent
+// document in the `public` IP address space.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: worker.window.js
+
+promise_test(t => workerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_LOCAL,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTPS_LOCAL },
+  expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to local: failed preflight.");
+
+promise_test(t => workerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_LOCAL,
+    treatAsPublic: true,
+  },
+  target: {
+    server: Server.HTTPS_LOCAL,
+    behavior: { preflight: PreflightBehavior.success(token()) },
+  },
+  expected: WorkerScriptTestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => workerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_PRIVATE,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTPS_PRIVATE },
+  expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to private: failed preflight.");
+
+promise_test(t => workerScriptTest(t, {
+  source: {
+    server: Server.HTTPS_PRIVATE,
+    treatAsPublic: true,
+  },
+  target: {
+    server: Server.HTTPS_PRIVATE,
+    behavior: { preflight: PreflightBehavior.success(token()) },
+  },
+  expected: WorkerScriptTestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => workerScriptTest(t, {
+  source: { server: Server.HTTPS_PUBLIC },
+  target: { server: Server.HTTPS_PUBLIC },
+  expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/worker.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/worker.window.js
new file mode 100644
index 0000000..118c0992
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/worker.window.js
@@ -0,0 +1,37 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that initial `Worker` script fetches are subject to Private
+// Network Access checks, just like a regular `fetch()`. The main difference is
+// that workers can only be fetched same-origin, so the only way to test this
+// is using the `treat-as-public` CSP directive to artificially place the parent
+// document in the `public` IP address space.
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: worker.https.window.js
+
+promise_test(t => workerScriptTest(t, {
+  source: {
+    server: Server.HTTP_LOCAL,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTP_LOCAL },
+  expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => workerScriptTest(t, {
+  source: {
+    server: Server.HTTP_PRIVATE,
+    treatAsPublic: true,
+  },
+  target: { server: Server.HTTP_PRIVATE },
+  expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => workerScriptTest(t, {
+  source: { server: Server.HTTP_PUBLIC },
+  target: { server: Server.HTTP_PUBLIC },
+  expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/compositing/video/video-poster-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/compositing/video/video-poster-expected.txt
index 31eb852..88ffc0a 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/compositing/video/video-poster-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/compositing/video/video-poster-expected.txt
@@ -45,6 +45,7 @@
       "name": "LayoutFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [352, 288],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
index 060045c..ce46b04 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
@@ -61,6 +61,7 @@
       "name": "LayoutFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [150, 60],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/compositing/video/video-poster-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/compositing/video/video-poster-expected.txt
index f867ea2..d8462e0c 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/compositing/video/video-poster-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/highdpi/compositing/video/video-poster-expected.txt
@@ -45,6 +45,7 @@
       "name": "LayoutNGFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [528, 432],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
index 211d236..ed99cd7 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
@@ -61,6 +61,7 @@
       "name": "LayoutNGFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [225, 90],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/virtual/backface-visibility-interop/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/virtual/backface-visibility-interop/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
index 211d236..ed99cd7 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/virtual/backface-visibility-interop/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/highdpi/virtual/backface-visibility-interop/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
@@ -61,6 +61,7 @@
       "name": "LayoutNGFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [225, 90],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/paint/invalidation/background/obscured-background-no-repaint.html b/third_party/blink/web_tests/paint/invalidation/background/obscured-background-no-repaint.html
index 87e510c8..19a62bfe 100644
--- a/third_party/blink/web_tests/paint/invalidation/background/obscured-background-no-repaint.html
+++ b/third_party/blink/web_tests/paint/invalidation/background/obscured-background-no-repaint.html
@@ -53,6 +53,9 @@
         for (var layer of JSON.parse(layerTree).layers) {
             if (layer.invalidations) {
                 for (var invalidation of layer.invalidations) {
+                    // We don't check no-repeat for background obscuration.
+                    if (invalidation.object.includes("id='target2'"))
+                        continue;
                     // Ok to invalidate the IFC of block-in-inline.
                     // See |LayoutBox::ForegroundIsKnownToBeOpaqueInRect|.
                     if (invalidation.object.includes("id='target4'"))
@@ -61,7 +64,8 @@
                 }
             }
         }
-        // Passes if there is no invalidations other than imgForAdvanceImageAnimation.
+        // Passes if there is no invalidations other than imgForAdvanceImageAnimation
+        // (which ensures we are running the image animation).
         if (Object.keys(invalidatedObjects).length != 1 ||
             !Object.keys(invalidatedObjects)[0].includes('imgForAdvanceImageAnimation'))
             output.textContent = 'FAIL: Unexpected paint invalidations: ' + JSON.stringify(invalidatedObjects) + '\n' + layerTree;
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
index eb16f6f..2f3bf8c 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
@@ -61,6 +61,7 @@
       "name": "LayoutNGFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [150, 60],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
index f2fa9bba..d3139e8 100644
--- a/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/backface-visibility-interop/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/linux/virtual/backface-visibility-interop/transforms/transformed-focused-text-input-expected.png
deleted file mode 100644
index f2fa9bba..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/backface-visibility-interop/transforms/transformed-focused-text-input-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/webmessaging/without-ports/020-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/webmessaging/without-ports/020-expected.txt
new file mode 100644
index 0000000..44de48e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.15/external/wpt/webmessaging/without-ports/020-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+PASS cross-origin test
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
index ae935f5..5d2502f 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
@@ -45,6 +45,7 @@
       "name": "LayoutNGFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [352, 288],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
index 2280a61..e841af8 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
@@ -61,6 +61,7 @@
       "name": "LayoutNGFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [150, 60],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/platform/mac/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/mac/transforms/transformed-focused-text-input-expected.png
index b5c04d0d..9837f7e1 100644
--- a/third_party/blink/web_tests/platform/mac/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/platform/mac/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/backface-visibility-interop/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/mac/virtual/backface-visibility-interop/transforms/transformed-focused-text-input-expected.png
deleted file mode 100644
index b5c04d0d..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/backface-visibility-interop/transforms/transformed-focused-text-input-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt b/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
index 5db5b0d2..e481296 100644
--- a/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
@@ -45,6 +45,7 @@
       "name": "LayoutNGFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [352, 288],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
index 1f9ff19..0f075a0 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
@@ -61,6 +61,7 @@
       "name": "LayoutNGFlexibleBox DIV class='sizing-small phase-pre-ready state-no-source'",
       "bounds": [150, 60],
       "contentsOpaqueForText": true,
+      "drawsContent": false,
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
index 303193a..41fbe50 100644
--- a/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/fetch/private-network-access/worker.https.window-expected.txt b/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/fetch/private-network-access/worker.https.window-expected.txt
new file mode 100644
index 0000000..6181be5
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/fetch/private-network-access/worker.https.window-expected.txt
@@ -0,0 +1,8 @@
+This is a testharness.js-based test.
+FAIL treat-as-public to local: failed preflight. assert_equals: response loaded expected false but got true
+FAIL treat-as-public to local: success. assert_equals: response loaded expected true but got false
+FAIL treat-as-public to private: failed preflight. assert_equals: response loaded expected false but got true
+FAIL treat-as-public to private: success. assert_equals: response loaded expected true but got false
+PASS public to public: success.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/fetch/private-network-access/worker.window-expected.txt b/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/fetch/private-network-access/worker.window-expected.txt
new file mode 100644
index 0000000..5afa07e7
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/plz-dedicated-worker/external/wpt/fetch/private-network-access/worker.window-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+FAIL treat-as-public to local: failure. assert_equals: response loaded expected false but got true
+FAIL treat-as-public to private: failure. assert_equals: response loaded expected false but got true
+PASS public to public: success.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-transforms/crashtests/large-offset-and-box-reflect-001.html b/third_party/blink/web_tests/wpt_internal/css/css-transforms/crashtests/large-offset-and-box-reflect-001.html
new file mode 100644
index 0000000..eb4367d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-transforms/crashtests/large-offset-and-box-reflect-001.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1282555">
+<style>
+#inner {
+  padding-top: 6px;
+  mix-blend-mode: soft-light;
+  offset: path("M 1 2147483648 h 7 v 0") 76px 0deg;
+}
+#outer {
+  overflow-x: overlay;
+  padding-top: 6px;
+  height: 0px;
+  border-top-right-radius: 8px;
+  resize: vertical;
+  -webkit-box-reflect: above 8px;
+}
+</style>
+<div id="outer"><div id="inner"></div></div>
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index d70756b..9fcb959 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-11-1-16-g4eb6cb881
-Revision: 4eb6cb8818057a022f97176b53738ee3098c8eb6
+Version: VER-2-11-1-17-g2b672e721
+Revision: 2b672e7210a6e989aca4787fb81f4b2542bad9c1
 CPEPrefix: cpe:/a:freetype:freetype:2.11.1
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/freetype/include/freetype-custom/freetype/config/ftoption.h b/third_party/freetype/include/freetype-custom/freetype/config/ftoption.h
index 0097f22..3e559a6 100644
--- a/third_party/freetype/include/freetype-custom/freetype/config/ftoption.h
+++ b/third_party/freetype/include/freetype-custom/freetype/config/ftoption.h
@@ -219,6 +219,10 @@
    *   If you use a build system like cmake or the `configure` script,
    *   options set by those programs have precedence, overwriting the value
    *   here with the configured one.
+   *
+   *   If you use the GNU make build system directly (that is, without the
+   *   `configure` script) and you define this macro, you also have to pass
+   *   `SYSTEM_ZLIB=yes` as an argument to make.
    */
 #define FT_CONFIG_OPTION_SYSTEM_ZLIB
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 3bc6722..f5ab9bd1 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -11773,6 +11773,544 @@
   <int value="1000003" label="kConnectionError"/>
 </enum>
 
+<enum name="CDPCommands">
+<!-- Generated from *.pdl files.
+Called by update_protocol_commands_enum.py.-->
+
+  <int value="-2145122747" label="CSS.getMatchedStylesForNode"/>
+  <int value="-2144344672" label="Accessibility.getRootAXNode"/>
+  <int value="-2140450778" label="Network.enableReportingApi"/>
+  <int value="-2120702918" label="Network.enable"/>
+  <int value="-2114426720" label="Target.getTargetInfo"/>
+  <int value="-2090524743" label="Performance.disable"/>
+  <int value="-2085416813" label="Page.reload"/>
+  <int value="-2077791758" label="CSS.setEffectivePropertyValueForNode"/>
+  <int value="-2074309237" label="DOMSnapshot.disable"/>
+  <int value="-2071198138" label="WebAuthn.removeVirtualAuthenticator"/>
+  <int value="-2061665068" label="Runtime.releaseObjectGroup"/>
+  <int value="-2054641626" label="Network.setBlockedURLs"/>
+  <int value="-2048050381" label="CSS.getStyleSheetText"/>
+  <int value="-2043283578" label="HeapProfiler.getObjectByHeapObjectId"/>
+  <int value="-2037885365" label="Debugger.continueToLocation"/>
+  <int value="-2019499730" label="WebAuthn.clearCredentials"/>
+  <int value="-2013503780" label="Target.setAutoAttach"/>
+  <int value="-1984361426" label="Page.setWebLifecycleState"/>
+  <int value="-1965623020" label="Overlay.setShowLayoutShiftRegions"/>
+  <int value="-1965057034" label="ServiceWorker.startWorker"/>
+  <int value="-1964329099" label="Debugger.setScriptSource"/>
+  <int value="-1949001185" label="Page.setDeviceOrientationOverride"/>
+  <int value="-1921205276" label="CSS.setRuleSelector"/>
+  <int value="-1884786444" label="CSS.setContainerQueryText"/>
+  <int value="-1880887647" label="Browser.resetPermissions"/>
+  <int value="-1858038350" label="LayerTree.profileSnapshot"/>
+  <int value="-1851961771" label="Overlay.setShowGridOverlays"/>
+  <int value="-1841547092" label="DOM.setInspectedNode"/>
+  <int value="-1826914751" label="CSS.enable"/>
+  <int value="-1821623837" label="DOM.getNodesForSubtreeByStyle"/>
+  <int value="-1812700715" label="Network.setCacheDisabled"/>
+  <int value="-1805700735" label="Debugger.setPauseOnExceptions"/>
+  <int value="-1803793450" label="DOMDebugger.removeInstrumentationBreakpoint"/>
+  <int value="-1787839645" label="Animation.setPlaybackRate"/>
+  <int value="-1784328782" label="Page.setFontFamilies"/>
+  <int value="-1781466156" label="ServiceWorker.deliverPushMessage"/>
+  <int value="-1777331751" label="Memory.setPressureNotificationsSuppressed"/>
+  <int value="-1755290507" label="Page.addScriptToEvaluateOnLoad"/>
+  <int value="-1748219543" label="Cast.startTabMirroring"/>
+  <int value="-1741977375" label="Input.synthesizePinchGesture"/>
+  <int value="-1703305710" label="CSS.getMediaQueries"/>
+  <int value="-1702182004" label="HeapProfiler.startSampling"/>
+  <int value="-1699153796" label="Debugger.setBlackboxedRanges"/>
+  <int value="-1688287057" label="Browser.getBrowserCommandLine"/>
+  <int value="-1679779501" label="Profiler.getBestEffortCoverage"/>
+  <int value="-1678649390" label="Overlay.highlightSourceOrder"/>
+  <int value="-1673966787" label="Page.removeScriptToEvaluateOnNewDocument"/>
+  <int value="-1667233417" label="VisualDebugger.startStream"/>
+  <int value="-1658779077" label="Debugger.pause"/>
+  <int value="-1652028774" label="Security.setIgnoreCertificateErrors"/>
+  <int value="-1644991671" label="BackgroundService.stopObserving"/>
+  <int value="-1644184365" label="Runtime.awaitPromise"/>
+  <int value="-1624825298" label="PerformanceTimeline.enable"/>
+  <int value="-1585273540" label="Network.canClearBrowserCache"/>
+  <int value="-1569586139" label="Network.canEmulateNetworkConditions"/>
+  <int value="-1563030253" label="Runtime.terminateExecution"/>
+  <int value="-1544247768" label="Profiler.stop"/>
+  <int value="-1527727763" label="WindowManager.enterOverviewMode"/>
+  <int value="-1526812044" label="DOM.highlightNode"/>
+  <int value="-1525750288" label="CacheStorage.deleteCache"/>
+  <int value="-1519947305" label="DOM.getContentQuads"/>
+  <int value="-1515300649" label="Emulation.setDefaultBackgroundColorOverride"/>
+  <int value="-1492666567" label="Accessibility.disable"/>
+  <int value="-1470297258" label="Emulation.setEmulatedVisionDeficiency"/>
+  <int value="-1465858972" label="Fetch.getResponseBody"/>
+  <int value="-1455930618" label="Target.createTarget"/>
+  <int value="-1451540405" label="Input.emulateTouchFromMouseEvent"/>
+  <int value="-1446883163" label="Console.clearMessages"/>
+  <int value="-1442526576" label="Storage.untrackCacheStorageForOrigin"/>
+  <int value="-1432964347" label="Page.getCookies"/>
+  <int value="-1431697196" label="DOM.getNodeForLocation"/>
+  <int value="-1420317579" label="Security.setOverrideCertificateErrors"/>
+  <int value="-1395942554" label="Page.getManifestIcons"/>
+  <int value="-1371094883" label="DOM.collectClassNamesFromSubtree"/>
+  <int value="-1369223898" label="Page.createIsolatedWorld"/>
+  <int value="-1368526920" label="DOM.getAttributes"/>
+  <int value="-1367872962" label="DOMStorage.clear"/>
+  <int value="-1357267098" label="Runtime.evaluate"/>
+  <int value="-1355571742" label="Tracing.recordClockSyncMarker"/>
+  <int value="-1355120895" label="Storage.clearCookies"/>
+  <int value="-1351903301" label="IO.close"/>
+  <int value="-1346825537" label="DOM.requestChildNodes"/>
+  <int value="-1345093649" label="Emulation.clearDeviceMetricsOverride"/>
+  <int value="-1343392079" label="Overlay.highlightNode"/>
+  <int value="-1342513878" label="Animation.getCurrentTime"/>
+  <int value="-1324576626" label="Emulation.setAutoDarkModeOverride"/>
+  <int value="-1322277271" label="HeapProfiler.startTrackingHeapObjects"/>
+  <int value="-1302835117" label="Network.clearBrowserCache"/>
+  <int value="-1295808105" label="Runtime.getHeapUsage"/>
+  <int value="-1293169951" label="Overlay.setShowContainerQueryOverlays"/>
+  <int value="-1288191555" label="Audits.enable"/>
+  <int value="-1284949359" label="Emulation.setDeviceMetricsOverride"/>
+  <int value="-1258931033" label="Emulation.setVirtualTimePolicy"/>
+  <int value="-1257736881" label="Page.getAppManifest"/>
+  <int value="-1250238276" label="LayerTree.releaseSnapshot"/>
+  <int value="-1244378441" label="Target.getBrowserContexts"/>
+  <int value="-1243666767" label="Security.handleCertificateError"/>
+  <int value="-1231855102" label="Page.captureSnapshot"/>
+  <int value="-1225935188" label="Accessibility.getChildAXNodes"/>
+  <int value="-1216493428" label="Runtime.removeBinding"/>
+  <int value="-1213122281" label="Cast.enable"/>
+  <int value="-1209043890" label="Animation.setPaused"/>
+  <int value="-1208897686" label="LayerTree.enable"/>
+  <int value="-1199243217" label="Security.enable"/>
+  <int value="-1197428003" label="DOMDebugger.setXHRBreakpoint"/>
+  <int value="-1173968788" label="Accessibility.queryAXTree"/>
+  <int value="-1173653183" label="Page.setLifecycleEventsEnabled"/>
+  <int value="-1173634427" label="Profiler.takePreciseCoverage"/>
+  <int value="-1158992641" label="IndexedDB.enable"/>
+  <int value="-1158254608" label="Page.generateTestReport"/>
+  <int value="-1151367351" label="Emulation.clearIdleOverride"/>
+  <int value="-1145539007" label="Runtime.releaseObject"/>
+  <int value="-1145265485" label="Accessibility.enable"/>
+  <int value="-1144692665" label="WebAudio.enable"/>
+  <int value="-1137656432" label="Emulation.setFocusEmulationEnabled"/>
+  <int value="-1112688989" label="Debugger.getScriptSource"/>
+  <int value="-1101830280" label="Runtime.queryObjects"/>
+  <int value="-1086562894" label="DOM.markUndoableState"/>
+  <int value="-1079235489" label="Target.exposeDevToolsProtocol"/>
+  <int value="-1029638029"
+      label="DeviceOrientation.setDeviceOrientationOverride"/>
+  <int value="-1029220989" label="Emulation.setUserAgentOverride"/>
+  <int value="-1022705504" label="DOM.getQueryingDescendantsForContainer"/>
+  <int value="-1007644344" label="Tracing.end"/>
+  <int value="-1002264197" label="Page.getResourceTree"/>
+  <int value="-990115402" label="Page.getFrameTree"/>
+  <int value="-988085333" label="Page.searchInResource"/>
+  <int value="-986068555" label="Target.closeTarget"/>
+  <int value="-970451323" label="Network.deleteCookies"/>
+  <int value="-960596835" label="LayerTree.makeSnapshot"/>
+  <int value="-958796925" label="Network.searchInResponseBody"/>
+  <int value="-955928342" label="Browser.getWindowBounds"/>
+  <int value="-953162206" label="Debugger.removeBreakpoint"/>
+  <int value="-948475012" label="WebAuthn.getCredential"/>
+  <int value="-945591029" label="Overlay.highlightRect"/>
+  <int value="-944411795" label="LayerTree.compositingReasons"/>
+  <int value="-911207405" label="Emulation.setTouchEmulationEnabled"/>
+  <int value="-907457147" label="Tethering.bind"/>
+  <int value="-901272541" label="Debugger.setSkipAllPauses"/>
+  <int value="-891040737" label="Performance.enable"/>
+  <int value="-890778350" label="Target.getTargets"/>
+  <int value="-875190713" label="Console.enable"/>
+  <int value="-871086928" label="Page.setFontSizes"/>
+  <int value="-867245446" label="Overlay.setShowIsolatedElements"/>
+  <int value="-866676444" label="Input.insertText"/>
+  <int value="-858293734" label="Input.setInterceptDrags"/>
+  <int value="-858025699" label="ServiceWorker.updateRegistration"/>
+  <int value="-854072406" label="Runtime.compileScript"/>
+  <int value="-848915045" label="Debugger.searchInContent"/>
+  <int value="-846072986" label="Storage.trackIndexedDBForOrigin"/>
+  <int value="-838401229" label="CSS.takeComputedStyleUpdates"/>
+  <int value="-829268123" label="Browser.setDownloadBehavior"/>
+  <int value="-818356867" label="Input.setIgnoreInputEvents"/>
+  <int value="-816796286" label="Animation.releaseAnimations"/>
+  <int value="-800701259"
+      label="EventBreakpoints.removeInstrumentationBreakpoint"/>
+  <int value="-789619680" label="DOMStorage.getDOMStorageItems"/>
+  <int value="-781301357" label="HeadlessExperimental.enable"/>
+  <int value="-775886564" label="DOM.discardSearchResults"/>
+  <int value="-774038059" label="Browser.crash"/>
+  <int value="-772306428" label="Fetch.continueWithAuth"/>
+  <int value="-763358578" label="Animation.enable"/>
+  <int value="-762202899" label="Runtime.setCustomObjectFormatterEnabled"/>
+  <int value="-755375693" label="Accessibility.getAXNodeAndAncestors"/>
+  <int value="-744282770" label="Log.disable"/>
+  <int value="-739815822" label="Overlay.setPausedInDebuggerMessage"/>
+  <int value="-738488878" label="DOMDebugger.removeXHRBreakpoint"/>
+  <int value="-721034032" label="Runtime.setAsyncCallStackDepth"/>
+  <int value="-711908380" label="Page.screencastFrameAck"/>
+  <int value="-707727729" label="Debugger.pauseOnAsyncCall"/>
+  <int value="-695222613" label="Target.sendMessageToTarget"/>
+  <int value="-691754282" label="Debugger.getPossibleBreakpoints"/>
+  <int value="-688213769" label="Emulation.canEmulate"/>
+  <int value="-681199137" label="DOM.setNodeValue"/>
+  <int value="-673814370" label="Overlay.setShowViewportSizeOnResize"/>
+  <int value="-665789240" label="Cast.startDesktopMirroring"/>
+  <int value="-664387516" label="CSS.addRule"/>
+  <int value="-662080232" label="DOM.getFileInfo"/>
+  <int value="-639662723" label="Page.printToPDF"/>
+  <int value="-631715272" label="Page.navigateToHistoryEntry"/>
+  <int value="-629408737" label="Tracing.start"/>
+  <int value="-626460204" label="ServiceWorker.disable"/>
+  <int value="-617191394" label="Debugger.setBreakpointOnFunctionCall"/>
+  <int value="-609229118" label="Target.detachFromTarget"/>
+  <int value="-604953317" label="DOM.describeNode"/>
+  <int value="-603976241" label="Runtime.enable"/>
+  <int value="-603062504" label="Overlay.disable"/>
+  <int value="-600097863" label="Cast.setSinkToUse"/>
+  <int value="-590920088" label="Debugger.setReturnValue"/>
+  <int value="-590804334" label="Input.imeSetComposition"/>
+  <int value="-578519885" label="Overlay.setShowFlexOverlays"/>
+  <int value="-573285176" label="CSS.setLocalFontsEnabled"/>
+  <int value="-568907586" label="DOM.getFlattenedDocument"/>
+  <int value="-568009441" label="Animation.getPlaybackRate"/>
+  <int value="-559198953" label="DOM.removeAttribute"/>
+  <int value="-551218294" label="Emulation.setVisibleSize"/>
+  <int value="-535671266" label="Memory.simulatePressureNotification"/>
+  <int value="-531544830" label="Debugger.restartFrame"/>
+  <int value="-530334410" label="Page.setDeviceMetricsOverride"/>
+  <int value="-500426075" label="DOMStorage.enable"/>
+  <int value="-459969447" label="Runtime.callFunctionOn"/>
+  <int value="-459625210" label="SystemInfo.getProcessInfo"/>
+  <int value="-437962397" label="DOM.getRelayoutBoundary"/>
+  <int value="-436119998" label="HeapProfiler.collectGarbage"/>
+  <int value="-427019446" label="Fetch.failRequest"/>
+  <int value="-423918421" label="Network.setAttachDebugStack"/>
+  <int value="-417003470" label="CSS.setMediaText"/>
+  <int value="-404755282" label="Page.clearDeviceOrientationOverride"/>
+  <int value="-396596692" label="DOM.querySelector"/>
+  <int value="-394047025" label="DOMSnapshot.captureSnapshot"/>
+  <int value="-389389859" label="Security.disable"/>
+  <int value="-377338474" label="Runtime.disable"/>
+  <int value="-362386202" label="Overlay.setShowFPSCounter"/>
+  <int value="-361464467" label="Browser.getHistogram"/>
+  <int value="-348531350" label="CSS.getPlatformFontsForNode"/>
+  <int value="-331795539" label="Runtime.setMaxCallStackSizeToCapture"/>
+  <int value="-310372475" label="Page.setDocumentContent"/>
+  <int value="-302864521" label="Target.createBrowserContext"/>
+  <int value="-297759119" label="Profiler.stopTypeProfile"/>
+  <int value="-289149939" label="IndexedDB.clearObjectStore"/>
+  <int value="-277117129" label="Browser.getVersion"/>
+  <int value="-275022964" label="Page.enable"/>
+  <int value="-267746267" label="DOM.getFrameOwner"/>
+  <int value="-266984094" label="Fetch.fulfillRequest"/>
+  <int value="-249710131" label="Page.getAppId"/>
+  <int value="-247806539" label="WindowManager.exitOverviewMode"/>
+  <int value="-247683435" label="DOM.pushNodeByPathToFrontend"/>
+  <int value="-242754535" label="Target.autoAttachRelated"/>
+  <int value="-242721628" label="Overlay.getGridHighlightObjectsForTest"/>
+  <int value="-242161447" label="Media.enable"/>
+  <int value="-234690229" label="Runtime.getIsolateId"/>
+  <int value="-222268748" label="Browser.close"/>
+  <int value="-220986582" label="DOMDebugger.setBreakOnCSPViolation"/>
+  <int value="-211237482" label="Input.synthesizeTapGesture"/>
+  <int value="-208652542" label="DOM.setFileInputFiles"/>
+  <int value="-202100275" label="CSS.createStyleSheet"/>
+  <int value="-195833656" label="BackgroundService.clearEvents"/>
+  <int value="-190386215" label="Page.clearDeviceMetricsOverride"/>
+  <int value="-181158610" label="DOM.copyTo"/>
+  <int value="-177013498" label="IndexedDB.deleteObjectStoreEntries"/>
+  <int value="-175944980" label="Runtime.runScript"/>
+  <int value="-166036459" label="Storage.getUsageAndQuota"/>
+  <int value="-151644927" label="Runtime.discardConsoleEntries"/>
+  <int value="-150266330" label="DOM.disable"/>
+  <int value="-145467037" label="WebAuthn.disable"/>
+  <int value="-141740484" label="Network.setRequestInterception"/>
+  <int value="-94534758" label="Browser.grantPermissions"/>
+  <int value="-80583668" label="HeapProfiler.takeHeapSnapshot"/>
+  <int value="-76190083" label="DOM.getOuterHTML"/>
+  <int value="-75081120" label="Accessibility.getFullAXTree"/>
+  <int value="-67509677" label="DOMDebugger.setDOMBreakpoint"/>
+  <int value="-66426073" label="Overlay.enable"/>
+  <int value="-52351725" label="Debugger.disable"/>
+  <int value="-48195242" label="LayerTree.snapshotCommandLog"/>
+  <int value="-25801263" label="Fetch.enable"/>
+  <int value="-16024274" label="ServiceWorker.dispatchSyncEvent"/>
+  <int value="-9686684" label="Target.activateTarget"/>
+  <int value="21859521" label="Profiler.disable"/>
+  <int value="23568337" label="Inspector.enable"/>
+  <int value="24052971" label="Network.getRequestPostData"/>
+  <int value="26373409" label="Fetch.disable"/>
+  <int value="30791492" label="Log.stopViolationsReport"/>
+  <int value="53114292" label="Browser.crashGpuProcess"/>
+  <int value="86737553" label="BackgroundService.startObserving"/>
+  <int value="90550342" label="DOM.resolveNode"/>
+  <int value="91566930" label="CSS.setStyleSheetText"/>
+  <int value="93360860" label="DOMDebugger.setEventListenerBreakpoint"/>
+  <int value="111383160" label="Page.setDownloadBehavior"/>
+  <int value="139177156" label="Network.getCertificate"/>
+  <int value="149170295" label="Overlay.getHighlightObjectForTest"/>
+  <int value="150799963" label="HeapProfiler.getSamplingProfile"/>
+  <int value="155326200" label="HeapProfiler.enable"/>
+  <int value="158774237" label="WebAuthn.removeCredential"/>
+  <int value="159066111" label="WebAudio.getRealtimeData"/>
+  <int value="169232265" label="Storage.clearTrustTokens"/>
+  <int value="174855927" label="DOMStorage.removeDOMStorageItem"/>
+  <int value="177653395" label="Browser.setPermission"/>
+  <int value="187560850" label="Emulation.setEmulatedMedia"/>
+  <int value="196328412" label="WebAuthn.enable"/>
+  <int value="196370490" label="Network.getSecurityIsolationStatus"/>
+  <int value="208235261" label="Memory.startSampling"/>
+  <int value="246064052" label="Debugger.stepOver"/>
+  <int value="262694304" label="Page.setSPCTransactionMode"/>
+  <int value="263187585" label="IO.read"/>
+  <int value="270144535" label="CacheStorage.requestCachedResponse"/>
+  <int value="271867211" label="Memory.getSamplingProfile"/>
+  <int value="290135457" label="Network.disable"/>
+  <int value="290409213" label="Page.setTouchEmulationEnabled"/>
+  <int value="292481558" label="Storage.trackCacheStorageForOrigin"/>
+  <int value="292528027" label="CSS.takeCoverageDelta"/>
+  <int value="295531820" label="Page.clearCompilationCache"/>
+  <int value="308700671" label="DOM.setAttributeValue"/>
+  <int value="327696792" label="DOM.pushNodesByBackendIdsToFrontend"/>
+  <int value="335437143" label="Performance.setTimeDomain"/>
+  <int value="336021010" label="Browser.getWindowForTarget"/>
+  <int value="341273323" label="DOM.performSearch"/>
+  <int value="348034141" label="Fetch.continueResponse"/>
+  <int value="353032180" label="Page.getInstallabilityErrors"/>
+  <int value="358972747" label="Emulation.setCPUThrottlingRate"/>
+  <int value="379234119" label="Browser.cancelDownload"/>
+  <int value="379903893" label="Log.enable"/>
+  <int value="388031835" label="Profiler.startTypeProfile"/>
+  <int value="389992603" label="Page.waitForDebugger"/>
+  <int value="393843097" label="Debugger.setInstrumentationBreakpoint"/>
+  <int value="399273851"
+      label="Network.takeResponseBodyForInterceptionAsStream"/>
+  <int value="399689316" label="DOMSnapshot.enable"/>
+  <int value="408966355" label="Memory.getAllTimeSamplingProfile"/>
+  <int value="411573731" label="DOM.getSearchResults"/>
+  <int value="421675002" label="Page.addCompilationCache"/>
+  <int value="429566559" label="DOM.removeNode"/>
+  <int value="430311216" label="Page.stopLoading"/>
+  <int value="430414061" label="Database.disable"/>
+  <int value="445282012" label="CSS.disable"/>
+  <int value="446273824" label="IndexedDB.disable"/>
+  <int value="456715942" label="Network.setAcceptedEncodings"/>
+  <int value="457119051" label="ServiceWorker.stopAllWorkers"/>
+  <int value="458783544" label="Emulation.setLocaleOverride"/>
+  <int value="466229462" label="ServiceWorker.skipWaiting"/>
+  <int value="479774670" label="Page.setGeolocationOverride"/>
+  <int value="497546749" label="Animation.resolveAnimation"/>
+  <int value="508794131" label="DOM.focus"/>
+  <int value="529971716" label="Network.emulateNetworkConditions"/>
+  <int value="530494361" label="WebAuthn.addCredential"/>
+  <int value="536921420" label="Debugger.setBreakpointsActive"/>
+  <int value="559429465" label="DOMDebugger.getEventListeners"/>
+  <int value="565557320" label="Overlay.setShowPaintRects"/>
+  <int value="582901199" label="Page.crash"/>
+  <int value="599262407" label="DOM.setNodeName"/>
+  <int value="618183074" label="Browser.getHistograms"/>
+  <int value="618382475" label="Page.getOriginTrials"/>
+  <int value="622147841" label="Cast.stopCasting"/>
+  <int value="626600378" label="Input.dispatchDragEvent"/>
+  <int value="632939937" label="Page.setInterceptFileChooserDialog"/>
+  <int value="641086326" label="HeapProfiler.getHeapObjectId"/>
+  <int value="645220257" label="Debugger.setBreakpoint"/>
+  <int value="649541695" label="Network.setExtraHTTPHeaders"/>
+  <int value="651945695" label="Network.canClearBrowserCookies"/>
+  <int value="652630036" label="Emulation.setDisabledImageTypes"/>
+  <int value="670149526" label="Page.disable"/>
+  <int value="671757807" label="SystemInfo.getInfo"/>
+  <int value="677171630" label="Page.deleteCookie"/>
+  <int value="683635159" label="Network.setCookie"/>
+  <int value="692020211" label="Debugger.evaluateOnCallFrame"/>
+  <int value="709278881" label="EventBreakpoints.setInstrumentationBreakpoint"/>
+  <int value="724549002" label="Runtime.getProperties"/>
+  <int value="729059492" label="CSS.setStyleTexts"/>
+  <int value="738767691" label="LayerTree.disable"/>
+  <int value="746599172" label="CSS.collectClassNames"/>
+  <int value="759146008" label="Storage.getCookies"/>
+  <int value="771075180" label="Page.close"/>
+  <int value="786912412" label="Emulation.setDocumentCookieDisabled"/>
+  <int value="813176426" label="Emulation.setIdleOverride"/>
+  <int value="814261671" label="VisualDebugger.stopStream"/>
+  <int value="814946914" label="Console.disable"/>
+  <int value="818686559" label="Target.setRemoteLocations"/>
+  <int value="834356206" label="Page.addScriptToEvaluateOnNewDocument"/>
+  <int value="837103377" label="Browser.setWindowBounds"/>
+  <int value="869257715" label="DOM.moveTo"/>
+  <int value="878415823" label="Target.disposeBrowserContext"/>
+  <int value="904716405" label="LayerTree.loadSnapshot"/>
+  <int value="907569144" label="Accessibility.getPartialAXTree"/>
+  <int value="913559015" label="DOM.undo"/>
+  <int value="915465516" label="Page.stopScreencast"/>
+  <int value="916698928" label="Target.attachToTarget"/>
+  <int value="935769253" label="CSS.setKeyframeKey"/>
+  <int value="942566208" label="Emulation.resetPageScaleFactor"/>
+  <int value="945359169" label="Storage.setCookies"/>
+  <int value="953030097" label="Fetch.takeResponseBodyAsStream"/>
+  <int value="967272576" label="Tracing.requestMemoryDump"/>
+  <int value="974680650" label="Media.disable"/>
+  <int value="978448179" label="BackgroundService.setRecording"/>
+  <int value="984040144" label="WebAuthn.addVirtualAuthenticator"/>
+  <int value="986343566" label="Browser.executeBrowserCommand"/>
+  <int value="993405240" label="Page.getResourceContent"/>
+  <int value="1000056561" label="Inspector.disable"/>
+  <int value="1004564198" label="DOMDebugger.removeEventListenerBreakpoint"/>
+  <int value="1016614774" label="ServiceWorker.setForceUpdateOnPageLoad"/>
+  <int value="1017434616" label="HeapProfiler.stopSampling"/>
+  <int value="1017704054" label="Network.clearAcceptedEncodingsOverride"/>
+  <int value="1024855010" label="Profiler.stopPreciseCoverage"/>
+  <int value="1026383861" label="Network.getCookies"/>
+  <int value="1026634223" label="Page.getNavigationHistory"/>
+  <int value="1060710637" label="CSS.trackComputedStyleUpdates"/>
+  <int value="1062782810" label="Network.getResponseBodyForInterception"/>
+  <int value="1070168037" label="CacheStorage.requestEntries"/>
+  <int value="1081462702" label="Performance.getMetrics"/>
+  <int value="1096155281" label="Overlay.highlightQuad"/>
+  <int value="1099637962" label="ServiceWorker.inspectWorker"/>
+  <int value="1101014906" label="Page.startScreencast"/>
+  <int value="1105205243" label="Tracing.getCategories"/>
+  <int value="1106495234" label="Input.synthesizeScrollGesture"/>
+  <int value="1107583474" label="Overlay.setShowScrollSnapOverlays"/>
+  <int value="1116221178" label="Page.removeScriptToEvaluateOnLoad"/>
+  <int value="1127653211" label="ServiceWorker.enable"/>
+  <int value="1145030905" label="ServiceWorker.dispatchPeriodicSyncEvent"/>
+  <int value="1152826264" label="DOM.getBoxModel"/>
+  <int value="1153980310" label="Audits.checkContrast"/>
+  <int value="1167544831" label="WebAuthn.setUserVerified"/>
+  <int value="1168280422" label="CSS.forcePseudoState"/>
+  <int value="1168863074" label="Network.setBypassServiceWorker"/>
+  <int value="1169680273" label="DOM.hideHighlight"/>
+  <int value="1198496813" label="CSS.getInlineStylesForNode"/>
+  <int value="1200442441" label="CacheStorage.deleteEntry"/>
+  <int value="1214187445" label="VisualDebugger.filterStream"/>
+  <int value="1219893432"
+      label="DeviceOrientation.clearDeviceOrientationOverride"/>
+  <int value="1221113372" label="LayerTree.replaySnapshot"/>
+  <int value="1229328408" label="HeapProfiler.addInspectedHeapObject"/>
+  <int value="1234956974" label="Input.dispatchKeyEvent"/>
+  <int value="1247381184" label="Emulation.setScriptExecutionDisabled"/>
+  <int value="1254672336" label="Page.setAdBlockingEnabled"/>
+  <int value="1259032272" label="Emulation.setEmitTouchEventsForMouse"/>
+  <int value="1262636987" label="Target.attachToBrowserTarget"/>
+  <int value="1266700733" label="Memory.getBrowserSamplingProfile"/>
+  <int value="1266719444" label="Page.getLayoutMetrics"/>
+  <int value="1281437313" label="Profiler.startPreciseCoverage"/>
+  <int value="1281996484" label="Debugger.setBreakpointByUrl"/>
+  <int value="1294247175" label="Page.getPermissionsPolicyState"/>
+  <int value="1297774021" label="HeapProfiler.disable"/>
+  <int value="1339457799" label="Page.produceCompilationCache"/>
+  <int value="1347864180" label="Storage.clearDataForOrigin"/>
+  <int value="1349483767" label="Emulation.setPageScaleFactor"/>
+  <int value="1355114541" label="Overlay.setShowAdHighlights"/>
+  <int value="1360040284" label="DOM.scrollIntoViewIfNeeded"/>
+  <int value="1367459250" label="Storage.untrackIndexedDBForOrigin"/>
+  <int value="1385622633" label="Memory.getDOMCounters"/>
+  <int value="1386253892" label="Emulation.setScrollbarsHidden"/>
+  <int value="1396206961" label="DOM.requestNode"/>
+  <int value="1400965131" label="Profiler.start"/>
+  <int value="1403028249" label="Page.clearGeolocationOverride"/>
+  <int value="1410125738" label="WebAuthn.getCredentials"/>
+  <int value="1425296098" label="Page.handleJavaScriptDialog"/>
+  <int value="1428875463" label="Emulation.setTimezoneOverride"/>
+  <int value="1430753789" label="CSS.stopRuleUsageTracking"/>
+  <int value="1437117511" label="Database.getDatabaseTableNames"/>
+  <int value="1447498492" label="IndexedDB.requestData"/>
+  <int value="1465527659" label="Network.clearBrowserCookies"/>
+  <int value="1468513664" label="HeadlessExperimental.beginFrame"/>
+  <int value="1473981969" label="Debugger.stepOut"/>
+  <int value="1481738191" label="DOM.setOuterHTML"/>
+  <int value="1491688128" label="Schema.getDomains"/>
+  <int value="1496745411" label="ServiceWorker.unregister"/>
+  <int value="1501336388" label="Network.getAllCookies"/>
+  <int value="1507998313" label="DOMDebugger.removeDOMBreakpoint"/>
+  <int value="1508208490" label="Cast.disable"/>
+  <int value="1510866078" label="Page.navigate"/>
+  <int value="1519789940" label="Debugger.getStackTrace"/>
+  <int value="1524679055" label="DOM.setNodeStackTracesEnabled"/>
+  <int value="1531241679" label="Animation.seekAnimations"/>
+  <int value="1534099552" label="HeapProfiler.stopTrackingHeapObjects"/>
+  <int value="1536244161" label="Overlay.setInspectMode"/>
+  <int value="1536817350" label="DOM.enable"/>
+  <int value="1539904595" label="Overlay.hideHighlight"/>
+  <int value="1543359405" label="CSS.getComputedStyleForNode"/>
+  <int value="1549510961" label="Network.setUserAgentOverride"/>
+  <int value="1574924337" label="Emulation.setNavigatorOverrides"/>
+  <int value="1607170407" label="Page.resetNavigationHistory"/>
+  <int value="1620885255" label="Animation.disable"/>
+  <int value="1637824156" label="Storage.overrideQuotaForOrigin"/>
+  <int value="1646836806" label="Log.clear"/>
+  <int value="1646926369" label="Browser.setDockTile"/>
+  <int value="1650959940" label="Tethering.unbind"/>
+  <int value="1653770998" label="Debugger.getWasmBytecode"/>
+  <int value="1660003527" label="Network.getResponseBody"/>
+  <int value="1667959673" label="DOMDebugger.setInstrumentationBreakpoint"/>
+  <int value="1670672931" label="Network.loadNetworkResource"/>
+  <int value="1672195472" label="DOM.setAttributesAsText"/>
+  <int value="1691295897" label="Network.setCookies"/>
+  <int value="1703748832" label="Debugger.setVariableValue"/>
+  <int value="1718688528" label="IndexedDB.requestDatabase"/>
+  <int value="1721800289" label="Overlay.setShowScrollBottleneckRects"/>
+  <int value="1722263252" label="Page.captureScreenshot"/>
+  <int value="1745507914" label="Overlay.getSourceOrderHighlightObjectForTest"/>
+  <int value="1771888696" label="Debugger.setBlackboxPatterns"/>
+  <int value="1773320382" label="CSS.getBackgroundColors"/>
+  <int value="1781595469" label="CacheStorage.requestCacheNames"/>
+  <int value="1789526457" label="Emulation.setGeolocationOverride"/>
+  <int value="1818737395" label="Runtime.addBinding"/>
+  <int value="1821949345" label="DOMStorage.disable"/>
+  <int value="1832339238" label="Network.continueInterceptedRequest"/>
+  <int value="1841408581" label="DOM.getNodeStackTraces"/>
+  <int value="1845967999" label="Animation.setTiming"/>
+  <int value="1852597165" label="Debugger.setAsyncCallStackDepth"/>
+  <int value="1862437443" label="Debugger.stepInto"/>
+  <int value="1863511163" label="Log.startViolationsReport"/>
+  <int value="1866482138" label="Emulation.clearGeolocationOverride"/>
+  <int value="1867970052" label="Runtime.runIfWaitingForDebugger"/>
+  <int value="1871531173" label="Audits.disable"/>
+  <int value="1872799439" label="Runtime.globalLexicalScopeNames"/>
+  <int value="1875498006" label="Input.dispatchTouchEvent"/>
+  <int value="1879140248" label="Database.enable"/>
+  <int value="1880013258" label="Debugger.resume"/>
+  <int value="1880557781" label="Storage.getTrustTokens"/>
+  <int value="1895140764" label="WebAuthn.setAutomaticPresenceSimulation"/>
+  <int value="1906105120" label="ServiceWorker.stopWorker"/>
+  <int value="1909294407" label="Profiler.enable"/>
+  <int value="1909383483" label="Audits.getEncodedResponse"/>
+  <int value="1909880687" label="Overlay.setShowWebVitals"/>
+  <int value="1916354122" label="Memory.prepareForLeakDetection"/>
+  <int value="1921901308" label="Overlay.setShowDebugBorders"/>
+  <int value="1923312798" label="IO.resolveBlob"/>
+  <int value="1925128188" label="DOM.querySelectorAll"/>
+  <int value="1930515882" label="Fetch.continueRequest"/>
+  <int value="1932914604" label="Profiler.takeTypeProfile"/>
+  <int value="1953833269" label="Overlay.highlightFrame"/>
+  <int value="1958287755"
+      label="NativeProfiling.dumpProfilingDataOfAllProcesses"/>
+  <int value="1969160702" label="WebAudio.disable"/>
+  <int value="1983891430" label="HeadlessExperimental.disable"/>
+  <int value="1994321687" label="DOMSnapshot.getSnapshot"/>
+  <int value="1998509423" label="Page.setBypassCSP"/>
+  <int value="2000633710" label="Page.bringToFront"/>
+  <int value="2003699669" label="DOM.redo"/>
+  <int value="2033808144" label="DOM.highlightRect"/>
+  <int value="2035842032" label="Input.dispatchMouseEvent"/>
+  <int value="2038753303" label="Target.setDiscoverTargets"/>
+  <int value="2039405317" label="IndexedDB.deleteDatabase"/>
+  <int value="2045011463" label="Memory.forciblyPurgeJavaScriptMemory"/>
+  <int value="2052331271" label="IndexedDB.requestDatabaseNames"/>
+  <int value="2056602650" label="Overlay.setShowHinge"/>
+  <int value="2057401477" label="Profiler.setSamplingInterval"/>
+  <int value="2069838873" label="IndexedDB.getMetadata"/>
+  <int value="2090828476" label="Network.replayXHR"/>
+  <int value="2099191527" label="DOM.getDocument"/>
+  <int value="2099947942" label="Memory.stopSampling"/>
+  <int value="2106705522" label="Overlay.setShowHitTestBorders"/>
+  <int value="2122626183" label="DOM.getContainerForNode"/>
+  <int value="2125009876" label="Database.executeSQL"/>
+  <int value="2127469544" label="DOMStorage.setDOMStorageItem"/>
+  <int value="2129092570" label="Debugger.enable"/>
+  <int value="2140268492" label="CSS.startRuleUsageTracking"/>
+</enum>
+
 <enum name="CellularConnectResult">
   <obsolete>
     Deprecated as of 04/2021.
diff --git a/tools/metrics/histograms/metadata/dev/histograms.xml b/tools/metrics/histograms/metadata/dev/histograms.xml
index 974cdd97..3b2b9c3 100644
--- a/tools/metrics/histograms/metadata/dev/histograms.xml
+++ b/tools/metrics/histograms/metadata/dev/histograms.xml
@@ -77,6 +77,23 @@
   </summary>
 </histogram>
 
+<histogram name="DevTools.CDPCommandFrom{ClientType}" enum="CDPCommands"
+    expires_after="2022-12-16">
+  <owner>wolfi@chromium.org</owner>
+  <owner>dsv@chromium.org</owner>
+  <owner>yangguo@chromium.org</owner>
+  <summary>
+    Records CDP command usage originating from {ClientType}. This is recorded
+    whenever a CDP session receives and handles a command from a CDP client.
+  </summary>
+  <token key="ClientType">
+    <variant name="DevTools"/>
+    <variant name="Extension" summary="an extension"/>
+    <variant name="Other" summary="other sources"/>
+    <variant name="RemoteDebugger" summary="remote debugging"/>
+  </token>
+</histogram>
+
 <histogram name="DevTools.ColorPicker.FixedColor"
     enum="DevToolsColorPickerFixedColor" expires_after="2021-10-04">
   <obsolete>
diff --git a/tools/metrics/histograms/update_protocol_commands_enum.py b/tools/metrics/histograms/update_protocol_commands_enum.py
new file mode 100755
index 0000000..28bfce47
--- /dev/null
+++ b/tools/metrics/histograms/update_protocol_commands_enum.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import re
+import sys
+import hashlib
+import ctypes
+
+from xml.dom import minidom
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
+import path_util
+
+sys.path.append(
+    os.path.join(os.path.dirname(__file__), '..', '..', '..', 'third_party',
+                 'inspector_protocol'))
+import pdl
+
+import update_histogram_enum
+import histogram_paths
+
+
+def GetCommandUMAId(cdp_command):
+  """Generate a hash consistent with GetCommandUMAId() in ChromeDevToolsSession.
+
+   Args:
+    cdp_command: A string containing a CDP command.
+
+   Returns:
+    The hashed value for the CDP command.
+  """
+  digest = hashlib.md5(cdp_command.encode('utf-8')).hexdigest()
+  first_eight_bytes = digest[:16]
+  long_value = int(first_eight_bytes, 16)
+  signed_32bit = ctypes.c_int(long_value).value
+  return signed_32bit
+
+
+def ParseProtocolCommandsFromPDL(file_path):
+  """Parses a PDL file and returns a dictionary of all its commands and their
+  hashes.
+
+   Args:
+    file_path: The path of the PDL file.
+
+   Returns:
+    A dictionary with the hashes as keys and the CDP commands as values.
+  """
+  file_name = path_util.GetInputFile(file_path)
+  input_file = open(file_name, "r")
+  pdl_string = input_file.read()
+  protocol = pdl.loads(pdl_string, file_name, False)
+  input_file.close()
+
+  result = {}
+  for domain in protocol["domains"]:
+    if "commands" in domain:
+      for command in domain["commands"]:
+        command_name = domain["domain"] + "." + command["name"]
+        hashed_command = GetCommandUMAId(command_name)
+        if (hashed_command in result):
+          print('Hash collision between "{}" and "{}" in {} when '\
+          'generating CDPCommands for enums.xml'
+          .format(result[hashed_command], command_name, file_path))
+        result[hashed_command] = command_name
+
+  return result
+
+
+def ParseProtocolCommandsFromXML():
+  """Parses the 'CDPCommands' enum in enums.xml.
+
+   Returns:
+    A dictionary with the hashes as keys and the CDP commands as values.
+  """
+  document = minidom.parse(
+      path_util.GetInputFile(histogram_paths.ENUMS_XML_RELATIVE))
+  result = {}
+
+  # Get DOM of the <enum name="CDPCommands"> node.
+  for enum_node in document.getElementsByTagName('enum'):
+    if enum_node.attributes['name'].value == 'CDPCommands':
+      break
+  else:
+    raise UserError('CDPCommands enum node not found in enums.xml')
+
+  for child in enum_node.childNodes:
+    if child.nodeName == 'int':
+      enum_value = int(child.attributes['value'].value)
+      enum_label = str(child.attributes['label'].value)
+      result[enum_value] = enum_label
+
+  return result
+
+
+def CheckDictsForCollisions(first, second):
+  """Compares 2 dictionaries and prints an error message for each key which
+  is contained in both dics for which the corresponding value differs in the
+  2 dicts.
+
+   Args:
+    first: A dictionary.
+    second: A dictionary.
+  """
+  for hashedValue in first.keys():
+    if (hashedValue in second and second[hashedValue] != first[hashedValue]):
+      print(
+        'Hash collision between "{}" and "{}" when generating CDPCommands '\
+        'for enums.xml'
+        .format(first[hashedValue], second[hashedValue]))
+
+
+def MaybeUpdateEnumFromFile(file_path):
+  """Gets the results of parsing a pdl file and of enums.xml, and updates
+  enums.xml if necessary.
+
+   Args:
+    file_path: Path of the pdl file to be parsed.
+  """
+  print('Parsing {}'.format(file_path))
+  pdl_dict = ParseProtocolCommandsFromPDL(file_path)
+  xml_dict = ParseProtocolCommandsFromXML()
+  CheckDictsForCollisions(pdl_dict, xml_dict)
+  files_for_enum_comment = '*.pdl files'
+  update_histogram_enum.UpdateHistogramFromDict('CDPCommands', pdl_dict,
+                                                files_for_enum_comment,
+                                                os.path.basename(__file__))
+
+
+def main():
+  """Checks that the 'CDPCommands' enum in enums.xml matches the content of
+  the various pdl protocol definition files and updates the enum if necessary.
+  """
+
+  pdl_file_paths = [
+      'third_party/blink/public/devtools_protocol/browser_protocol.pdl',
+      'v8/include/js_protocol.pdl', 'chrome/browser/devtools/cros_protocol.pdl',
+      'components/viz/common/debugger/viz_debugger.pdl',
+      'content/browser/native_profiling.pdl'
+  ]
+  for pdl_file_path in pdl_file_paths:
+    MaybeUpdateEnumFromFile(pdl_file_path)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/ui/chromeos/translations/ui_chromeos_strings_be.xtb b/ui/chromeos/translations/ui_chromeos_strings_be.xtb
index f86e4507..be14db2 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_be.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_be.xtb
@@ -47,7 +47,7 @@
 <translation id="1378727793141957596">Вітаем у Google Дыску!</translation>
 <translation id="1379911846207762492">Вы можаце зрабіць файлы даступнымі ў пазасеткавым рэжыме, каб мець магчымасць працаваць з імі, калі не будзе падключэння да інтэрнэту.</translation>
 <translation id="1383876407941801731">Пошук</translation>
-<translation id="1395262318152388157">Паўзунок пераходу</translation>
+<translation id="1395262318152388157">Паўзунок перамоткі</translation>
 <translation id="1399511500114202393">Не выкарыстоўваць сертыфікат карыстальніка</translation>
 <translation id="1404323374378969387">Нарвежская</translation>
 <translation id="1433628812591023318">Каб перацягнуць файл у Parallels Desktop, яго неабходна перамясціць у папку "Файлы Windows".</translation>
diff --git a/ui/webui/resources/images/BUILD.gn b/ui/webui/resources/images/BUILD.gn
index a9b455da..9df8d51e 100644
--- a/ui/webui/resources/images/BUILD.gn
+++ b/ui/webui/resources/images/BUILD.gn
@@ -38,6 +38,7 @@
     "select.png",
     "throbber_medium.svg",
     "throbber_small.svg",
+    "throbber_small_dark.svg",
     "dark/icon_search.svg",
   ]
 
diff --git a/ui/webui/resources/images/throbber_small_dark.svg b/ui/webui/resources/images/throbber_small_dark.svg
new file mode 100644
index 0000000..a236ecf
--- /dev/null
+++ b/ui/webui/resources/images/throbber_small_dark.svg
@@ -0,0 +1 @@
+<svg version="1" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style>@keyframes rotate{0%{transform:rotate(0deg)}to{transform:rotate(360deg)}}@keyframes fillunfill{0%{stroke-dashoffset:32.3}50%{stroke-dashoffset:0}to{stroke-dashoffset:-31.9}}@keyframes rot{0%{transform:rotate(0deg)}to{transform:rotate(-360deg)}}@keyframes colors{0%,to{stroke:#8ab4f8}}</style><g style="animation-duration:1568.63ms;animation-iteration-count:infinite;animation-name:rotate;animation-timing-function:linear;transform-origin:50% 50%;width:16px;height:16px"><path fill="none" d="M8 1.125A6.875 6.875 0 1 1 1.125 8" stroke-width="2.25" stroke-linecap="round" style="animation-duration:1333ms,5332ms,5332ms;animation-fill-mode:forwards;animation-iteration-count:infinite,infinite,infinite;animation-name:fillunfill,rot,colors;animation-play-state:running,running,running;animation-timing-function:cubic-bezier(.4,0,.2,1),steps(4),linear;transform-origin:50% 50%" stroke-dasharray="32.4" stroke-dashoffset="32.4"/></g></svg>
\ No newline at end of file