Roll googleurl to the latest version

This is based on Chromium commit
f1d6ff420ea2ca807718d8204c1e26ff45119e95 from Sun Nov 9 23:54:57 2025
-0800

Some of the Copybara fixes are borrowed from
https://quiche-review.git.corp.google.com/c/googleurl/+/11780

Change-Id: Iec1f43f65d4a8a8953e5054d2fefee483f719210
diff --git a/AUTHORS b/AUTHORS
index d6a532a..ea37ea7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,3 +1,5 @@
+# For more details, see docs/contributing.md#first_time-contributors
+#
 # Names should be added to this file with this pattern:
 #
 # For individuals:
@@ -17,6 +19,7 @@
 Aaron Leventhal <aaronlevbugs@gmail.com>
 Aaron Randolph <aaron.randolph@gmail.com>
 Aaryaman Vasishta <jem456.vasishta@gmail.com>
+AbdAlRahman Gad <abdobngad@gmail.com>
 Abdu Ameen <abdu.ameen000@gmail.com>
 Abdullah Abu Tasneem <a.tasneem@samsung.com>
 Abhijeet Kandalkar <abhijeet.k@samsung.com>
@@ -36,6 +39,7 @@
 Adam Yi <i@adamyi.com>
 Addanki Gandhi Kishor <kishor.ag@samsung.com>
 Adenilson Cavalcanti <a.cavalcanti@samsung.com>
+Adesh Attavar <adesh.attavar@gmail.com>
 Aditi Singh <a20.singh@samsung.com>
 Aditya Agarwal <ad.agarwal@samsung.com>
 Aditya Bhargava <heuristicist@gmail.com>
@@ -47,18 +51,20 @@
 Ahmet Emir Ercin <ahmetemiremir@gmail.com>
 Aidarbek Suleimenov <suleimenov.aidarbek@gmail.com>
 Aiden Grossman <aidengrossmanpso@gmail.com>
+Airing Deng <airingdeng@gmail.com>
 Ajay Berwal <a.berwal@samsung.com>
 Ajay Berwal <ajay.berwal@samsung.com>
 Ajay Sharma <ajay.sh@samsung.com>
 Ajith Kumar V <ajith.v@samsung.com>
 Akash Yadav <akash1.yadav@samsung.com>
+Akihiko Odaki <akihiko.odaki@gmail.com>
 Akos Kiss <akiss@inf.u-szeged.hu>
+Akpokwaye Mudiaga <mudiaga.akpokwaye@gitstart.dev>
 Aku Kotkavuo <a.kotkavuo@partner.samsung.com>
 Aldo Culquicondor <alculquicondor@gmail.com>
 Alec Petridis <alecthechop@gmail.com>
 Aleksandar Stojiljkovic <aleksandar.stojiljkovic@intel.com>
 Aleksei Gurianov <gurianov@gmail.com>
-Aleksey Khoroshilov <akhoroshilov@brave.com>
 Alesandro Ortiz <alesandro@alesandroortiz.com>
 Alessandro Astone <ales.astone@gmail.com>
 Alex Chronopoulos <achronop@gmail.com>
@@ -83,6 +89,7 @@
 Alexey Kuzmin <alex.s.kuzmin@gmail.com>
 Alexey Kuznetsov <saturas2000@gmail.com>
 Alexey Terentiev <alexeyter@gmail.com>
+Alexia Bojian <bojianalexia4@gmail.com>
 Alexis Brenon <brenon.alexis@gmail.com>
 Alexis La Goutte <alexis.lagoutte@gmail.com>
 Alexis Menard <alexis.menard@intel.com>
@@ -93,7 +100,9 @@
 Alvaro Silva <alvaro.fagner@gmail.com>
 Ambareesh Balaji <ambareeshbalaji@gmail.com>
 Ambarish Rapte <ambarish.r@samsung.com>
+Ameen Basha <ameenbasha111@gmail.com>
 Amey Jahagirdar <jahagird@amazon.com>
+Amit P <ponnan2112@gmail.com>
 Amit Paul <a.paul@samsung.com>
 Amit Sarkar <amit.srkr@samsung.com>
 Amogh Bihani <amogh.bihani@samsung.com>
@@ -113,6 +122,7 @@
 Andrei Borza <andrei.borza@gmail.com>
 Andrei Parvu <andrei.prv@gmail.com>
 Andrei Parvu <parvu@adobe.com>
+Andrei Volykhin <andrei.volykhin@gmail.com>
 Andres Salomon <dilinger@queued.net>
 Andreu Botella <andreu@andreubotella.com>
 Andrew Boyarshin <andrew.boyarshin@gmail.com>
@@ -124,9 +134,11 @@
 Andrew Nicols <andrewrn@gmail.com>
 Andrew Tulloch <andrew@tullo.ch>
 Andriy Rysin <arysin@gmail.com>
+Ane Diaz de Tuesta <anediaz@gmail.com>
 Anish Patankar <anish.p@samsung.com>
 Ankit Kiran <sahuankit453@gmail.com>
 Ankit Kumar <ankit2.kumar@samsung.com>
+Ankit Singh <ankit.4@samsung.com>
 Ankur Verma <ankur1.verma@samsung.com>
 Anna Henningsen <anna@addaleax.net>
 Anne Kao <annekao94@gmail.com>
@@ -134,11 +146,12 @@
 Anshul Jain <anshul.jain@samsung.com>
 Anssi Hannula <anssi.hannula@iki.fi>
 Anthony Halliday <anth.halliday12@gmail.com>
-Anton Bershanskiy <8knots@protonmail.com>
+Anton Bershanskyi <bershanskyi@gmail.com>
 Anton Obzhirov <a.obzhirov@samsung.com>
 Antonin Hildebrand <antonin.hildebrand@gmail.com>
 Antonio Gomes <a1.gomes@sisa.samsung.com>
 Anuj Kumar Sharma <anujk.sharma@samsung.com>
+Anukul Chand <anukul.chand@samsung.com>
 Anže Lešnik <anze@lesnik.cc>
 Ao Hui <aohui.wan@gmail.com>
 Ao Sun <ntusunao@gmail.com>
@@ -150,6 +163,7 @@
 Arnaud Coomans <hello@acoomans.com>
 Arnaud Mandy <arnaud.mandy@intel.com>
 Arnaud Renevier <a.renevier@samsung.com>
+Arnaud Renevier <arnaud@switchboard.app>
 Arpita Bahuguna <a.bah@samsung.com>
 Arthur Lussos <developer0420@gmail.com>
 Artin Lindqvist <artin.lindqvist.chromium@gmail.com>
@@ -157,11 +171,13 @@
 Arun Kulkarni <kulkarni.a@samsung.com>
 Arun Kumar <arun87.kumar@samsung.com>
 Arun Mankuzhi <arun.m@samsung.com>
+Arunachalam G <arunachalam93g@gmail.com>
 Arunoday Sarkar <a.sarkar.arun@gmail.com>
 Arunprasad Rajkumar <ararunprasad@gmail.com>
 Arunprasad Rajkumar <arurajku@cisco.com>
 Arup Barua <arup.barua@samsung.com>
 Aryan Kaushik <aryankaushik2023@gmail.com>
+Aryan P Krishnan <aryankrishnan4b@gmail.com>
 Asami Doi <d0iasm.pub@gmail.com>
 Ashish Kumar Gupta <guptaag@amazon.com>
 Ashlin Joseph <ashlin.j@samsung.com>
@@ -175,6 +191,7 @@
 Ayush Khandelwal <k.ayush@samsung.com>
 Azhar Shaikh <azhar.shaikh@intel.com>
 Balazs Kelemen <b.kelemen@samsung.com>
+Baoyu Xu <xubaoyu@bytedance.com>
 Baul Eun <baul.eun@samsung.com>
 Behara Mani Shyam Patro <behara.ms@samsung.com>
 Bem Jones-Bey <bemajaniman@gmail.com>
@@ -186,6 +203,7 @@
 Benedek Heilig <benecene@gmail.com>
 Benjamin Dupont <bedupont@cisco.com>
 Benjamin Jemlich <pcgod99@gmail.com>
+Beomsik Min <beomsikm@gmail.com>
 Bernard Cafarelli <voyageur@gentoo.org>
 Bernhard M. Wiedemann <bwiedemann@suse.de>
 Bert Belder <bertbelder@gmail.com>
@@ -194,6 +212,7 @@
 Biljith Jayan <billy.jayan@samsung.com>
 Bin Liao <bin.liao@intel.com>
 Bin Miao <bin.miao@intel.com>
+Binoy Kumar Sutradhar <binoy.s@samsung.com>
 Boaz Sender <boaz@bocoup.com>
 Bobby Powers <bobbypowers@gmail.com>
 Bradley Needham <bradley.needham@sony.com>
@@ -202,14 +221,16 @@
 Brendan Long <self@brendanlong.com>
 Brendon Tiszka <btiszka@gmail.com>
 Brett Lewis <brettlewis@brettlewis.us>
-Brian Clifton <clifton@brave.com>
 Brian Dunn <brian@theophil.us>
 Brian G. Merrell <bgmerrell@gmail.com>
 Brian Konzman, SJ <b.g.konzman@gmail.com>
 Brian Luft <brian@electroly.com>
 Brian Merrell, Novell Inc. <bgmerrell@gmail.com>
+Brian Salomon <briansalomon@gmail.com>
 Brian Yip <itsbriany@gmail.com>
+Brijesh Giri <brijeshvgiri@gmail.com>
 Brook Hong <hzgmaxwell@gmail.com>
+Bruce Dai <feng.dai@intel.com>
 Bruno Calvignac <bruno@flock.com>
 Bruno de Oliveira Abinader <brunoabinader@gmail.com>
 Bruno Pitrus <brunopitrus@hotmail.com>
@@ -229,13 +250,17 @@
 Camille Viot <viot.camille@outlook.com>
 Can Liu <peter.can.liu@gmail.com>
 Carlos Santa <carlos.santa@intel.com>
+Casey Primozic <me@ameo.link>
 Catalin Badea <badea@adobe.com>
 Cathie Chen <cathiechen@tencent.com>
 Cem Kocagil <cem.kocagil@gmail.com>
 Cezary Kułakowski <cezary.kulakowski@gmail.com>
+CGQAQ <m.jason.liu@gmail.com>
+ChaeRin Lim <hurblin@gmail.com>
 Chakshu Ahuja <chakshu.a@samsung.com>
 Chakshu Pragya <pragya.c1@samsung.com>
 Chamal De Silva <chamalsl@yahoo.com>
+Chan Park <parkchan37@gmail.com>
 Chandan Padhi <c.padhi@samsung.com>
 Chandra Shekar Vallala <brk376@motorola.com>
 Chandramouli Sanchi <cm.sanchi@samsung.com>
@@ -253,6 +278,7 @@
 Chanyong Moon <dev.chanyongmoon@gmail.com>
 Chaobin Zhang <zhchbin@gmail.com>
 Charles Vaughn <cvaughn@gmail.com>
+Chenhao Diao <diaochenhao@gmail.com>
 Cheng Zhao <zcbenz@gmail.com>
 Cheng Yu <yuzichengcode@gmail.com>
 Chenguang Shao <chenguangshao1@gmail.com>
@@ -267,25 +293,32 @@
 Chris Tserng <tserng@amazon.com>
 Chris Vasselli <clindsay@gmail.com>
 Chris Ye <hawkoyates@gmail.com>
+Christian Liebel <christianliebel@gmail.com>
 Christoph Staengle <christoph142@gmx.com>
 Christophe Dumez <ch.dumez@samsung.com>
 Christopher Dale <chrelad@gmail.com>
 Chunbo Hua <chunbo.hua@intel.com>
 Claudio DeSouza <claudiomdsjr@gmail.com>
+Clay Miller <clay@smockle.com>
 Clemens Fruhwirth <clemens@endorphin.org>
 Clement Scheelfeldt Skau <clementskau@gmail.com>
 Clinton Staley <clintstaley@gmail.com>
 Cong Zuo <zckevinzc@gmail.com>
+Connor Hewitt <connor.hewitt@gmail.com>
 Connor Pearson <cjp822@gmail.com>
 Conrad Irwin <conrad.irwin@gmail.com>
 Craig Schlenter <craig.schlenter@gmail.com>
 Csaba Osztrogonác <ossy.szeged@gmail.com>
 Cynthia Revström <me@cynthia.re>
 Daegyu Lee <na7jun8gi@gmail.com>
+Daeyoon Choi <yoonda5898@gmail.com>
 Dai Chunyang <chunyang.dai@intel.com>
 Daiwei Li <daiweili@suitabletech.com>
 Damien Marié <damien@dam.io>
+Damitha Gunawardena <damitha@canva.com>
 Dan McCombs <overridex@gmail.com>
+Daniel Adams <msub2official@gmail.com>
+Daniel Bertalan <dani@danielbertalan.dev>
 Daniel Bevenius <daniel.bevenius@gmail.com>
 Daniel Bomar <dbdaniel42@gmail.com>
 Daniel Carvalho Liedke <dliedke@gmail.com>
@@ -297,9 +330,11 @@
 Daniel Nishi <dhnishi@gmail.com>
 Daniel Platz <daplatz@googlemail.com>
 Daniel Playfair Cal <daniel.playfair.cal@gmail.com>
+Daniel Richard G. <iskunk@gmail.com>
 Daniel Shaulov <dshaulov@ptc.com>
 Daniel Trebbien <dtrebbien@gmail.com>
 Daniel Waxweiler <daniel.waxweiler@gmail.com>
+Daniel Zhao <zhaodani@amazon.com>
 Dániel Bátyai <dbatyai@inf.u-szeged.hu>
 Dániel Vince <vinced@inf.u-szeged.hu>
 Daniil Suvorov <severecloud@gmail.com>
@@ -307,13 +342,16 @@
 Danylo Boiko <danielboyko02@gmail.com>
 Daoming Qiu <daoming.qiu@intel.com>
 Darik Harter <darik.harter@gmail.com>
+Darryl Pogue <darryl@dpogue.ca>
 Darshan Sen <raisinten@gmail.com>
 Darshini KN <kn.darshini@samsung.com>
 Dave Vandyke <kzar@kzar.co.uk>
 David Benjamin <davidben@mit.edu>
 David Brown <develop.david.brown@gmail.com>
+David Cernoch <dcernoch@uplandsoftware.com>
 David Davidovic <david@davidovic.io>
 David Erceg <erceg.david@gmail.com>
+David Faden <dfaden@gmail.com>
 David Fox <david@davidjfox.com>
 David Futcher <david.mike.futcher@gmail.com>
 David Jin <davidjin@amazon.com>
@@ -322,6 +360,7 @@
 David Manouchehri <david@davidmanouchehri.com>
 David McAllister <mcdavid@amazon.com>
 David Michael Barr <david.barr@samsung.com>
+David Redondo <kde@david-redondo.de>
 David Sanders <dsanders11@ucsbalum.com>
 David Spellman <dspell@amazon.com>
 David Valachovic <adenflorian@gmail.com>
@@ -329,6 +368,7 @@
 Dean Leitersdorf <dean.leitersdorf@gmail.com>
 Debadree Chatterjee <debadree333@gmail.com>
 Debashish Samantaray <d.samantaray@samsung.com>
+Debin Zhang <debinzhang3@gmail.com>
 Debug Wang <debugwang@tencent.com>
 Deep Shah <deep.shah@samsung.com>
 Deepak Dilip Borade <deepak.db@samsung.com>
@@ -336,6 +376,7 @@
 Deepak Mohan <hop2deep@gmail.com>
 Deepak Sharma <deepak.sharma@amd.com>
 Deepak Singla <deepak.s@samsung.com>
+Deniz Eren Evrendilek <deniz.evrendilek@shift.com>
 Deniz Eren Evrendilek <devrendilek@gmail.com>
 Deokjin Kim <deokjin81.kim@samsung.com>
 Deomid rojer Ryabkov <rojer9@gmail.com>
@@ -348,14 +389,17 @@
 Diego Fernández Santos <agujaydedal@gmail.com>
 Diego Ferreiro Val <elfogris@gmail.com>
 Dillon Sellars <dill.sellars@gmail.com>
+Dingming Liu <liudingming@bytedance.com>
 Divya Bansal <divya.bansal@samsung.com>
 Dmitry Shachnev <mitya57@gmail.com>
 Dmitry Sokolov <dimanne@gmail.com>
+Dominic Elm <elmdominic@gmx.net>
 Dominic Farolino <domfarolino@gmail.com>
 Dominic Jodoin <dominic.jodoin@gmail.com>
 Dominik Röttsches <dominik.rottsches@intel.com>
 Dominik Schütz <do.sch.dev@gmail.com>
 Don Woodward <woodward@adobe.com>
+Donghan Park <vkfkdtor00@gmail.com>
 Donghee Na <corona10@gmail.com>
 Dong-hee Na <donghee.na92@gmail.com>
 Dongie Agnir <dongie.agnir@gmail.com>
@@ -365,12 +409,14 @@
 Dongwoo Joshua Im <dw.im@samsung.com>
 Dongyu Lin <l2d4y3@gmail.com>
 Donna Wu <donna.wu@intel.com>
+Douglas Browne <douglas.browne123@gmail.com>
 Douglas F. Turner <doug.turner@gmail.com>
 Drew Blaisdell <drew.blaisdell@gmail.com>
 Dushyant Kant Sharma <dush.sharma@samsung.com>
 Dustin Doloff <doloffd@amazon.com>
 Ebrahim Byagowi <ebrahim@gnu.org>
 Ebrahim Byagowi <ebraminio@gmail.com>
+Eden Wang <nedenwang@gmail.com>
 Eden Wang <nedenwang@tencent.com>
 Eduardo Lima (Etrunko) <eblima@gmail.com>
 Eduardo Lima (Etrunko) <eduardo.lima@intel.com>
@@ -383,17 +429,20 @@
 Ehsan Akhgari <ehsan.akhgari@gmail.com>
 Ehsan Akhgari <ehsan@mightyapp.com>
 Elan Ruusamäe <elan.ruusamae@gmail.com>
+Eldar Rello <eldar.rello@gmail.com>
 Ely Ronnen <elyronnen@gmail.com>
 Emil Suleymanov <emil@esnx.xyz>
 Ergun Erdogmus <erdogmusergun@gmail.com>
 Eric Ahn <byungwook.ahn@gmail.com>
 Eric Huang <ele828@gmail.com>
+Eric Long <i@hack3r.moe>
 Eric Rescorla <ekr@rtfm.com>
 Erik Hill <erikghill@gmail.com>
 Erik Kurzinger <ekurzinger@gmail.com>
 Erik Sjölund <erik.sjolund@gmail.com>
 Eriq Augustine <eriq.augustine@gmail.com>
 Ernesto Mudu <ernesto.mudu@gmail.com>
+Ethan Chen <randomgamingdev@gmail.com>
 Ethan Wong <bunnnywong@gmail.com>
 Etienne Laurin <etienne@atnnn.com>
 Eugene Kim <eugene70kim@gmail.com>
@@ -419,8 +468,8 @@
 Finbar Crago <finbar.crago@gmail.com>
 François Beaufort <beaufort.francois@gmail.com>
 François Devatine <devatine@verizonmedia.com>
+Franco Pieri <geo22therm@gmail.com>
 Francois Kritzinger <francoisk777@gmail.com>
-Francois Marier <francois@brave.com>
 Francois Rauch <leopardb@gmail.com>
 Frankie Dintino <fdintino@theatlantic.com>
 Franklin Ta <fta2012@gmail.com>
@@ -429,6 +478,7 @@
 Frédéric Wang <fred.wang@free.fr>
 Fu Junwei <junwei.fu@intel.com>
 Gabriel Campana <gabriel.campana@ledger.fr>
+Gabriel “gabldotink” <gabl@gabl.ink>
 Gabor Rapcsanyi <g.rapcsanyi@samsung.com>
 Gaetano Mendola <mendola@gmail.com>
 Gajendra N <gajendra.n@samsung.com>
@@ -462,8 +512,11 @@
 Gregory Davis <gpdavis.chromium@gmail.com>
 Grzegorz Czajkowski <g.czajkowski@samsung.com>
 Guangzhen Li <guangzhen.li@intel.com>
+Guohui Xie <vampirelightsss@gmail.com>
+Guobin Wu <wuguobin.1229@bytedance.com>
 Gurpreet Kaur <k.gurpreet@samsung.com>
 Gustav Tiger <gustav.tiger@sonymobile.com>
+Gustavo Martin <gusmartin@google.com>
 Gyuyoung Kim <gyuyoung.kim@navercorp.com>
 Gzob Qq <gzobqq@gmail.com>
 Habib Virji <habib.virji@samsung.com>
@@ -473,30 +526,40 @@
 Halley Zhao <halley.zhao@intel.com>
 Halton Huo <halton.huo@gmail.com>
 Halton Huo <halton.huo@intel.com>
+Hamed A. Elgizery <hamedashraf2004@gmail.com>
 Hans Hillen <hans.hillen@gmail.com>
+Hansel Lee <mr.hansel.lee@gmail.com>
+Hanwen Zheng <eserinc.z@gmail.com>
 Hao Li <hao.x.li@intel.com>
 Haojian Wu <hokein.wu@gmail.com>
+Haoran Tang <haoran.tang.personal@gmail.com>
 Haoxuan Zhang <zhanghaoxuan.59@bytedance.com>
 Hari Singh <hari.singh1@samsung.com>
 Harpreet Singh Khurana <harpreet.sk@samsung.com>
+Harry Chen <harpsichen@gmail.com>
+Harsh Singh <harshsinghiitism@gmail.com>
+Harshal Gupta <gupta.h@samsung.com>
 Harshikesh Kumar <harshikeshnobug@gmail.com>
 Harshit Pal <harshitp12345@gmail.com>
 Hassan Salehe Matar <hassansalehe@gmail.com>
 Hautio Kari <khautio@gmail.com>
 He Qi <heqi899@gmail.com>
+He Yang <1160386205@qq.com>
 Heejin R. Chung <heejin.r.chung@samsung.com>
 Heeyoun Lee <heeyoun.lee@samsung.com>
 Helmut Januschka <helmut@januschka.com>
 Henrique de Carvalho <decarv.henrique@gmail.com>
 Henrique Limas <henrique.ramos.limas@gmail.com>
+Henrique Valcanaia <henriqueindalencio@gmail.com>
 Henry Lim <henry@limhenry.xyz>
+Hewei Hewro <ihewro@gmail.com>
 Hikari Fujimoto <hikari.p.fujimoto@gmail.com>
 Himadri Agrawal <h2.agrawal@samsung.com>
 Himanshu Joshi <h.joshi@samsung.com>
 Himanshu Nayak <himanshu.nayak@amd.corp-partner.google.com>
 Hiroki Oshima <hiroki.oshima@gmail.com>
 Hiroyuki Matsuda <gsittyz@gmail.com>
-Ho Cheung <uioptt24@gmail.com>
+Ho Choi <hochoi8621@gmail.com>
 Hodol Han <bab6ting@gmail.com>
 Holger Kraus <kraush@amazon.com>
 Hong Zheng <hong.zheng@intel.com>
@@ -504,6 +567,8 @@
 Horia Olaru <horia.olaru@gmail.com>
 Horia Olaru <olaru@adobe.com>
 Hosung You <hosung.you@samsung.com>
+Howie Ji <imaginejhy@163.com>
+Hritwik Bhardwaj <hritwik.bh@samsung.com>
 Huai Wang <gkvjwa@gmail.com>
 Huapeng Li <huapengl@amazon.com>
 Huayong Xu <huayong.xu@samsung.com>
@@ -521,6 +586,7 @@
 Hyojeong Kim <42.4.hyojekim@gmail.com>
 Hyomin Kim <ajtwlsalsdl0@gmail.com>
 Hyomin Kim <hyoputer.kim@samsung.com>
+Hyuck Kang <kyok4ku@gmail.com>
 Hyungchan Kim <inlinechan@gmail.com>
 Hyungun Kim <khw3754@gmail.com>
 Hyungwook Lee <hyungwook.lee@navercorp.com>
@@ -543,36 +609,45 @@
 Imranur Rahman <ir.shimul@gmail.com>
 Ion Rosca <rosca@adobe.com>
 Irmak Kavasoglu <irmakkavasoglu@gmail.com>
+Isaac Khor <dev@isaackhor.com>
 Isaac Murchie <murchieisaac@gmail.com>
 Isaac Reilly <reillyi@amazon.com>
 Ivan Naydonov <samogot@gmail.com>
 Ivan Pavlotskiy <ivan.pavlotskiy@lgepartner.com>
 Ivan Sham <ivansham@amazon.com>
 Ivan Sidorov <ivansid@gmail.com>
+Jacek Fedoryński <jfedor@gmail.com>
 Jack Bates <jack@nottheoilrig.com>
+Jack Shi <flystone2020@gmail.com>
 Jackson Loeffler <j@jloeffler.com>
 Jacky Hu <flameddd@gmail.com>
 Jacob Clark <jacob.jh.clark@googlemail.com>
 Jacob Mandelson <jacob@mandelson.org>
 Jaehun Lim <ljaehun.lim@samsung.com>
+Jaehyun Chung <jaehyun.chung@amd.com>
 Jaehyun Ko <jaehyun.dev@gmail.com>
 Jaehyun Lee <j-hyun.lee@samsung.com>
 Jaekyeom Kim <btapiz@gmail.com>
 Jaemin Seo <jaemin86.seo@samsung.com>
 Jaemo Koo <jaemok@amazon.com>
+Jaemo Koo <koo2434@gmail.com>
 Jaeseok Yoon <yjaeseok@gmail.com>
+Jaesung Hyun <dev.hyunjaesung@gmail.com>
 Jaewon Choi <jaewon.james.choi@gmail.com>
 Jaewon Jung <jw.jung@navercorp.com>
 Jaeyong Bae <jdragon.bae@gmail.com>
 Jagadesh P <jagadeshjai1999@gmail.com>
 Jagdish Chourasia <jagdish.c@samsung.com>
+Jagdish Chourasia <jagdish.jnu08@gmail.com>
 Jaime Soriano Pastor <jsorianopastor@gmail.com>
+Jaimukund Bhan <bhanjaimukund@gmail.com>
 Jake Helfert <jake@helfert.us>
 Jake Hendy <me@jakehendy.com>
 Jakob Weigert <jakob.j.w@googlemail.com>
 Jakub Machacek <xtreit@gmail.com>
 James Burton <jb@0.me.uk>
 James Choi <jchoi42@pha.jhu.edu>
+James Crosby <crosby.james@gmail.com>
 James Raphael Tiovalen <jamestiotio@gmail.com>
 James Stanley <james@apphaus.co.uk>
 James Vega <vega.james@gmail.com>
@@ -591,8 +666,10 @@
 Jari Karppanen <jkarp@amazon.com>
 Jason Gronn <jasontopia03@gmail.com>
 Javayhu <javayhu@gmail.com>
+Jay Kapadia <jaykapadia389@gmail.com>
 Jay Oster <jay@kodewerx.org>
 Jay Soffian <jaysoffian@gmail.com>
+Jay Yang <sjyang1126@gmail.com>
 Jeado Ko <haibane84@gmail.com>
 Jeffrey C <jeffreyca16@gmail.com>
 Jeffrey Yeung <jeffrey.yeung@poly.com>
@@ -611,12 +688,14 @@
 Jesper van den Ende <jespertheend@gmail.com>
 Jesse Miller <jesse@jmiller.biz>
 Jesus Sanchez-Palencia <jesus.sanchez-palencia.fernandez.fil@intel.com>
+Jia Yu <yujia.1019@bytedance.com>
 Jiadong Chen <chenjiadong@huawei.com>
 Jiadong Zhu <jiadong.zhu@linaro.org>
 Jiahao Lu <lujjjh@gmail.com>
 Jiahe Zhang <jiahe.zhang@intel.com>
 Jiajia Qin <jiajia.qin@intel.com>
 Jiajie Hu <jiajie.hu@intel.com>
+Jialun Hu <jialun.hu@razer.com>
 Jianfeng Liu <liujianfeng1994@gmail.com>
 Jiangzhen Hou <houjiangzhen@360.cn>
 Jianjun Zhu <jianjun.zhu@intel.com>
@@ -626,6 +705,7 @@
 Jiawei Wang <hellojw513@gmail.com>
 Jiaxun Wei <leuisken@gmail.com>
 Jiaxun Yang <jiaxun.yang@flygoat.com>
+Jiayi Yao <zhexi.yjy@antgroup.com>
 Jidong Qin <qinjidong@qianxin.com>
 Jie Chen <jie.a.chen@intel.com>
 Jihan Chao <jihan@bluejeans.com>
@@ -633,11 +713,17 @@
 Jihoon Chung <jihoon@gmail.com>
 Jihun Brent Kim <devgrapher@gmail.com>
 Jihwan Marc Kim <bluewhale.marc@gmail.com>
+Jihye Hyun <jijinny26@gmail.com>
+Jihyeon Lee <wlgus7464@gmail.com>
+Jim Wu <lofoz.tw@gmail.com>
 Jin Yang <jin.a.yang@intel.com>
 Jincheol Jo <jincheol.jo@navercorp.com>
 Jinfeng Ma <majinfeng1@xiaomi.com>
 Jing Zhao <zhaojing7@xiaomi.com>
+Jinghua Guo <guojinghua@bytedance.com>
 Jinglong Zuo <zuojinglong@xiaomi.com>
+Jingqi Sun <jingqi.sun@hotmail.com>
+Jingqi Sun <sunjingqi47@gmail.com>
 Jingwei Liu <kingweiliu@gmail.com>
 Jingyi Wei <wjywbs@gmail.com>
 Jinho Bang <jinho.bang@samsung.com>
@@ -646,6 +732,7 @@
 Jinwoo Song <jinwoo7.song@samsung.com>
 Jinyoung Hur <hur.ims@navercorp.com>
 Jinyoung Hur <hurims@gmail.com>
+Jisu Kim <hellojs242@gmail.com>
 Jitendra Kumar Sahoo <jitendra.ks@samsung.com>
 Jitesh Pareek <j1.pareek@samsung.com>
 Joachim Bauch <jbauch@webrtc.org>
@@ -663,6 +750,7 @@
 John Yani <vanuan@gmail.com>
 John Yoo <nearbyh13@gmail.com>
 Johnson Lin <johnson.lin@intel.com>
+Jojo R <rjiejie@gmail.com>
 Jon Jensen <jonj@netflix.com>
 Jonathan Frazer <listedegarde@gmail.com>
 Jonathan Garbee <jonathan@garbee.me>
@@ -676,6 +764,7 @@
 Jongmok Kim <jongmok.kim@navercorp.com>
 Jongmok Kim <johny.kimc@gmail.com>
 Jongsoo Lee <leejongsoo@gmail.com>
+Joonas Halinen <joonashalinen@outlook.com>
 Joone Hur <joone.hur@intel.com>
 Joonghun Park <pjh0718@gmail.com>
 Jorge Villatoro <jorge@tomatocannon.com>
@@ -685,9 +774,11 @@
 Josh Triplett <josh.triplett@intel.com>
 Josh Triplett <josh@joshtriplett.org>
 Joshua Lock <joshua.lock@intel.com>
+Joshua Olaoye <joshuaolaoye46@gmail.com>
 Joshua Roesslein <jroesslein@gmail.com>
 Josué Ratelle <jorat1346@gmail.com>
 Josyula Venkat Narasimham <venkat.nj@samsung.com>
+Joy Roy <joy.roy.nil76@gmail.com>
 Joyer Huang <collger@gmail.com>
 Juan Cruz Viotti <jv@jviotti.com>
 Juan Jose Lopez Jaimez <jj.lopezjaimez@gmail.com>
@@ -710,6 +801,7 @@
 Junsang Mo <mojunsang26@gmail.com>
 Junsong Li <ljs.darkfish@gmail.com>
 Jun Wang <wangjuna@uniontech.com>
+Jun Xu <jun1.xu@intel.com>
 Jun Zeng <hjunzeng6@gmail.com>
 Justin Okamoto <justmoto@amazon.com>
 Justin Ribeiro <justin@justinribeiro.com>
@@ -717,6 +809,8 @@
 Juyoung Kim <chattank05@gmail.com>
 Jingge Yu <jinggeyu423@gmail.com>
 Jing Peiyang <jingpeiyang@eswincomputing.com>
+Jinli Wu <wujinli@bytedance.com>
+K. M. Merajul Arefin <m.arefin@samsung.com>
 Kai Jiang <jiangkai@gmail.com>
 Kai Köhne <kai.koehne@qt.io>
 Kai Uwe Broulik <kde@privat.broulik.de>
@@ -724,11 +818,14 @@
 Kalyan Kondapally <kalyan.kondapally@intel.com>
 Kamil Jiwa <kamil.jiwa@gmail.com>
 Kamil Rytarowski <krytarowski@gmail.com>
+Kanaru Sato <i.am.kanaru.sato@gmail.com>
 Kangil Han <kangil.han@samsung.com>
 Kangyuan Shu <kangyuan.shu@intel.com>
 Karan Thakkar <karanjthakkar@gmail.com>
 Karel Král <kralkareliv@gmail.com>
 Karl <karlpolicechromium@gmail.com>
+Karl Piper <karl4piper@gmail.com>
+Karl Tarvas <karl.tarvas@gmail.com>
 Kartikey Bhatt <kartikey@amazon.com>
 Kaspar Brand <googlecontrib@velox.ch>
 Kaushalendra Mishra <k.mishra@samsung.com>
@@ -744,16 +841,20 @@
 Keita Yoshimoto <y073k3@gmail.com>
 Keith Chen <keitchen@amazon.com>
 Keith Cirkel <chromium@keithcirkel.co.uk>
+Kelsen Liu <kelsenliu21@gmail.com>
 Kenneth Rohde Christiansen <kenneth.r.christiansen@intel.com>
 Kenneth Strickland <ken.strickland@gmail.com>
 Kenneth Zhou <knthzh@gmail.com>
 Kenny Levinsen <kl@kl.wtf>
 Keonho Kim <keonho07.kim@samsung.com>
+Kerollos Emad <kerollos.em@gmail.com>
 Ketan Atri <ketan.atri@samsung.com>
 Ketan Goyal <ketan.goyal@samsung.com>
 Kevin Gibbons <bakkot@gmail.com>
 Kevin Lee Helpingstine <sig11@reprehensible.net>
 Kevin M. McCormick <mckev@amazon.com>
+Kexy Biscuit <kexybiscuit@aosc.io>
+Kexy Biscuit <kexybiscuit@gmail.com>
 Keyou <qqkillyou@gmail.com>
 Khasim Syed Mohammed <khasim.mohammed@linaro.org>
 Khem Raj <raj.khem@gmail.com>
@@ -768,6 +869,9 @@
 Klemen Forstnerič <klemen.forstneric@gmail.com>
 Kodam Nagaraju <k2.nagaraju@samsung.com>
 Konrad Dzwinel <kdzwinel@gmail.com>
+Koushik Kumar Bug <koushikbug123@gmail.com>
+Kousuke Takaki <yoseio@brainoid.dev>
+Kovacs Zeteny <brightbulbapp@gmail.com>
 Krishna Chaitanya <krish.botta@samsung.com>
 Kristof Kosztyo <kkosztyo.u-szeged@partner.samsung.com>
 Krzysztof Czech <k.czech@samsung.com>
@@ -779,6 +883,7 @@
 Kwangho Shin <k_h.shin@samsung.com>
 Kyle Nahrgang <kpn24@drexel.edu>
 Kyle Plumadore <kyle.plumadore@amd.com>
+Kyouhei Horizumi <kyouhei.horizumi@gmail.com>
 Kyounga Ra <kyounga.ra@gmail.com>
 Kyoungdeok Kwon <kkd927@gmail.com>
 Kyung Yeol Kim <chitacan@gmail.com>
@@ -787,13 +892,16 @@
 Kyungyoung Heo <bbvch13531@gmail.com>
 Kyutae Lee <gorisanson@gmail.com>
 Lalit Chandivade <lalit.chandivade@einfochips.com>
+Lalit Rana <lalitrn44@gmail.com>
 Lam Lu <lamlu@amazon.com>
 Laszlo Gombos <l.gombos@samsung.com>
 Laszlo Radanyi <bekkra@gmail.com>
+lauren n. liberda <lauren@selfisekai.rocks>
 Lauren Yeun Kim <lauren.yeun.kim@gmail.com>
 Lauri Oherd <lauri.oherd@gmail.com>
 Lavar Askew <open.hyperion@gmail.com>
 Le Hoang Quyen <le.hoang.q@gmail.com>
+Jaewoo Lee <ljw5953@gmail.com>
 Leena Kaushik <l1.kaushik@samsung.com>
 Legend Lee <guanxian.li@intel.com>
 Leith Bade <leith@leithalweapon.geek.nz>
@@ -803,6 +911,7 @@
 Leo Wolf <jclw@ymail.com>
 Leon Han <leon.han@intel.com>
 Leung Wing Chung <lwchkg@gmail.com>
+Levi Zim <rsworktech@outlook.com>
 Li Yanbo <liyanbo.monster@bytedance.com>
 Li Yin <li.yin@intel.com>
 Lian Ruilong <lianrl@dingdao.com>
@@ -814,9 +923,13 @@
 Lin Peng <penglin22@huawei.com>
 Lingqi Chi <someway.bit@gmail.com>
 Lingyun Cai <lingyun.cai@intel.com>
+Linnan Li <lilinnan0903@gmail.com>
 Lionel Landwerlin <lionel.g.landwerlin@intel.com>
 Lisha Guo <lisha.guo@intel.com>
 Lizhi Fan <lizhi.fan@samsung.com>
+Lloyd Huang <bzkirto@gmail.com>
+Lloyd Torres <torlloyd@amazon.com>
+Loay Ghreeb <loayahmed655@gmail.com>
 Loo Rong Jie <loorongjie@gmail.com>
 Lorenzo Stoakes <lstoakes@gmail.com>
 Lu Guanqun <guanqun.lu@gmail.com>
@@ -835,8 +948,10 @@
 Luke Zarko <lukezarko@gmail.com>
 Luoxi Pan <l.panpax@gmail.com>
 Lu Yahan <yahan@iscas.ac.cn>
+Lyra Rebane <rebane2001@gmail.com>
 Ma Aiguo <imaiguo@gmail.com>
 Maarten Lankhorst <m.b.lankhorst@gmail.com>
+Maciej Czarnecki <mcczarny@gmail.com>
 Maciej Pawlowski <m.pawlowski@eyeo.com>
 Magnus Danielsson <fuzzac@gmail.com>
 Mahesh Kulkarni <mahesh.kk@samsung.com>
@@ -848,6 +963,7 @@
 Mallikarjuna Rao V <vm.arjun@samsung.com>
 Manish Chhajer <chhajer.m@samsung.com>
 Manish Jethani <m.jethani@eyeo.com>
+Manjunath Babu <10manju@gmail.com>
 Manojkumar Bhosale <manojkumar.bhosale@imgtec.com>
 Manuel Braun <thembrown@gmail.com>
 Manuel Lagana <manuel.lagana.dev@gmail.com>
@@ -855,6 +971,7 @@
 Mao Yujie <maojie0924@gmail.com>
 Mao Yujie <yujie.mao@intel.com>
 Marc des Garets <marc.desgarets@googlemail.com>
+Marcello Balduccini <marcello.balduccini@gmail.com>
 Marcio Caroso <msscaroso@gmail.com>
 Marcin Wiacek <marcin@mwiacek.com>
 Marco Monaco <marco.monaco@ocado.com>
@@ -876,6 +993,7 @@
 Martin Rogalla <martin@martinrogalla.com>
 Martina Kollarova <martina.kollarova@intel.com>
 Martino Fontana <tinozzo123@gmail.com>
+Marvin Giessing <marvin.giessing@gmail.com>
 Masahiro Yado <yado.masa@gmail.com>
 Masaru Nishida <msr.i386@gmail.com>
 Masayuki Wakizaka <mwakizaka0108@gmail.com>
@@ -885,6 +1003,8 @@
 Mathieu Meisser <mmeisser@logitech.com>
 Matt Arpidone <mma.public@gmail.com>
 Matt Fysh <mattfysh@gmail.com>
+Matt Harding <majaharding@gmail.com>
+Matt Jolly <kangie@gentoo.org>
 Matt Strum <mstrum@amazon.com>
 Matt Zeunert <matt@mostlystatic.com>
 Matthew "strager" Glazar <strager.nds@gmail.com>
@@ -897,8 +1017,9 @@
 Matthias Reitinger <reimarvin@gmail.com>
 Matthieu Rigolot <matthieu.rigolot@gmail.com>
 Matthieu Vlad Hauglustaine <matt.hauglustaine@gmail.com>
+Mattias Buelens <mattias.buelens@gmail.com>
+Maurice Dauer <layton.cscg@gmail.com>
 Max Coplan <mchcopl@gmail.com>
-Max Karolinskiy <max@brave.com>
 Max Perepelitsyn <pph34r@gmail.com>
 Max Schmitt <max@schmitt.mx>
 Max Vujovic <mvujovic@adobe.com>
@@ -907,19 +1028,26 @@
 Mc Zeng <zengmcong@gmail.com>
 Md Abdullah Al Alamin <a.alamin.cse@gmail.com>
 Md. Hasanur Rashid <hasanur.r@samsung.com>
+Md Hasibul Hasan <hasibulhasan873@gmail.com>
+Md Hasibul Hasan <hasibul.h@samsung.com>
 Md Jobed Hossain <jobed.h@samsung.com>
 Md Raiyan bin Sayeed <mrbsayee@uwaterloo.ca>
 Md. Sadiqul Amin <sadiqul.amin@samsung.com>
 Md Sami Uddin <md.sami@samsung.com>
+Mego Tan <tannal2409@gmail.com>
+Merajul Arefin <merajularefin@gmail.com>
 Micha Hanselmann <micha.hanselmann@gmail.com>
+Michael Chan <mzchan@dolby.com>
 Michael Cirone <mikecirone@gmail.com>
 Michael Constant <mconst@gmail.com>
 Michael Forney <mforney@mforney.org>
 Michael Gilbert <floppymaster@gmail.com>
+Michael Herrmann <michael@herrmann.io>
 Michael Kolomeytsev <michael.kolomeytsev@gmail.com>
 Michael Lopez <lopes92290@gmail.com>
 Michael Morrison <codebythepound@gmail.com>
 Michael Müller <michael@fds-team.de>
+Michael Reed <mike@reedtribe.org>
 Michael Schechter <mike.schechter@gmail.com>
 Michael Smith <sideshowbarker@gmail.com>
 Michael Weiss <dev.primeos@gmail.com>
@@ -936,14 +1064,20 @@
 Milton Chiang <milton.chiang@mediatek.com>
 Milutin Smiljanic <msmiljanic.gm@gmail.com>
 Minchul Kang <tegongkang@gmail.com>
+Ming Lei <minggeorgelei@gmail.com>
 Mingeun Park <mindal99546@gmail.com>
 Minggang Wang <minggang.wang@intel.com>
 Mingmin Xie <melvinxie@gmail.com>
 Mingming Xu <mingming1.xu@intel.com>
+Mingtao Zhou <mingtaoxt@gmail.com>
+Mingyue Ji <myandyji@gmail.com>
 Minjeong Kim <deoxyribonucleicacid150@gmail.com>
 Minjeong Lee <apenr1234@gmail.com>
 Minseok Koo <kei98301@gmail.com>
+Minseong Kim <jja08111@gmail.com>
+Minseop Choi <minsubb13@gmail.com>
 Minsoo Max Koo <msu.koo@samsung.com>
+Minsung Jin <m4ushold@gmail.com>
 Miran Karic <miran.karic@imgtec.com>
 Mirela Budaes <mbudaes@adobe.com>
 Mirela Budaes <mbudaes@gmail.com>
@@ -951,15 +1085,22 @@
 Miyoung Shin <myid.shin@navercorp.com>
 Mohamed I. Hammad <ibraaaa@gmail.com>
 Mohamed Mansour <m0.interactive@gmail.com>
+Mohamed Hany Youns <mohamedhyouns@gmail.com>
 Mohammad Azam <m.azam@samsung.com>
+MohammadSabri <mohammad.kh.sabri@exalt.ps>
+Mohammed Ashraf <mohammedashraf4599@gmail.com>
 Mohammed Wajahat Ali Siddiqui <wajahat.s@samsung.com>
 Mohan Reddy <mohan.reddy@samsung.com>
 Mohit Bhalla <bhallam@amazon.com>
+Mohraiel Matta <mohraielmatta@gmail.com>
 Moiseanu Rares-Marian <moiseanurares@gmail.com>
 Momoka Yamamoto <momoka.my6@gmail.com>
 Momoko Hattori <momohatt10@gmail.com>
+Mostafa Aboalkasim <mostafa.aboalkasim.offical@gmail.com>
 Mostafa Sedaghat joo <mostafa.sedaghat@gmail.com>
 Mrunal Kapade <mrunal.kapade@intel.com>
+Muhammad Mahad <mahadtxt@gmail.com>
+Muhammad Saqlain <2mesaqlain@gmail.com>
 Munira Tursunova <moonira@google.com>
 Myeongjin Cho <myeongjin.cho@navercorp.com>
 Myles C. Maxfield <mymax@amazon.com>
@@ -968,10 +1109,13 @@
 Nagarajan Narayanan <nagarajan.n@samsung.com>
 Nagarjuna Atluri <nagarjuna.a@samsung.com>
 Naiem Shaik <naiem.shaik@gmail.com>
+Nakuru Wubni <nakuru.wubni@gitstart.dev>
 Naman Kumar Narula <namankumarnarula@gmail.com>
 Naman Yadav <naman.yadav@samsung.com>
+Nancy Tillery <hedonistsmith@gmail.com>
 Naoki Takano <takano.naoki@gmail.com>
 Naoto Ono <onoto1998@gmail.com>
+Naresh Pratap Singh <naresh.singh@samsung.com>
 Nathan Mitchell <nathaniel.v.mitchell@gmail.com>
 Naveen Bobbili <naveenbobbili@motorola.com>
 Naveen Bobbili <qghc36@motorola.com>
@@ -986,7 +1130,9 @@
 Neehit Goyal <neehit.goyal@samsung.com>
 Nidhi Jaju <nidhijaju127@gmail.com>
 Niek van der Maas <mail@niekvandermaas.nl>
+Nik Pavlov <nikita.pavlov.dev@gmail.com>
 Nikhil Bansal <n.bansal@samsung.com>
+Nikhil Meena <iakhilmeena@gmail.com>
 Nikhil Sahni <nikhil.sahni@samsung.com>
 Nikita Ofitserov <himikof@gmail.com>
 Niklas Hambüchen <mail@nh2.me>
@@ -995,23 +1141,29 @@
 Nils Schneider <nils.schneider@gmail.com>
 Nils Schneider <nils@nilsschneider.net>
 Ningxin Hu <ningxin.hu@intel.com>
+Nishaanth Shriram M S <nishaanthshriramms@gmail.com>
 Nitish Mehrotra <nitish.m@samsung.com>
 Nivedan Sharma <ni.sharma@samsung.com>
 Noam Rosenthal <noam.j.rosenthal@gmail.com>
 Noj Vek <nojvek@gmail.com>
 Nolan Cao <nolan.robin.cao@gmail.com>
+Nourhan Hasan <nourhan.m.hasan@gmail.com>
 Oleksii Kadurin <ovkadurin@gmail.com>
 Oliver Dunk <oliver@oliverdunk.com>
 Olivier Tilloy <olivier+chromium@tilloy.net>
 Olli Raula (Old name Olli Syrjälä) <olli.raula@intel.com>
+Omar Emara <mail@OmarEmara.dev>
 Omar Sandoval <osandov@osandov.com>
+Omar Shawky <omarmshawky11@gmail.com>
 Orko Garai <orko.garai@gmail.com>
 Owen Shaw <owenpshaw@gmail.com>
 Owen Yuwono <owenyuwono@gmail.com>
 Palash Verma <palashverma47@gmail.com>
 Pan Deng <pan.deng@intel.com>
 Parag Radke <nrqv63@motorola.com>
+Paras Awasthi <awasthiparas6@gmail.com>
 Paritosh Kumar <paritosh.in@samsung.com>
+Pasquale Riello <pas.riello@gmail.com>
 Patrasciuc Sorin Cristian <cristian.patrasciuc@gmail.com>
 Patricija Cerkaite <cer.patricija@gmail.com>
 Patrick Chan <chanpatorikku@gmail.com>
@@ -1030,6 +1182,7 @@
 Pavan Kumar Emani <pavan.e@samsung.com>
 Pavel Golikov <paullo612@ya.ru>
 Pavel Ivanov <paivanof@gmail.com>
+Pawan Udassi <pawanudassi@hotmail.com>
 Pawel Forysiuk <p.forysiuk@samsung.com>
 Paweł Hajdan jr <phajdan.jr@gmail.com>
 Paweł Stanek <pawel@gener8ads.com>
@@ -1042,6 +1195,7 @@
 Peng Zhou <zhoupeng.1996@bytedance.com>
 Peng-Yu Chen <pengyu@libstarrify.so>
 Pei Wang <wangpei@uniontech.com>
+Perry Wang <perryuwang@gmail.com>
 Peter Bright <drpizza@quiscalusmexicanus.org>
 Peter Brophy <pbrophy@adobe.com>
 Peter Collingbourne <peter@pcc.me.uk>
@@ -1064,7 +1218,6 @@
 Prakhar Shrivastav <p.shri@samsung.com>
 Pramod Begur Srinath <pramod.bs@samsung.com>
 Pranay Kumar <pranay.kumar@samsung.com>
-Pranjal Jumde <pranjal@brave.com>
 Prashant Hiremath <prashhir@cisco.com>
 Prashant Nevase <prashant.n@samsung.com>
 Prashant Patil <prashant.patil@imgtec.com>
@@ -1072,6 +1225,7 @@
 Praveen Akkiraju <praveen.anp@samsung.com>
 Preeti Nayak <preeti.nayak@samsung.com>
 Pritam Nikam <pritam.nikam@samsung.com>
+Psychpsyo <psychpsyo@gmail.com>
 Puttaraju R <puttaraju.r@samsung.com>
 Punith Nayak <npunith125@gmail.com>
 Qi Tiezheng <qitiezheng@360.cn>
@@ -1148,15 +1302,19 @@
 Rufus Hamade <rufus.hamade@imgtec.com>
 Ruiyi Luo <luoruiyi2008@gmail.com>
 Rulong Chen <rulong.crl@alibaba-inc.com>
+Rulong Chen(陈汝龙) <rulongchen@live.com>
 Russell Davis <russell.davis@gmail.com>
 Ryan Ackley <ryanackley@gmail.com>
 Ryan Gonzalez <rymg19@gmail.com>
+Ryan Manuel <rfmanuel@gmail.com>
 Ryan Norton <rnorton10@gmail.com>
 Ryan Sleevi <ryan-chromium-dev@sleevi.com>
 Ryan Yoakum <ryoakum@skobalt.com>
+Ryan Huen <ryanhuenprivate@gmail.com>
 Rye Zhang <ryezhang@tencent.com>
 Ryo Ogawa <negibokken@gmail.com>
 Ryuan Choi <ryuan.choi@samsung.com>
+Sahil Khan <sahilkhanvns1228@gmail.com>
 Saikrishna Arcot <saiarcot895@gmail.com>
 Sajal Khandelwal <skhandelwa22@bloomberg.net>
 Sajeesh Sidharthan <sajeesh.sidharthan@amd.corp-partner.google.com>
@@ -1166,7 +1324,10 @@
 Sam James <sam@gentoo.org>
 Sam Larison <qufighter@gmail.com>
 Sam McDonald <sam@sammcd.com>
+Saming Lin <santoin351@gmail.com>
 Samuel Attard <samuel.r.attard@gmail.com>
+Samuel Maddock <samuelmaddock@electronjs.org>
+Sanfeng Liao <sanfengliao@gmail.com>
 Sanggi Hong <sanggi.hong11@gmail.com>
 Sanghee Lee <sanghee.lee1992@gmail.com>
 Sangheon Kim <sangheon77.kim@samsung.com>
@@ -1189,6 +1350,7 @@
 Sathish Kuppuswamy <sathish.kuppuswamy@intel.com>
 Satoshi Matsuzaki <satoshi.matsuzaki@gmail.com>
 Satyajit Sahu <satyajit.sahu@amd.com>
+Satvic Dhawan <satvicdhawan14@gmail.com>
 Sayan Nayak <sayan.nayak@samsung.com>
 Sayan Sivakumaran <sivakusayan@gmail.com>
 Scott D Phillips <scott.d.phillips@intel.com>
@@ -1196,14 +1358,17 @@
 Sean DuBois <seaduboi@amazon.com>
 Sebastian Amend <sebastian.amend@googlemail.com>
 Sebastian Krzyszkowiak <dos@dosowisko.net>
+Sebastian Markbåge <sebastian@calyptus.eu>
 Sebastjan Raspor <sebastjan.raspor1@gmail.com>
 Seo Sanghyeon <sanxiyn@gmail.com>
 Seokju Kwon <seokju.kwon@gmail.com>
 Seokho Song <0xdevssh@gmail.com>
 SeongTae Jeong <ferendevelop.gl@gmail.com>
+Sergei Poletaev <spylogsster@gmail.com>
 Sergei Romanov <rsv.981@gmail.com>
 Sergey Romanov <svromanov@sberdevices.ru>
 Sergey Kipet <sergey.kipet@gmail.com>
+Sergey Markelov <sergionso@gmail.com>
 Sergey Putilin <p.sergey@samsung.com>
 Sergey Shekyan <shekyan@gmail.com>
 Sergey Talantov <sergey.talantov@gmail.com>
@@ -1214,11 +1379,13 @@
 Seshadri Mahalingam <seshadri.mahalingam@gmail.com>
 Seungkyu Lee <zx6658@gmail.com>
 Sevan Janiyan <venture37@geeklan.co.uk>
+Shaheen Fazim <fazim.pentester@gmail.com>
 Shahriar Rostami <shahriar.rostami@gmail.com>
 Shail Singhal <shail.s@samsung.com>
 Shane Hansen <shanemhansen@gmail.com>
 ShankarGanesh K <blr.bmlab@gmail.com>
 Shanmuga Pandi M <shanmuga.m@samsung.com>
+Shanxing Mei <shanxing.mei@intel.com>
 Shaobo Yan <shaobo.yan@intel.com>
 Shaotang Zhu <zhushaotang@uniontech.com>
 Shashi Kumar <sk.kumar@samsung.com>
@@ -1251,10 +1418,12 @@
 Sida Zhu <zhusida@bytedance.com>
 Siddharth Bagai <b.siddharth@samsung.com>
 Siddharth Shankar <funkysidd@gmail.com>
+Siddhartha Barman Joy <siddhartha.j@samsung.com>
 Simeon Kuran <simeon.kuran@gmail.com>
 Simon Arlott <simon.arlott@gmail.com>
 Simon Cadman <simon@cadman.uk>
 Simon Jackson <simon.jackson@sonocent.com>
+Simon Knott <info@simonknott.de>
 Simon La Macchia <smacchia@amazon.com>
 Siva Kumar Gunturi <siva.gunturi@samsung.com>
 Slava Aseev <nullptrnine@gmail.com>
@@ -1265,14 +1434,17 @@
 Song Qinglin <songql@dingdao.com>
 Song Qinglin <songqinglin@gmail.com>
 Song YeWen <ffmpeg@gmail.com>
+Sonu Thomas <sonu.thomas@amd.com>
 Sooho Park <sooho1000@gmail.com>
 Soojung Choi <crystal2840@gmail.com>
 Soorya R <soorya.r@samsung.com>
 Soren Dreijer <dreijerbit@gmail.com>
+Spencer Wilson <spencer@spencerwilson.org>
 Sreerenj Balachandran <sreerenj.balachandran@intel.com>
 Srirama Chandra Sekhar Mogali <srirama.m@samsung.com>
 Stacy Kim <stacy.kim@ucla.edu>
 Staphany Park <stapark008@gmail.com>
+Steffen Hau <steffen@hauihau.de>
 Stephan Hartmann <stha09@googlemail.com>
 Stephen Searles <stephen.searles@gmail.com>
 Stephen Sigwart <ssigwart@gmail.com>
@@ -1287,11 +1459,14 @@
 Sudarshan Parthasarathy <sudarshan.p@samsung.com>
 Sujae Jo <sujae33.jo@gmail.com>
 Sujith S S <sujiths.s@samsung.com>
+Sukyung Byun <dev.suu3@gmail.com>
 Sumaid Syed <sumaidsyed@gmail.com>
 Sunchang Li <johnstonli@tencent.com>
 Sundoo Kim <nerdooit@gmail.com>
 Sundoo Kim <0xd00d00b@gmail.com>
 Suneel Kota <suneel.kota@samsung.com>
+Sung Lee <sung.lee@amd.com>
+Sungboo Park <seongbooopark@gmail.com>
 Sungguk Lim <limasdf@gmail.com>
 Sunghyeok Kang <sh0528.kang@samsung.com>
 Sungmann Cho <sungmann.cho@gmail.com>
@@ -1306,6 +1481,7 @@
 Suyambulingam R M <suyambu.rm@samsung.com>
 Suyash Nayan <suyashnyn1@gmail.com>
 Suyash Sengar <suyash.s@samsung.com>
+Suyeon Ji <zeesuyeon@gmail.com>
 Swarali Raut <swarali.sr@samsung.com>
 Swati Jaiswal <swa.jaiswal@samsung.com>
 Syed Wajid <syed.wajid@samsung.com>
@@ -1315,6 +1491,7 @@
 Szabolcs David <davidsz@inf.u-szeged.hu>
 Szilard Szaloki <szilardszaloki@gmail.com>
 Szymon Piechowicz <szymonpiechowicz@o2.pl>
+Taehee Yoo <ap420073@gmail.com>
 Taeheon Kim <skyrabbits1@gmail.com>
 Taeho Nam <thn7440@gmail.com>
 Taehoon Lee <taylor.hoon@gmail.com>
@@ -1331,13 +1508,16 @@
 Tanay Chowdhury <tanay.c@samsung.com>
 Tanvir Rizvi <tanvir.rizvi@samsung.com>
 Tao Wang <tao.wang.2261@gmail.com>
+Tao Xiong <taox4@illinois.edu>
 Tapu Kumar Ghose <ghose.tapu@gmail.com>
+Tau Gärtli <google@tau.garden>
 Taylor Price <trprice@gmail.com>
 Ted Kim <neot0000@gmail.com>
 Ted Vessenes <tedvessenes@gmail.com>
 Teodora Novkovic <teodora.petrovic@gmail.com>
 Thiago Farina <thiago.farina@gmail.com>
 Thiago Marcos P. Santos <thiago.santos@intel.com>
+Thibault Gagnaux <tgagnaux@gmail.com>
 Thirumurugan <thiruak1024@gmail.com>
 Thomas Butter <tbutter@gmail.com>
 Thomas Conti <tomc@amazon.com>
@@ -1345,10 +1525,12 @@
 Thomas Phillips <tphillips@snapchat.com>
 Thomas White <im.toms.inbox@gmail.com>
 Tiago Vignatti <tiago.vignatti@intel.com>
+Tianyi Zhang <me@1stprinciple.org>
 Tibor Dusnoki <tibor.dusnoki.91@gmail.com>
 Tibor Dusnoki <tdusnoki@inf.u-szeged.hu>
 Tien Hock Loh <tienhock.loh@starfivetech.com>
 Tim Ansell <mithro@mithis.com>
+Tim Barry <oregongraperoot@gmail.com>
 Tim Niederhausen <tim@rnc-ag.de>
 Tim Steiner <twsteiner@gmail.com>
 Timo Gurr <timo.gurr@gmail.com>
@@ -1364,16 +1546,21 @@
 Tomas Popela <tomas.popela@gmail.com>
 Tomasz Edward Posłuszny <tom@devpeer.net>
 Tony Shen <legendmastertony@gmail.com>
+Topi Lassila <tolassila@gmail.com>
 Torsten Kurbad <google@tk-webart.de>
 Toshihito Kikuchi <leamovret@gmail.com>
 Toshiaki Tanaka <zokutyou2@gmail.com>
+Travis Leithead <travis.leithead@gmail.com>
+Trent Taylor <trent.taylor@ulteig.com>
 Trent Willis <trentmwillis@gmail.com>
 Trevor Perrin <unsafe@trevp.net>
+Tripta Gupta <triptagupta19@gmail.com>
 Tripta Gupta <tripta.g@samsung.com>
 Tristan Fraipont <tristan.fraipont@gmail.com>
 Tudor Brindus <me@tbrindus.ca>
 Tushar Singh <tusharsinghnx@gmail.com>
 Tuukka Toivonen <tuukka.toivonen@intel.com>
+Tyler Carson <tyler@keepersecurity.com>
 Tyler Jones <tylerjdev@github.com>
 U. Artie Eoff <ullysses.a.eoff@intel.com>
 Umar Hansa <umar.hansa@gmail.com>
@@ -1381,6 +1568,7 @@
 Utzcoz <utzcoz@gmail.com>
 UwU UwU <uwu7586@gmail.com>
 Uzair Jaleel <uzair.jaleel@samsung.com>
+Uzochukwu Ochogu <uzochukwu.ochogu@gitstart.dev>
 Vadim Gorbachev <bmsdave@gmail.com>
 Vaibhav Agrawal <vaibhav1.a@samsung.com>
 Valentin Ilie <valentin.ilie@intel.com>
@@ -1394,6 +1582,7 @@
 Viatcheslav Ostapenko <sl.ostapenko@samsung.com>
 Victor Costan <costan@gmail.com>
 Victor Solonsky <victor.solonsky@gmail.com>
+Vidya Balachander <vidyakbalachander@gmail.com>
 Viet-Trung Luu <viettrungluu@gmail.com>
 Vikas Mundra <vikas.mundra@samsung.com>
 Vinay Anantharaman <vinaya@adobe.com>
@@ -1404,6 +1593,7 @@
 Vishal Lingam <vishal.reddy@samsung.com>
 Vitaliy Kharin <kvserr@gmail.com>
 Vivek Galatage <vivek.vg@samsung.com>
+Vlad Zahorodnii <vlad.zahorodnii@kde.org>
 Volker Sorge <volker.sorge@gmail.com>
 Waihung Fu <fufranci@amazon.com>
 wafuwafu13 <mariobaske@i.softbank.jp>
@@ -1411,10 +1601,13 @@
 Wang Chen <wangchen20@iscas.ac.cn>
 Wang Chen <unicornxw@gmail.com>
 Wang Weiwei <wangww@dingdao.com>
+Wang Zirui <kingzirvi@gmail.com>
 Wangyang Dai <jludwy@gmail.com>
 Wanming Lin <wanming.lin@intel.com>
 Wei Li <wei.c.li@intel.com>
+Weicong Yu <yuweicong666@gmail.com>
 Wen Fan <fanwen1@huawei.com>
+Wendi Gan <ganwendix@gmail.com>
 Wenxiang Qian <leonwxqian@gmail.com>
 WenSheng He <wensheng.he@samsung.com>
 Wesley Lancel <wesleylancel@gmail.com>
@@ -1428,6 +1621,7 @@
 Will Watts <willwatts.ww@googlemail.com>
 William Xie <william.xie@intel.com>
 Winston Chen <winston.c1@samsung.com>
+Xiao Wang <wangxiao@bytedance.com>
 Xialei Qin <qinxialei@uniontech.com>
 Xiang Long <xiang.long@intel.com>
 XiangYang <yangxiang12@huawei.com>
@@ -1451,9 +1645,13 @@
 Xunran Ding <xunran.ding@samsung.com>
 Xunran Ding <dingxunran@gmail.com>
 Yael Aharon <yael.aharon@intel.com>
+Yagiz Nizipli <yagiz@nizipli.com>
+Yaksh Bariya <yakshbari4@gmail.com>
 Yan Wang <yan0422.wang@samsung.com>
+Yaniv Yissachar <yaniv.yscr@gmail.com>
 Yang Gu <yang.gu@intel.com>
 Yang Liu <jd9668954@gmail.com>
+Yang Liu <yangliu.leo@bytedance.com>
 Yannay Hammer <yannayha@gmail.com>
 Yannic Bonenberger <yannic.bonenberger@gmail.com>
 Yarin Kaul <yarin.kaul@gmail.com>
@@ -1462,6 +1660,7 @@
 Yash Vinayak <yash.vinayak@samsung.com>
 Ye Liu <cbakgly@gmail.com>
 Yeol Park <peary2@gmail.com>
+Yeonghan Kim <soosungp33@gmail.com>
 Yeonwoo Jo <yeonwoo.jo.92@gmail.com>
 Yi Shen <yi.shen@samsung.com>
 Yi Sun <ratsunny@gmail.com>
@@ -1478,6 +1677,7 @@
 Yong Wang <ccyongwang@tencent.com>
 Yonggang Luo <luoyonggang@gmail.com>
 Yongha Lee <yongha78.lee@samsung.com>
+Yongsang Park <yongsangpark980813@gmail.com>
 Yongseok Choi <yongseok.choi@navercorp.com>
 Yongsheng Zhu <yongsheng.zhu@intel.com>
 Yoonjae Cho <yoonjae.cho92@gmail.com>
@@ -1488,15 +1688,19 @@
 Youngjin Choi <cyjin9.yc@gmail.com>
 YoungKi Hong <simon.hong81@gmail.com>
 Youngmin Yoo <youngmin.yoo@samsung.com>
+Youngmin Hong <mjdal0523@gmail.com>
 Youngsoo Choi <kenshin.choi@samsung.com>
 Youngsun Suh <zard17@gmail.com>
 Yuan-Pin Yu <yjames@uber.com>
 Yuhong Sha <yuhong.sha@samsung.com>
+YuJiang Zhou <zhouyujiang.zyj@alibaba-inc.com>
 Yuki Osaki <yuki.osaki7@gmail.com>
 Yuki Tsuchiya <Yuki.Tsuchiya@sony.com>
 Yuma Takai <tara20070827@gmail.com>
 Yumikiyo Osanai <yumios.art@gmail.com>
 Yumin Su <yuminsu.hi@gmail.com>
+Yun Jiyun <tomatoziiilll@gmail.com>
+Yun Ye <yeyun.anton@gmail.com>
 Yunchao He <yunchao.he@intel.com>
 Yupei Lin <yplam@yplam.com>
 Yupei Wang <perryuwang@tencent.com>
@@ -1508,11 +1712,13 @@
 Yuvanesh Natarajan <yuvanesh.n1@samsung.com>
 Zach Bjornson <zbbjornson@gmail.com>
 Zachary Capalbo <zach.geek@gmail.com>
+Zehan Li <synclzhhans@gmail.com>
 Zeno Albisser <zeno.albisser@digia.com>
 Zeqin Chen <talonchen@tencent.com>
 Zhanbang He <hezhanbang@gmail.com>
 Zhang Hao <zhanghao.m@bytedance.com>
 Zhang Hao <15686357310a@gmail.com>
+Zhao Qin <qzmiss@gmail.com>
 Zhaoming Jiang <zhaoming.jiang@intel.com>
 Zhaoze Zhou <zhaoze.zhou@partner.samsung.com>
 Zheda Chen <zheda.chen@intel.com>
@@ -1524,9 +1730,11 @@
 Zhibo Wang <zhibo1.wang@intel.com>
 Zhifei Fang <facetothefate@gmail.com>
 Zhiyuan Ye <zhiyuanye@tencent.com>
+Zhongwei Wang <carolwolfking@gmail.com>
 Zhou Jun <zhoujun@uniontech.com>
 Zhuoyu Qian <zhuoyu.qian@samsung.com>
 Ziran Sun <ziran.sun@samsung.com>
+Zixiang Cui <czxcanvas@gmail.com>
 Zoltan Czirkos <czirkos.zoltan@gmail.com>
 Zoltan Herczeg <zherczeg.u-szeged@partner.samsung.com>
 Zoltan Kuscsik <zoltan.kuscsik@linaro.org>
@@ -1536,6 +1744,10 @@
 方觉 (Fang Jue) <fangjue23303@gmail.com>
 迷渡 <justjavac@gmail.com>
 郑苏波 (Super Zheng) <superzheng@tencent.com>
+一丝 (Yisi) <yiorsi@gmail.com>
+林训杰 (XunJie Lin) <wick.linxunjie@gmail.com>
+郭燚 (Yi Guo) <guoyi122622@gmail.com>
+Kevin Wang <wangpengqiang@bytedance.com>
 # Please DO NOT APPEND here. See comments at the top of the file.
 # END individuals section.
 
@@ -1547,7 +1759,9 @@
 ARM Holdings <*@arm.com>
 BlackBerry Limited <*@blackberry.com>
 Bocoup <*@bocoup.com>
+Brave Software Inc. <*@brave.com>
 Canonical Limited <*@canonical.com>
+Canva Pty Ltd <*@canva.com>
 Cloudflare, Inc. <*@cloudflare.com>
 CloudMosa, Inc. <*@cloudmosa.com>
 Code Aurora Forum <*@codeaurora.org>
@@ -1564,6 +1778,7 @@
 Estimote, Inc. <*@estimote.com>
 Google Inc. <*@google.com>
 Grammarly, Inc. <*@grammarly.com>
+Here Inc. <*@here.io>
 Hewlett-Packard Development Company, L.P. <*@hp.com>
 HyperConnect Inc. <*@hpcnt.com>
 IBM Inc. <*@*.ibm.com>
@@ -1571,6 +1786,7 @@
 Igalia S.L. <*@igalia.com>
 Imagination Technologies Limited <*@imagination.corp-partner.google.com>
 Impossible Dreams Network <*@impossibledreams.net>
+imput LLC <*@imput.net>
 Intel Corporation <*@intel.com>
 Island Technology, Inc. <*@island.io>
 LG Electronics, Inc. <*@lge.com>
@@ -1579,6 +1795,7 @@
 Mail.ru Group <*@corp.mail.ru>
 Make Positive Provar Limited <*@provartesting.com>
 Mediatek <*@mediatek.com>
+Menlo Security, Inc. <*@menlosecurity.com>
 Meta Platforms, Inc. <*@fb.com>
 Meta Platforms, Inc. <*@meta.com>
 Meta Platforms, Inc. <*@oculus.com>
@@ -1593,12 +1810,14 @@
 OpenFin Inc. <*@openfin.co>
 Opera Software ASA <*@opera.com>
 Optical Tone Ltd <*@opticaltone.com>
+Palo Alto Networks, Inc. <*@paloaltonetworks.com>
 Pengutronix e.K. <*@pengutronix.de>
 Quality First Software GmbH <*@qf-software.com>
 Rakuten Kobo Inc. <*@kobo.com>
 Rakuten Kobo Inc. <*@rakuten.com>
 Red Hat Inc. <*@redhat.com>
 Semihalf <*@semihalf.com>
+S57 ApS <*@s57.io>
 Seznam.cz, a.s. <*@firma.seznam.cz>
 Slack Technologies Inc. <*@slack-corp.com>
 Spotify AB <*@spotify.com>
diff --git a/LICENSE b/LICENSE
index fdaa559..2249a28 100644
--- a/LICENSE
+++ b/LICENSE
@@ -4,22 +4,21 @@
 // modification, are permitted provided that the following conditions are
 // met:
 //
-// 1. Redistributions of source code must retain the above copyright
-//    notice, this list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright
-//    notice, this list of conditions and the following disclaimer in the
-//    documentation and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-//    contributors may be used to endorse or promote products derived from
-//    this software without specific prior written permission.
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
 //
 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
diff --git a/base/BUILD b/base/BUILD
index eda7352..447ac0e 100644
--- a/base/BUILD
+++ b/base/BUILD
@@ -1,8 +1,9 @@
+load("@rules_cc//cc:defs.bzl", "cc_library")
+
 # 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.
 load("//build_config:build_config.bzl", "build_config")
-load("@rules_cc//cc:defs.bzl", "cc_library")
 
 cc_library(
     name = "base",
@@ -10,9 +11,9 @@
         "debug/crash_logging.cc",
         "strings/string_util.cc",
         "strings/string_util_constants.cc",
+        "strings/utf_ostream_operators.cc",
         "strings/utf_string_conversion_utils.cc",
         "strings/utf_string_conversions.cc",
-        "strings/utf_ostream_operators.cc",
     ] + select({
         "//build_config:windows_x86_64": ["strings/string_util_win.cc"],
         "//conditions:default": [],
@@ -22,21 +23,18 @@
         "compiler_specific.h",
         "containers/checked_iterators.h",
         "containers/contains.h",
-        "containers/contiguous_iterator.h",
         "containers/span.h",
-        "containers/util.h",
-        "cxx20_is_constant_evaluated.h",
+        "containers/span_forward_internal.h",
         "debug/crash_logging.h",
         "debug/leak_annotations.h",
-        "functional/identity.h",
-        "functional/invoke.h",
-        "functional/not_fn.h",
         "memory/raw_ptr_exclusion.h",
+        "memory/stack_allocated.h",
         "no_destructor.h",
         "numerics/checked_math.h",
         "numerics/checked_math_impl.h",
         "numerics/clamped_math.h",
         "numerics/clamped_math_impl.h",
+        "numerics/integral_constant_like.h",
         "numerics/safe_conversions.h",
         "numerics/safe_conversions_arm_impl.h",
         "numerics/safe_conversions_impl.h",
@@ -44,21 +42,16 @@
         "numerics/safe_math_arm_impl.h",
         "numerics/safe_math_clang_gcc_impl.h",
         "numerics/safe_math_shared_impl.h",
-        "ranges/algorithm.h",
-        "ranges/functional.h",
-        "ranges/ranges.h",
         "stl_util.h",
-        "template_util.h",
-        "types/always_false.h",
-        "types/supports_ostream_operator.h",
-        "strings/string_piece.h",
+        "strings/string_number_conversions.h",
         "strings/string_util.h",
         "strings/string_util_impl_helpers.h",
         "strings/string_util_internal.h",
-        "strings/string_number_conversions.h",
-        "strings/utf_string_conversions.h",
         "strings/utf_ostream_operators.h",
         "strings/utf_string_conversion_utils.h",
+        "strings/utf_string_conversions.h",
+        "types/supports_ostream_operator.h",
+        "types/to_address.h",
         "win/win_handle_types.h",
     ] + build_config.strings_hdrs,
     copts = build_config.default_copts,
diff --git a/base/bits.h b/base/bits.h
index 6a30878..be4f926 100644
--- a/base/bits.h
+++ b/base/bits.h
@@ -10,131 +10,127 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <bit>
+#include <concepts>
 #include <type_traits>
 
 #include "polyfills/base/check.h"
-#include "base/compiler_specific.h"
-#include "build/build_config.h"
 
-namespace gurl_base {
-namespace bits {
+namespace gurl_base::bits {
 
-// Returns true iff |value| is a power of 2.
+// Bit functions in <bit> are restricted to a specific set of types of unsigned
+// integer; restrict functions in this file that are related to those in that
+// header to match for consistency.
+template <typename T>
+concept UnsignedInteger =
+    std::unsigned_integral<T> && !std::same_as<T, bool> &&
+    !std::same_as<T, char> && !std::same_as<T, char8_t> &&
+    !std::same_as<T, char16_t> && !std::same_as<T, char32_t> &&
+    !std::same_as<T, wchar_t>;
+
+// We want to migrate all users of these functions to use the unsigned type
+// versions of the functions, but until they are all moved over, create a
+// concept that captures all the types that must be supported for compatibility
+// but that we want to remove.
 //
-// TODO(pkasting): When C++20 is available, replace with std::has_single_bit().
-template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
-constexpr bool IsPowerOfTwo(T value) {
-  // From "Hacker's Delight": Section 2.1 Manipulating Rightmost Bits.
-  //
-  // Only positive integers with a single bit set are powers of two. If only one
-  // bit is set in x (e.g. 0b00000100000000) then |x-1| will have that bit set
-  // to zero and all bits to its right set to 1 (e.g. 0b00000011111111). Hence
-  // |x & (x-1)| is 0 iff x is a power of two.
-  return value > 0 && (value & (value - 1)) == 0;
+// TODO(crbug.com/40256225): Switch uses to supported functions and
+// remove.
+template <typename T>
+concept SignedIntegerDeprecatedDoNotUse =
+    std::integral<T> && !UnsignedInteger<T>;
+
+// Round down |size| to a multiple of alignment, which must be a power of two.
+template <typename T>
+  requires UnsignedInteger<T>
+inline constexpr T AlignDown(T size, T alignment) {
+  GURL_DCHECK(std::has_single_bit(alignment));
+  return size & ~(alignment - 1);
 }
 
 // Round down |size| to a multiple of alignment, which must be a power of two.
-template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
-constexpr T AlignDown(T size, T alignment) {
-  GURL_DCHECK(IsPowerOfTwo(alignment));
-  return size & ~(alignment - 1);
+// DEPRECATED; use the UnsignedInteger version.
+//
+// TODO(crbug.com/40256225): Switch uses and remove.
+template <typename T>
+inline constexpr auto AlignDownDeprecatedDoNotUse(T size, T alignment) {
+  using U = std::make_unsigned_t<T>;
+  GURL_DCHECK(std::has_single_bit(static_cast<U>(alignment)));
+  return static_cast<U>(size) & ~static_cast<U>(alignment - 1);
 }
 
 // Move |ptr| back to the previous multiple of alignment, which must be a power
 // of two. Defined for types where sizeof(T) is one byte.
-template <typename T, typename = std::enable_if_t<sizeof(T) == 1>>
+template <typename T>
+  requires(sizeof(T) == 1)
 inline T* AlignDown(T* ptr, uintptr_t alignment) {
   return reinterpret_cast<T*>(
       AlignDown(reinterpret_cast<uintptr_t>(ptr), alignment));
 }
 
 // Round up |size| to a multiple of alignment, which must be a power of two.
-template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
-constexpr T AlignUp(T size, T alignment) {
-  GURL_DCHECK(IsPowerOfTwo(alignment));
+template <typename T>
+  requires UnsignedInteger<T>
+inline constexpr T AlignUp(T size, T alignment) {
+  GURL_DCHECK(std::has_single_bit(alignment));
   return (size + alignment - 1) & ~(alignment - 1);
 }
 
+// Round up |size| to a multiple of alignment, which must be a power of two.
+// DEPRECATED; use the UnsignedInteger version.
+//
+// TODO(crbug.com/40256225): Switch uses and remove.
+template <typename T>
+  requires SignedIntegerDeprecatedDoNotUse<T>
+inline constexpr T AlignUpDeprecatedDoNotUse(T size, T alignment) {
+  using U = std::make_unsigned_t<T>;
+  GURL_DCHECK(std::has_single_bit(static_cast<U>(alignment)));
+  return static_cast<U>(size + alignment - 1) & ~static_cast<U>(alignment - 1);
+}
+
 // Advance |ptr| to the next multiple of alignment, which must be a power of
 // two. Defined for types where sizeof(T) is one byte.
-template <typename T, typename = std::enable_if_t<sizeof(T) == 1>>
+template <typename T>
+  requires(sizeof(T) == 1)
 inline T* AlignUp(T* ptr, uintptr_t alignment) {
   return reinterpret_cast<T*>(
       AlignUp(reinterpret_cast<uintptr_t>(ptr), alignment));
 }
 
-// CountLeadingZeroBits(value) returns the number of zero bits following the
-// most significant 1 bit in |value| if |value| is non-zero, otherwise it
-// returns {sizeof(T) * 8}.
-// Example: 00100010 -> 2
-//
-// CountTrailingZeroBits(value) returns the number of zero bits preceding the
-// least significant 1 bit in |value| if |value| is non-zero, otherwise it
-// returns {sizeof(T) * 8}.
-// Example: 00100010 -> 1
-//
-// C does not have an operator to do this, but fortunately the various
-// compilers have built-ins that map to fast underlying processor instructions.
-//
-// TODO(pkasting): When C++20 is available, replace with std::countl_zero() and
-// similar.
-
-// __builtin_clz has undefined behaviour for an input of 0, even though there's
-// clearly a return value that makes sense, and even though some processor clz
-// instructions have defined behaviour for 0. We could drop to raw __asm__ to
-// do better, but we'll avoid doing that unless we see proof that we need to.
-template <typename T, int bits = sizeof(T) * 8>
-ALWAYS_INLINE constexpr
-    typename std::enable_if<std::is_unsigned_v<T> && sizeof(T) <= 8, int>::type
-    CountLeadingZeroBits(T value) {
-  static_assert(bits > 0, "invalid instantiation");
-  return LIKELY(value)
-             ? bits == 64
-                   ? __builtin_clzll(static_cast<uint64_t>(value))
-                   : __builtin_clz(static_cast<uint32_t>(value)) - (32 - bits)
-             : bits;
-}
-
-template <typename T, int bits = sizeof(T) * 8>
-ALWAYS_INLINE constexpr
-    typename std::enable_if<std::is_unsigned_v<T> && sizeof(T) <= 8, int>::type
-    CountTrailingZeroBits(T value) {
-  return LIKELY(value) ? bits == 64
-                             ? __builtin_ctzll(static_cast<uint64_t>(value))
-                             : __builtin_ctz(static_cast<uint32_t>(value))
-                       : bits;
-}
-
 // Returns the integer i such as 2^i <= n < 2^(i+1).
 //
-// There is a common `BitLength` function, which returns the number of bits
-// required to represent a value. Rather than implement that function,
-// use `Log2Floor` and add 1 to the result.
+// A common use for this function is to measure the number of bits required to
+// contain a value; for that case use std::bit_width().
 //
-// TODO(pkasting): When C++20 is available, replace with std::bit_xxx().
+// A common use for this function is to take its result and use it to left-shift
+// a bit; instead of doing so, use std::bit_floor().
 constexpr int Log2Floor(uint32_t n) {
-  return 31 - CountLeadingZeroBits(n);
+  return 31 - std::countl_zero(n);
 }
 
 // Returns the integer i such as 2^(i-1) < n <= 2^i.
+//
+// A common use for this function is to measure the number of bits required to
+// contain a value; for that case use std::bit_width().
+//
+// A common use for this function is to take its result and use it to left-shift
+// a bit; instead of doing so, use std::bit_ceil().
 constexpr int Log2Ceiling(uint32_t n) {
   // When n == 0, we want the function to return -1.
   // When n == 0, (n - 1) will underflow to 0xFFFFFFFF, which is
   // why the statement below starts with (n ? 32 : -1).
-  return (n ? 32 : -1) - CountLeadingZeroBits(n - 1);
+  return (n ? 32 : -1) - std::countl_zero(n - 1);
 }
 
 // Returns a value of type T with a single bit set in the left-most position.
-// Can be used instead of manually shifting a 1 to the left.
+// Can be used instead of manually shifting a 1 to the left. Unlike the other
+// functions in this file, usable for any integral type.
 template <typename T>
+  requires std::integral<T>
 constexpr T LeftmostBit() {
-  static_assert(std::is_integral_v<T>,
-                "This function can only be used with integral types.");
   T one(1u);
   return one << (8 * sizeof(T) - 1);
 }
 
-}  // namespace bits
-}  // namespace base
+}  // namespace gurl_base::bits
 
 #endif  // BASE_BITS_H_
diff --git a/base/compiler_specific.h b/base/compiler_specific.h
index c8cb72a..728936e 100644
--- a/base/compiler_specific.h
+++ b/base/compiler_specific.h
@@ -11,166 +11,334 @@
 #error "Only clang-cl is supported on Windows, see https://crbug.com/988071"
 #endif
 
-// This is a wrapper around `__has_cpp_attribute`, which can be used to test for
-// the presence of an attribute. In case the compiler does not support this
-// macro it will simply evaluate to 0.
+// A wrapper around `__has_attribute()`, which is similar to the C++20-standard
+// `__has_cpp_attribute()`, but tests for support for `__attribute__(())`s.
+// Compilers that do not support this (e.g. MSVC) are also assumed not to
+// support `__attribute__`, so this is simply mapped to `0` there.
 //
-// References:
-// https://wg21.link/sd6#testing-for-the-presence-of-an-attribute-__has_cpp_attribute
-// https://wg21.link/cpp.cond#:__has_cpp_attribute
-#if defined(__has_cpp_attribute)
-#define HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
-#else
-#define HAS_CPP_ATTRIBUTE(x) 0
-#endif
-
-// A wrapper around `__has_attribute`, similar to HAS_CPP_ATTRIBUTE.
+// See also:
+//   https://clang.llvm.org/docs/LanguageExtensions.html#has-attribute
 #if defined(__has_attribute)
 #define HAS_ATTRIBUTE(x) __has_attribute(x)
 #else
 #define HAS_ATTRIBUTE(x) 0
 #endif
 
-// A wrapper around `__has_builtin`, similar to HAS_CPP_ATTRIBUTE.
+// A wrapper around `__has_builtin`, similar to `HAS_ATTRIBUTE()`.
+//
+// See also:
+//   https://clang.llvm.org/docs/LanguageExtensions.html#has-builtin
 #if defined(__has_builtin)
 #define HAS_BUILTIN(x) __has_builtin(x)
 #else
 #define HAS_BUILTIN(x) 0
 #endif
 
-// Annotate a function indicating it should not be inlined.
-// Use like:
-//   NOINLINE void DoStuff() { ... }
-#if defined(__clang__) && HAS_ATTRIBUTE(noinline)
+// A wrapper around `__has_feature`, similar to `HAS_ATTRIBUTE()`.
+//
+// See also:
+//   https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension
+#if defined(__has_feature)
+#define HAS_FEATURE(FEATURE) __has_feature(FEATURE)
+#else
+#define HAS_FEATURE(FEATURE) 0
+#endif
+
+// Annotates a function indicating it should not be inlined.
+//
+// You may also want `NOOPT` if your goal is to preserve a function call even
+// for the most trivial cases; see
+// https://stackoverflow.com/questions/54481855/clang-ignoring-attribute-noinline/54482070#54482070.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#noinline
+//
+// Usage:
+// ```
+//   NOINLINE void Func() {
+//     // This body will not be inlined into callers.
+//   }
+// ```
+#if __has_cpp_attribute(clang::noinline)
 #define NOINLINE [[clang::noinline]]
-#elif defined(COMPILER_GCC) && HAS_ATTRIBUTE(noinline)
-#define NOINLINE __attribute__((noinline))
-#elif defined(COMPILER_MSVC)
-#define NOINLINE __declspec(noinline)
+#elif __has_cpp_attribute(gnu::noinline)
+#define NOINLINE [[gnu::noinline]]
+#elif __has_cpp_attribute(msvc::noinline)
+#define NOINLINE [[msvc::noinline]]
 #else
 #define NOINLINE
 #endif
 
-// Annotate a function indicating it should not be optimized.
-#if defined(__clang__) && HAS_ATTRIBUTE(optnone)
+// Annotates a call site indicating that the callee should not be inlined.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#noinline
+//
+// Usage:
+// ```
+//   void Func() {
+//      // This specific call to `DoSomething` should not be inlined.
+//      NOINLINE_CALL DoSomething();
+//   }
+// ```
+#if __has_cpp_attribute(clang::noinline)
+#define NOINLINE_CALL [[clang::noinline]]
+#else
+#define NOINLINE_CALL
+#endif
+
+// Annotates a function indicating it should not be optimized.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#optnone
+//   https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-optimize-function-attribute
+//
+// Usage:
+// ```
+//   NOOPT void Func() {
+//     // This body will not be optimized.
+//   }
+// ```
+#if __has_cpp_attribute(clang::optnone)
 #define NOOPT [[clang::optnone]]
-#elif defined(COMPILER_GCC) && HAS_ATTRIBUTE(optimize)
-#define NOOPT __attribute__((optimize(0)))
+#elif __has_cpp_attribute(gnu::optimize)
+#define NOOPT [[gnu::optimize(0)]]
 #else
 #define NOOPT
 #endif
 
-#if defined(__clang__) && defined(NDEBUG) && HAS_ATTRIBUTE(always_inline)
+// Annotates a function indicating it should always be inlined.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#always-inline-force-inline
+//
+// Usage:
+// ```
+//   ALWAYS_INLINE void Func() {
+//     // This body will be inlined into callers whenever possible.
+//   }
+// ```
+//
+// Since `ALWAYS_INLINE` is performance-oriented but can hamper debugging,
+// ignore it in debug mode.
+#if defined(NDEBUG)
+#if __has_cpp_attribute(clang::always_inline)
 #define ALWAYS_INLINE [[clang::always_inline]] inline
-#elif defined(COMPILER_GCC) && defined(NDEBUG) && HAS_ATTRIBUTE(always_inline)
-#define ALWAYS_INLINE inline __attribute__((__always_inline__))
-#elif defined(COMPILER_MSVC) && defined(NDEBUG)
+#elif __has_cpp_attribute(gnu::always_inline)
+#define ALWAYS_INLINE [[gnu::always_inline]] inline
+#elif defined(COMPILER_MSVC)
 #define ALWAYS_INLINE __forceinline
-#else
+#endif
+#endif
+#if !defined(ALWAYS_INLINE)
 #define ALWAYS_INLINE inline
 #endif
 
-// Annotate a function indicating it should never be tail called. Useful to make
-// sure callers of the annotated function are never omitted from call-stacks.
-// To provide the complementary behavior (prevent the annotated function from
-// being omitted) look at NOINLINE. Also note that this doesn't prevent code
-// folding of multiple identical caller functions into a single signature. To
-// prevent code folding, see NO_CODE_FOLDING() in base/debug/alias.h.
-// Use like:
-//   NOT_TAIL_CALLED void FooBar();
-#if defined(__clang__) && HAS_ATTRIBUTE(not_tail_called)
+// Annotates a call site indicating the calee should always be inlined.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#always-inline-force-inline
+//
+// Usage:
+// ```
+//   void Func() {
+//     // This specific call will be inlined if possible.
+//     ALWAYS_INLINE_CALL DoSomething();
+//   }
+// ```
+//
+// Since `ALWAYS_INLINE_CALL` is performance-oriented but can hamper debugging,
+// ignore it in debug mode.
+#if defined(NDEBUG)
+#if __has_cpp_attribute(clang::always_inline)
+#define ALWAYS_INLINE_CALL [[clang::always_inline]]
+#endif
+#endif
+#if !defined(ALWAYS_INLINE_CALL)
+#define ALWAYS_INLINE_CALL
+#endif
+
+// Annotates a function indicating it should never be tail called. Useful to
+// make sure callers of the annotated function are never omitted from call
+// stacks. Often useful with `NOINLINE` to make sure the function itself is also
+// not omitted from call stacks. Note: this does not prevent code folding of
+// multiple identical callers into a single signature; to do that, see
+// `NO_CODE_FOLDING()` in base/debug/alias.h.
+//
+// For a caller-side version of this, see `DISABLE_TAIL_CALLS`.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#not-tail-called
+//
+// Usage:
+// ```
+//   // Calls to this function will not be tail calls.
+//   NOT_TAIL_CALLED void Func();
+// ```
+#if __has_cpp_attribute(clang::not_tail_called)
 #define NOT_TAIL_CALLED [[clang::not_tail_called]]
 #else
 #define NOT_TAIL_CALLED
 #endif
 
-// Specify memory alignment for structs, classes, etc.
-// Use like:
-//   class ALIGNAS(16) MyClass { ... }
-//   ALIGNAS(16) int array[4];
+// Annotates a return statement indicating the compiler must convert it to a
+// tail call. Can be used only on return statements, even for functions
+// returning void. Caller and callee must have the same number of arguments and
+// the argument types must be "similar". While the compiler may automatically
+// convert compatible calls to tail calls when optimizing, this annotation
+// requires it to occur if doing so is valid, and will not compile otherwise.
 //
-// In most places you can use the C++11 keyword "alignas", which is preferred.
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#musttail
 //
-// Historically, compilers had trouble mixing __attribute__((...)) syntax with
-// alignas(...) syntax. However, at least Clang is very accepting nowadays. It
-// may be that this macro can be removed entirely.
-#if defined(__clang__)
-#define ALIGNAS(byte_alignment) alignas(byte_alignment)
-#elif defined(COMPILER_MSVC)
-#define ALIGNAS(byte_alignment) __declspec(align(byte_alignment))
-#elif defined(COMPILER_GCC) && HAS_ATTRIBUTE(aligned)
-#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment)))
+// Usage:
+// ```
+//   int Func1(double);
+//   int Func2(double d) {
+//     MUSTTAIL return Func1(d + 1);  // `Func1()` will be tail-called.
+//   }
+// ```
+#if __has_cpp_attribute(clang::musttail)
+#define MUSTTAIL [[clang::musttail]]
+#else
+#define MUSTTAIL
 #endif
 
-// In case the compiler supports it NO_UNIQUE_ADDRESS evaluates to the C++20
-// attribute [[no_unique_address]]. This allows annotating data members so that
-// they need not have an address distinct from all other non-static data members
-// of its class.
+// Annotates a data member indicating it need not have an address distinct from
+// all other non-static data members of the class, and its tail padding may be
+// used for other objects' storage. This can have subtle and dangerous effects,
+// including on containing objects; use with caution.
 //
-// References:
-// * https://en.cppreference.com/w/cpp/language/attributes/no_unique_address
-// * https://wg21.link/dcl.attr.nouniqueaddr
-#if defined(COMPILER_MSVC) && HAS_CPP_ATTRIBUTE(msvc::no_unique_address)
+// See also:
+//   https://en.cppreference.com/w/cpp/language/attributes/no_unique_address
+//   https://wg21.link/dcl.attr.nouniqueaddr
+// Usage:
+// ```
+//   // In the following struct, `t` might not have a unique address from `i`,
+//   // and `t`'s tail padding (if any) may be reused by subsequent objects.
+//   struct S {
+//     int i;
+//     NO_UNIQUE_ADDRESS T t;
+//   };
+// ```
+//
 // Unfortunately MSVC ignores [[no_unique_address]] (see
 // https://devblogs.microsoft.com/cppblog/msvc-cpp20-and-the-std-cpp20-switch/#msvc-extensions-and-abi),
 // and clang-cl matches it for ABI compatibility reasons. We need to prefer
 // [[msvc::no_unique_address]] when available if we actually want any effect.
+#if __has_cpp_attribute(msvc::no_unique_address)
 #define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
-#elif HAS_CPP_ATTRIBUTE(no_unique_address)
+#elif __has_cpp_attribute(no_unique_address)
 #define NO_UNIQUE_ADDRESS [[no_unique_address]]
 #else
 #define NO_UNIQUE_ADDRESS
 #endif
 
-// Tells the compiler a function is using a printf-style format string.
-// |format_param| is the one-based index of the format string parameter;
-// |dots_param| is the one-based index of the "..." parameter.
-// For v*printf functions (which take a va_list), pass 0 for dots_param.
-// (This is undocumented but matches what the system C headers do.)
-// For member functions, the implicit this parameter counts as index 1.
-#if (defined(COMPILER_GCC) || defined(__clang__)) && HAS_ATTRIBUTE(format)
+// Annotates a function indicating it takes a `printf()`-style format string.
+// The compiler will check that the provided arguments match the type specifiers
+// in the format string. Useful to detect mismatched format strings/args.
+//
+// `format_param` is the one-based index of the format string parameter;
+// `dots_param` is the one-based index of the "..." parameter.
+// For `v*printf()` functions (which take a `va_list`), `dots_param` should be
+// 0. For member functions, the implicit `this` parameter is at index 1.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#format
+//   https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-format-function-attribute
+//
+// Usage:
+// ```
+//   PRINTF_FORMAT(1, 2)
+//   void Print(const char* format, ...);
+//   void Func() {
+//     // The following call will not compile; diagnosed as format and argument
+//     // types mismatching.
+//     Print("%s", 1);
+//   }
+// ```
+#if __has_cpp_attribute(gnu::format)
 #define PRINTF_FORMAT(format_param, dots_param) \
-  __attribute__((format(printf, format_param, dots_param)))
+  [[gnu::format(printf, format_param, dots_param)]]
 #else
 #define PRINTF_FORMAT(format_param, dots_param)
 #endif
 
-// WPRINTF_FORMAT is the same, but for wide format strings.
-// This doesn't appear to yet be implemented in any compiler.
-// See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38308 .
-#define WPRINTF_FORMAT(format_param, dots_param)
-// If available, it would look like:
-//   __attribute__((format(wprintf, format_param, dots_param)))
-
-// Sanitizers annotations.
-#if HAS_ATTRIBUTE(no_sanitize)
-#define NO_SANITIZE(what) __attribute__((no_sanitize(what)))
-#endif
-#if !defined(NO_SANITIZE)
-#define NO_SANITIZE(what)
+// Annotates a function disabling the named sanitizer within its body.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#no-sanitize
+//   https://clang.llvm.org/docs/UsersManual.html#controlling-code-generation
+//
+// Usage:
+// ```
+//   NO_SANITIZE("cfi-icall") void Func() {
+//     // CFI indirect call checks will not be performed in this body.
+//   }
+// ```
+#if __has_cpp_attribute(clang::no_sanitize)
+#define NO_SANITIZE(sanitizer) [[clang::no_sanitize(sanitizer)]]
+#else
+#define NO_SANITIZE(sanitizer)
 #endif
 
-// MemorySanitizer annotations.
-#if defined(MEMORY_SANITIZER) && !BUILDFLAG(IS_NACL)
+// Annotates a pointer and size directing MSAN to treat that memory region as
+// fully initialized. Useful for e.g. code that deliberately reads uninitialized
+// data, such as a GC scavenging root set pointers from the stack.
+//
+// See also:
+//   https://github.com/google/sanitizers/wiki/MemorySanitizer
+//
+// Usage:
+// ```
+//   T* ptr = ...;
+//   // After the next statement, MSAN will assume `ptr` points to an
+//   // initialized `T`.
+//   MSAN_UNPOISON(ptr, sizeof(T));
+// ```
+#if defined(MEMORY_SANITIZER)
 #include <sanitizer/msan_interface.h>
-
-// Mark a memory region fully initialized.
-// Use this to annotate code that deliberately reads uninitialized data, for
-// example a GC scavenging root set pointers from the stack.
 #define MSAN_UNPOISON(p, size) __msan_unpoison(p, size)
+#else
+#define MSAN_UNPOISON(p, size)
+#endif
 
-// Check a memory region for initializedness, as if it was being used here.
-// If any bits are uninitialized, crash with an MSan report.
-// Use this to sanitize data which MSan won't be able to track, e.g. before
-// passing data to another process via shared memory.
+// Annotates a pointer and size directing MSAN to check whether that memory
+// region is initialized, as if it was being read from. If any bits are
+// uninitialized, crashes with an MSAN report. Useful for e.g. sanitizing data
+// MSAN won't be able to track, such as data that is about to be passed to
+// another process via shared memory.
+//
+// See also:
+//   https://www.chromium.org/developers/testing/memorysanitizer/#debugging-msan-reports
+//
+// Usage:
+// ```
+//   T* ptr = ...;
+//   // The following line will crash at runtime in MSAN builds if `ptr` does
+//   // not point to an initialized `T`.
+//   MSAN_CHECK_MEM_IS_INITIALIZED(ptr, sizeof(T));
+// ```
+#if defined(MEMORY_SANITIZER)
 #define MSAN_CHECK_MEM_IS_INITIALIZED(p, size) \
   __msan_check_mem_is_initialized(p, size)
-#else  // MEMORY_SANITIZER
-#define MSAN_UNPOISON(p, size)
+#else
 #define MSAN_CHECK_MEM_IS_INITIALIZED(p, size)
-#endif  // MEMORY_SANITIZER
+#endif
 
-// DISABLE_CFI_PERF -- Disable Control Flow Integrity for perf reasons.
+// Annotates a function disabling Control Flow Integrity checks due to perf
+// impact.
+//
+// See also:
+//   https://clang.llvm.org/docs/ControlFlowIntegrity.html#performance
+//   https://www.chromium.org/developers/testing/control-flow-integrity/#overhead-only-tested-on-x64
+//
+// Usage:
+// ```
+//   DISABLE_CFI_PERF void Func() {
+//     // CFI checks will not be performed in this body, due to perf reasons.
+//   }
+// ```
 #if !defined(DISABLE_CFI_PERF)
 #if defined(__clang__) && defined(OFFICIAL_BUILD)
 #define DISABLE_CFI_PERF NO_SANITIZE("cfi")
@@ -179,289 +347,777 @@
 #endif
 #endif
 
-// DISABLE_CFI_ICALL -- Disable Control Flow Integrity indirect call checks.
-// Security Note: if you just need to allow calling of dlsym functions use
-// DISABLE_CFI_DLSYM.
+// Annotates a function disabling Control Flow Integrity indirect call checks.
+// NOTE: Prefer `DISABLE_CFI_DLSYM()` if you just need to allow calling of dlsym
+// functions.
+//
+// See also:
+//   https://clang.llvm.org/docs/ControlFlowIntegrity.html#available-schemes
+//   https://www.chromium.org/developers/testing/control-flow-integrity/#indirect-call-failures
+//
+// Usage:
+// ```
+//   DISABLE_CFI_ICALL void Func() {
+//     // CFI indirect call checks will not be performed in this body.
+//   }
+// ```
 #if !defined(DISABLE_CFI_ICALL)
 #if BUILDFLAG(IS_WIN)
-// Windows also needs __declspec(guard(nocf)).
 #define DISABLE_CFI_ICALL NO_SANITIZE("cfi-icall") __declspec(guard(nocf))
 #else
 #define DISABLE_CFI_ICALL NO_SANITIZE("cfi-icall")
 #endif
 #endif
-#if !defined(DISABLE_CFI_ICALL)
-#define DISABLE_CFI_ICALL
-#endif
 
-// DISABLE_CFI_DLSYM -- applies DISABLE_CFI_ICALL on platforms where dlsym
-// functions must be called. Retains CFI checks on platforms where loaded
-// modules participate in CFI (e.g. Windows).
+// Annotates a function disabling Control Flow Integrity indirect call checks if
+// doing so is necessary to call dlsym functions. The checks are retained on
+// platforms where loaded modules participate in CFI (viz. Windows).
+//
+// See also:
+//   https://www.chromium.org/developers/testing/control-flow-integrity/#indirect-call-failures
+//
+// Usage:
+// ```
+//   DISABLE_CFI_DLSYM void Func() {
+//     // On non-Windows platforms, CFI indirect call checks will not be
+//     // performed in this body.
+//   }
+// ```
 #if !defined(DISABLE_CFI_DLSYM)
 #if BUILDFLAG(IS_WIN)
-// Windows modules register functions when loaded so can be checked by CFG.
 #define DISABLE_CFI_DLSYM
 #else
 #define DISABLE_CFI_DLSYM DISABLE_CFI_ICALL
 #endif
 #endif
-#if !defined(DISABLE_CFI_DLSYM)
-#define DISABLE_CFI_DLSYM
-#endif
 
-// Macro useful for writing cross-platform function pointers.
-#if !defined(CDECL)
-#if BUILDFLAG(IS_WIN)
-#define CDECL __cdecl
-#else  // BUILDFLAG(IS_WIN)
-#define CDECL
-#endif  // BUILDFLAG(IS_WIN)
-#endif  // !defined(CDECL)
-
-// Macro for hinting that an expression is likely to be false.
-#if !defined(UNLIKELY)
-#if defined(COMPILER_GCC) || defined(__clang__)
-#define UNLIKELY(x) __builtin_expect(!!(x), 0)
-#else
-#define UNLIKELY(x) (x)
-#endif  // defined(COMPILER_GCC)
-#endif  // !defined(UNLIKELY)
-
-#if !defined(LIKELY)
-#if defined(COMPILER_GCC) || defined(__clang__)
-#define LIKELY(x) __builtin_expect(!!(x), 1)
-#else
-#define LIKELY(x) (x)
-#endif  // defined(COMPILER_GCC)
-#endif  // !defined(LIKELY)
-
-// Compiler feature-detection.
-// clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension
-#if defined(__has_feature)
-#define HAS_FEATURE(FEATURE) __has_feature(FEATURE)
-#else
-#define HAS_FEATURE(FEATURE) 0
-#endif
-
+// Evaluates to a string constant containing the function signature.
+//
+// See also:
+//   https://clang.llvm.org/docs/LanguageExtensions.html#source-location-builtins
+//   https://en.cppreference.com/w/c/language/function_definition#func
+//
+// Usage:
+// ```
+//   void Func(int arg) {
+//     std::cout << PRETTY_FUNCTION;  // Prints `void Func(int)` or similar.
+//   }
+// ```
 #if defined(COMPILER_GCC)
 #define PRETTY_FUNCTION __PRETTY_FUNCTION__
 #elif defined(COMPILER_MSVC)
 #define PRETTY_FUNCTION __FUNCSIG__
 #else
-// See https://en.cppreference.com/w/c/language/function_definition#func
 #define PRETTY_FUNCTION __func__
 #endif
 
-#if !defined(CPU_ARM_NEON)
-#if defined(__arm__)
-#if !defined(__ARMEB__) && !defined(__ARM_EABI__) && !defined(__EABI__) && \
-    !defined(__VFP_FP__) && !defined(_WIN32_WCE) && !defined(ANDROID)
-#error Chromium does not support middle endian architecture
-#endif
-#if defined(__ARM_NEON__)
-#define CPU_ARM_NEON 1
-#endif
-#endif  // defined(__arm__)
-#endif  // !defined(CPU_ARM_NEON)
-
-#if !defined(HAVE_MIPS_MSA_INTRINSICS)
-#if defined(__mips_msa) && defined(__mips_isa_rev) && (__mips_isa_rev >= 5)
-#define HAVE_MIPS_MSA_INTRINSICS 1
-#endif
-#endif
-
-#if defined(__clang__) && HAS_ATTRIBUTE(uninitialized)
-// Attribute "uninitialized" disables -ftrivial-auto-var-init=pattern for
-// the specified variable.
-// Library-wide alternative is
-// 'configs -= [ "//build/config/compiler:default_init_stack_vars" ]' in .gn
-// file.
+// Annotates a variable indicating that its storage should not be filled with a
+// fixed pattern when uninitialized.
 //
-// See "init_stack_vars" in build/config/compiler/BUILD.gn and
-// http://crbug.com/977230
-// "init_stack_vars" is enabled for non-official builds and we hope to enable it
-// in official build in 2020 as well. The flag writes fixed pattern into
-// uninitialized parts of all local variables. In rare cases such initialization
-// is undesirable and attribute can be used:
-//   1. Degraded performance
-// In most cases compiler is able to remove additional stores. E.g. if memory is
-// never accessed or properly initialized later. Preserved stores mostly will
-// not affect program performance. However if compiler failed on some
-// performance critical code we can get a visible regression in a benchmark.
-//   2. memset, memcpy calls
-// Compiler may replaces some memory writes with memset or memcpy calls. This is
-// not -ftrivial-auto-var-init specific, but it can happen more likely with the
-// flag. It can be a problem if code is not linked with C run-time library.
+// The `init_stack_vars` gn arg (enabled on most build configs) causes the
+// compiler to generate code that writes a fixed pattern into uninitialized
+// parts of all local variables, to mitigate security risks. In most cases, e.g.
+// when such memory is either never accessed or will be initialized later before
+// reading, the compiler is able to remove the additional stores, and any
+// remaining stores are unlikely to affect program performance.
 //
-// Note: The flag is security risk mitigation feature. So in future the
-// attribute uses should be avoided when possible. However to enable this
-// mitigation on the most of the code we need to be less strict now and minimize
-// number of exceptions later. So if in doubt feel free to use attribute, but
-// please document the problem for someone who is going to cleanup it later.
-// E.g. platform, bot, benchmark or test name in patch description or next to
-// the attribute.
+// If hot code suffers unavoidable perf penalties, this can disable the
+// pattern-filling there. This should only be done when necessary, since reads
+// from uninitialized variables are not only UB, they can in practice allow
+// attackers to control logic by pre-filling the variable's memory with a
+// desirable value.
+//
+// NOTE: This behavior also increases the likelihood the compiler will generate
+// `memcpy()`/`memset()` calls to init variables. If this causes link errors for
+// targets that don't link against the CRT, this macro can help; you may instead
+// want 'configs -= [ "//build/config/compiler:default_init_stack_vars" ]' in
+// the relevant .gn file to disable this on the whole target.
+//
+// See also:
+//   https://source.chromium.org/chromium/chromium/src/+/main:build/config/compiler/BUILD.gn;l=3088;drc=24ccaf63ff5b1883be1ebe5f979d917ce28b0131
+//   https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-ftrivial-auto-var-init
+//   https://clang.llvm.org/docs/AttributeReference.html#uninitialized
+//
+// Usage:
+// ```
+//   // The following line declares `i` without ensuring it initially contains
+//   // any particular pattern.
+//   STACK_UNINITIALIZED int i;
+// ```
+#if __has_cpp_attribute(clang::uninitialized)
 #define STACK_UNINITIALIZED [[clang::uninitialized]]
+#elif __has_cpp_attribute(gnu::uninitialized)
+#define STACK_UNINITIALIZED [[gnu::uninitialized]]
 #else
 #define STACK_UNINITIALIZED
 #endif
 
-// Attribute "no_stack_protector" disables -fstack-protector for the specified
-// function.
+// Annotates a function disabling stack canary checks.
 //
-// "stack_protector" is enabled on most POSIX builds. The flag adds a canary
-// to each stack frame, which on function return is checked against a reference
-// canary. If the canaries do not match, it's likely that a stack buffer
-// overflow has occurred, so immediately crashing will prevent exploitation in
-// many cases.
+// The `-fstack-protector` compiler flag (passed on most non-Windows builds)
+// causes the compiler to extend some function prologues and epilogues to set
+// and check a canary value, to detect stack buffer overflows and crash in
+// response. If hot code suffers unavoidable perf penalties, or intentionally
+// modifies the canary value, this can disable the behavior there.
 //
-// In some cases it's desirable to remove this, e.g. on hot functions, or if
-// we have purposely changed the reference canary.
-#if defined(COMPILER_GCC) || defined(__clang__)
-#if HAS_ATTRIBUTE(__no_stack_protector__)
-#define NO_STACK_PROTECTOR __attribute__((__no_stack_protector__))
-#else
-#define NO_STACK_PROTECTOR __attribute__((__optimize__("-fno-stack-protector")))
-#endif
+// See also:
+//   https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fstack-protector
+//   https://clang.llvm.org/docs/AttributeReference.html#no-stack-protector-safebuffers
+//
+// Usage:
+// ```
+//   NO_STACK_PROTECTOR void Func() {
+//     // Stack canary checks will not be performed in this body.
+//   }
+// ```
+#if __has_cpp_attribute(gnu::no_stack_protector)
+#define NO_STACK_PROTECTOR [[gnu::no_stack_protector]]
+#elif __has_cpp_attribute(gnu::optimize)
+#define NO_STACK_PROTECTOR [[gnu::optimize("-fno-stack-protector")]]
 #else
 #define NO_STACK_PROTECTOR
 #endif
 
-// The ANALYZER_ASSUME_TRUE(bool arg) macro adds compiler-specific hints
-// to Clang which control what code paths are statically analyzed,
-// and is meant to be used in conjunction with assert & assert-like functions.
-// The expression is passed straight through if analysis isn't enabled.
+// Annotates a codepath suppressing static analysis along that path. Useful when
+// code is safe in practice for reasons the analyzer can't detect, e.g. because
+// the condition leading to that path guarantees a param is non-null.
 //
-// ANALYZER_SKIP_THIS_PATH() suppresses static analysis for the current
-// codepath and any other branching codepaths that might follow.
+// Usage:
+// ```
+//   if (cond) {
+//     ANALYZER_SKIP_THIS_PATH();
+//     // Static analysis will be disabled for the remainder of this block.
+//     delete ptr;
+//   }
+// ```
 #if defined(__clang_analyzer__)
-
-inline constexpr bool AnalyzerNoReturn() __attribute__((analyzer_noreturn)) {
+inline constexpr bool AnalyzerNoReturn()
+#if HAS_ATTRIBUTE(analyzer_noreturn)
+    __attribute__((analyzer_noreturn))
+#endif
+{
   return false;
 }
+#define ANALYZER_SKIP_THIS_PATH() static_cast<void>(::AnalyzerNoReturn())
+#else
+// The above definition would be safe even outside the analyzer, but defining
+// the macro away entirely avoids the need for the optimizer to eliminate it.
+#define ANALYZER_SKIP_THIS_PATH()
+#endif
 
+// Annotates a condition directing static analysis to assume it is always true.
+// Evaluates to the provided `arg` as a `bool`.
+//
+// Usage:
+// ```
+//   // Static analysis will assume the following condition always holds.
+//   if (ANALYZER_ASSUME_TRUE(cond)) ...
+// ```
+#if defined(__clang_analyzer__)
 inline constexpr bool AnalyzerAssumeTrue(bool arg) {
-  // AnalyzerNoReturn() is invoked and analysis is terminated if |arg| is
-  // false.
   return arg || AnalyzerNoReturn();
 }
-
 #define ANALYZER_ASSUME_TRUE(arg) ::AnalyzerAssumeTrue(!!(arg))
-#define ANALYZER_SKIP_THIS_PATH() static_cast<void>(::AnalyzerNoReturn())
-
-#else  // !defined(__clang_analyzer__)
-
+#else
+// Again, the above definition is safe, this is just simpler for the optimizer.
 #define ANALYZER_ASSUME_TRUE(arg) (arg)
-#define ANALYZER_SKIP_THIS_PATH()
+#endif
 
-#endif  // defined(__clang_analyzer__)
-
-// Use nomerge attribute to disable optimization of merging multiple same calls.
-#if defined(__clang__) && HAS_ATTRIBUTE(nomerge)
+// Annotates a function, function pointer, or statement to disallow
+// optimizations that merge calls. Useful to ensure the source locations of such
+// calls are not obscured.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#nomerge
+//
+// Usage:
+// ```
+//   NOMERGE void Func();  // No direct calls to `Func()` will be merged.
+//
+//   using Ptr = decltype(&Func);
+//   NOMERGE Ptr ptr = &Func;  // No calls through `ptr` will be merged.
+//
+//   NOMERGE if (cond) {
+//     // No calls in this block will be merged.
+//   }
+// ```
+#if __has_cpp_attribute(clang::nomerge)
 #define NOMERGE [[clang::nomerge]]
 #else
 #define NOMERGE
 #endif
 
-// Marks a type as being eligible for the "trivial" ABI despite having a
-// non-trivial destructor or copy/move constructor. Such types can be relocated
-// after construction by simply copying their memory, which makes them eligible
-// to be passed in registers. The canonical example is std::unique_ptr.
+// Annotates a type as being suitable for passing in registers despite having a
+// non-trivial copy or move constructor or destructor. This requires the type
+// not be concerned about its address remaining constant, be safely usable after
+// copying its memory, and have a destructor that may be safely omitted on
+// moved-from instances; an example is `std::unique_ptr`. Unnecessary if the
+// copy/move constructor(s) and destructor are unconditionally trivial; likely
+// ineffective if the type is too large to be passed in one or two registers
+// with the target ABI. However, annotating a type this way will also cause
+// `IS_TRIVIALLY_RELOCATABLE()` to return true for that type, and so may be
+// desirable even for large types, if they are placed in containers that
+// optimize based on that check.
 //
-// Use with caution; this has some subtle effects on constructor/destructor
-// ordering and will be very incorrect if the type relies on its address
-// remaining constant. When used as a function argument (by value), the value
-// may be constructed in the caller's stack frame, passed in a register, and
-// then used and destructed in the callee's stack frame. A similar thing can
-// occur when values are returned.
-//
-// TRIVIAL_ABI is not needed for types which have a trivial destructor and
-// copy/move constructors, such as gurl_base::TimeTicks and other POD.
-//
-// It is also not likely to be effective on types too large to be passed in one
-// or two registers on typical target ABIs.
+// NOTE: Use with caution; this has subtle effects on constructor/destructor
+// ordering. When used with types passed or returned by value, values may be
+// constructed in the source stack frame, passed in a register, and then used
+// and destroyed in the target stack frame.
 //
 // See also:
 //   https://clang.llvm.org/docs/AttributeReference.html#trivial-abi
 //   https://libcxx.llvm.org/docs/DesignDocs/UniquePtrTrivialAbi.html
-#if defined(__clang__) && HAS_ATTRIBUTE(trivial_abi)
+//
+// Usage:
+// ```
+//   // Instances of type `S` will be eligible to be passed in registers despite
+//   // `S`'s nontrivial destructor.
+//   struct TRIVIAL_ABI S { ~S(); }
+// ```
+#if __has_cpp_attribute(clang::trivial_abi)
 #define TRIVIAL_ABI [[clang::trivial_abi]]
 #else
 #define TRIVIAL_ABI
 #endif
 
-// Detect whether a type is trivially relocatable, ie. a move-and-destroy
-// sequence can replaced with memmove(). This can be used to optimise the
-// implementation of containers. This is automatically true for types that were
-// defined with TRIVIAL_ABI such as scoped_refptr.
+// Determines whether a type is trivially relocatable, i.e. a move-and-destroy
+// sequence can safely be replaced with `memcpy()`. This is true of types with
+// trivial copy or move construction plus trivial destruction, as well as types
+// marked `TRIVIAL_ABI`. Useful to optimize container implementations.
 //
 // See also:
 //   https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p1144r8.html
-//   https://clang.llvm.org/docs/LanguageExtensions.html#:~:text=__is_trivially_relocatable
-#if defined(__clang__) && HAS_BUILTIN(__is_trivially_relocatable)
+//   https://clang.llvm.org/docs/LanguageExtensions.html#:~:text=__builtin_is_cpp_trivially_relocatable
+//
+// Usage:
+// ```
+//   if constexpr (IS_TRIVIALLY_RELOCATABLE(T)) {
+//     // This block will only be executed if type `T` is trivially relocatable.
+//   }
+// ```
+#if HAS_BUILTIN(__builtin_is_cpp_trivially_relocatable)
+#define IS_TRIVIALLY_RELOCATABLE(t) __builtin_is_cpp_trivially_relocatable(t)
+#elif HAS_BUILTIN(__is_trivially_relocatable)
+// TODO(crbug.com/416394845): This is deprecated. Remove once all toolchains
+// have __builtin_is_cpp_trivially_relocatable.
 #define IS_TRIVIALLY_RELOCATABLE(t) __is_trivially_relocatable(t)
 #else
 #define IS_TRIVIALLY_RELOCATABLE(t) false
 #endif
 
-// Marks a member function as reinitializing a moved-from variable.
-// See also
-// https://clang.llvm.org/extra/clang-tidy/checks/bugprone-use-after-move.html#reinitialization
-#if defined(__clang__) && HAS_ATTRIBUTE(reinitializes)
+// Annotates a member function as safe to call on a moved-from object, which it
+// will reinitialize.
+//
+// See also:
+//   https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html#reinitialization
+//
+// Usage:
+// ```
+//   struct S {
+//     REINITIALIZES_AFTER_MOVE void Reset();
+//   };
+//   void Func1(const S&);
+//   void Func2() {
+//     S s1;
+//     S s2 = std::move(s1);
+//     s1.Reset();
+//     // clang-tidy's `bugprone-use-after-move` check will not flag the
+//     // following call as a use-after-move, due to the intervening `Reset()`.
+//     Func1(s1);
+//   }
+// ```
+#if __has_cpp_attribute(clang::reinitializes)
 #define REINITIALIZES_AFTER_MOVE [[clang::reinitializes]]
 #else
 #define REINITIALIZES_AFTER_MOVE
 #endif
 
-// Requires constant initialization. See constinit in C++20. Allows to rely on a
-// variable being initialized before execution, and not requiring a global
-// constructor.
-#if HAS_ATTRIBUTE(require_constant_initialization)
-#define CONSTINIT __attribute__((require_constant_initialization))
-#endif
-#if !defined(CONSTINIT)
-#define CONSTINIT
-#endif
-
-#if defined(__clang__)
+// Annotates a type as owning an object or memory region whose address may be
+// vended to or stored by other objects. For example, `std::unique_ptr<T>` owns
+// a `T` and vends its address via `.get()`, and `std::string` owns a block of
+// `char` and vends its address via `.data()`. Used to detect lifetime errors in
+// conjunction with `GSL_POINTER`; see documentation there.
+//
+// See also:
+//   https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#SS-ownership
+//   https://clang.llvm.org/docs/AttributeReference.html#owner
+//   https://clang.llvm.org/docs/DiagnosticsReference.html#wdangling-gsl
+//
+// Usage:
+// ```
+//   // Marking `S` as `GSL_OWNER` enables `-Wdangling-gsl` to detect misuse by
+//   // types annotated as `GSL_POINTER`.
+//   struct GSL_OWNER S;
+// ```
+#if __has_cpp_attribute(gsl::Owner)
 #define GSL_OWNER [[gsl::Owner]]
-#define GSL_POINTER [[gsl::Pointer]]
 #else
 #define GSL_OWNER
+#endif
+
+// Annotates a type as holding a pointer into an owner object (an appropriate
+// STL or `GSL_OWNER`-annotated type). If an instance of the pointer type is
+// constructed from an instance of the owner type, and the owner instance is
+// destroyed, the pointer instance is considered to be dangling. Useful to
+// diagnose some cases of lifetime errors.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#pointer
+//
+// Usage:
+// ```
+//  struct GSL_OWNER T {};
+//  struct GSL_POINTER S {
+//    S(const T&);
+//  };
+//  S Func() {
+//    // The following return will not compile; diagnosed as returning address
+//    // of local temporary.
+//    return S(T());
+//  }
+// ```
+#if __has_cpp_attribute(gsl::Pointer)
+#define GSL_POINTER [[gsl::Pointer]]
+#else
 #define GSL_POINTER
 #endif
 
-// Adds the "logically_const" tag to a symbol's mangled name. The "Mutable
-// Constants" check [1] detects instances of constants that aren't in .rodata,
-// e.g. due to a missing `const`. Using this tag suppresses the check for this
-// symbol, allowing it to live outside .rodata without a warning.
+// Annotates a type or variable to add a "logically_const" ABI tag to any
+// corresponding mangled symbol name(s). Useful to suppress warnings from the
+// "Mutable Constants" trybot check [1] when logically const instances are named
+// like `kConstants` but for some reason should not be marked `const`.
 //
 // [1]:
-// https://crsrc.org/c/docs/speed/binary_size/android_binary_size_trybot.md#Mutable-Constants
-#if defined(COMPILER_GCC) || defined(__clang__)
+// https://chromium.googlesource.com/chromium/src/+/main/docs/speed/binary_size/android_binary_size_trybot.md#Mutable-Constants
+//
+// Usage:
+// ```
+//   struct S {};
+//   S kConstS;                      // Fails on some trybots.
+//   LOGICALLY_CONST S kAlsoConstS;  // OK
+//
+//   struct LOGICALLY_CONST T {};
+//   T kConstT;                      // OK
+// ```
+#if __has_cpp_attribute(gnu::abi_tag)
 #define LOGICALLY_CONST [[gnu::abi_tag("logically_const")]]
 #else
 #define LOGICALLY_CONST
 #endif
 
-// preserve_most clang's calling convention. Reduces register pressure for the
-// caller and as such can be used for cold calls. Support for the
-// "preserve_most" attribute is limited:
-// - 32-bit platforms do not implement it,
-// - component builds fail because _dl_runtime_resolve() clobbers registers,
-// - there are crashes on arm64 on Windows (https://crbug.com/v8/14065), which
-//   can hopefully be fixed in the future.
-// Additionally, the initial implementation in clang <= 16 overwrote the return
-// register(s) in the epilogue of a preserve_most function, so we only use
-// preserve_most in clang >= 17 (see https://reviews.llvm.org/D143425).
-// See https://clang.llvm.org/docs/AttributeReference.html#preserve-most for
-// more details.
-#if defined(ARCH_CPU_64_BITS) &&                       \
-    !(BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)) && \
-    !defined(COMPONENT_BUILD) && defined(__clang__) && \
-    __clang_major__ >= 17 && HAS_ATTRIBUTE(preserve_most)
-#define PRESERVE_MOST __attribute__((preserve_most))
+// Annotates a function indicating it is cold, but called from hot functions.
+// Useful when a performance-sensitive function is usually simple, but in edge
+// cases must fall back to a more complex handler.
+//
+// On X86-64 and AArch64, this changes the calling convention so most registers
+// are callee-saved, reducing register spills in the caller. This can improve
+// caller performance in the common case, at the cost of pessimizing the callee.
+// On other platforms, this attribute has no effect as of Clang 20.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#preserve-most
+//
+// Usage:
+// ```
+//   // Calls to this function will not require most registers to be saved.
+//   PRESERVE_MOST void Func();
+// ```
+//
+// Disable `PRESERVE_MOST` in component builds, since `_dl_runtime_resolve()`
+// clobbers registers on platforms where it's used, and the component build is
+// not perf-critical anyway; see
+// https://github.com/llvm/llvm-project/issues/105588.
+//
+// Also disable for Win ARM64 due to as-yet-uninvestigated crashes.
+// TODO(crbug.com/42204008): Investigate, fix, and re-enable.
+#if __has_cpp_attribute(clang::preserve_most) &&             \
+    (defined(ARCH_CPU_ARM64) || defined(ARCH_CPU_X86_64)) && \
+    !defined(COMPONENT_BUILD) &&                             \
+    !(BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64))
+#define PRESERVE_MOST [[clang::preserve_most]]
 #else
 #define PRESERVE_MOST
 #endif
 
+// Annotates a pointer or reference parameter or return value for a member
+// function as having lifetime intertwined with the instance on which the
+// function is called. For parameters, the function is assumed to store the
+// value into the called-on object, so if the referred-to object is later
+// destroyed, the called-on object is also considered to be dangling. For return
+// values, the value is assumed to point into the called-on object, so if that
+// object is destroyed, the returned value is also considered to be dangling.
+// Useful to diagnose some cases of lifetime errors.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
+//
+// Usage:
+// ```
+//   struct S {
+//      S(int* p LIFETIME_BOUND);
+//      int* Get() LIFETIME_BOUND;
+//   };
+//   S Func1() {
+//     int i = 0;
+//     // The following return will not compile; diagnosed as returning address
+//     // of a stack object.
+//     return S(&i);
+//   }
+//   int* Func2(int* p) {
+//     // The following return will not compile; diagnosed as returning address
+//     // of a local temporary.
+//     return S(p).Get();
+//   }
+// ```
+#if __has_cpp_attribute(clang::lifetimebound)
+#define LIFETIME_BOUND [[clang::lifetimebound]]
+#else
+#define LIFETIME_BOUND
+#endif
+
+// Annotates a function or variable to indicate that it should have weak
+// linkage. Useful for library code that wants code linking against it to be
+// able to override its functionality; inside a single target, this is better
+// accomplished via virtual methods and other more standard mechanisms.
+//
+// Any weak definition of a symbol will be overridden at link time by a non-weak
+// definition. Marking a `const` or `constexpr` variable weak makes it no longer
+// be considered a compile-time constant, since its value may be different after
+// linking.
+//
+// Multiple weak definitions of a symbol may exist, in which case the linker is
+// free to select any when there are no non-weak definitions. Like with symbols
+// marked `inline`, this can lead to subtle, difficult-to-diagnose bugs if not
+// all definitions are identical.
+//
+// A weak declaration that has no definitions at link time will be linked as if
+// the corresponding address is null. Therefore library code can use weak
+// declarations and conditionals to allow consumers to provide optional
+// customizations.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#weak
+//
+// Usage:
+// ```
+//   // The following definition defaults `x` to 10, but allows other object
+//   // files to override its value. Thus, despite `constexpr`, `x` is not
+//   // considered a compile-time constant (and cannot be used in a `constexpr`
+//   // context).
+//   extern const int x;
+//   WEAK_SYMBOL constexpr int x = 10;
+//
+//   // The following declaration allows linking to occur whether a definition
+//   // of `Func()` is provided or not; if none is present, `&Func` will
+//   // evaluate to `nullptr` at runtime.
+//   WEAK_SYMBOL void Func();
+//
+//   // The following definition provides a default implementation of `Func2()`,
+//   // but allows other object files to override.
+//   WEAK_SYMBOL void Func2() { ... }
+// ```
+#if __has_cpp_attribute(gnu::weak)
+#define WEAK_SYMBOL [[gnu::weak]]
+#else
+#define WEAK_SYMBOL
+#endif
+
+// Annotates a function indicating that the compiler should not convert calls
+// within it to tail calls.
+//
+// For a callee-side version of this, see `NOT_TAIL_CALLED`.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#disable-tail-calls
+// Usage:
+// ```
+//   DISABLE_TAIL_CALLS void Func() {
+//     // Function calls in this body will not be tail calls.
+//   }
+// ```
+#if __has_cpp_attribute(clang::disable_tail_calls)
+#define DISABLE_TAIL_CALLS [[clang::disable_tail_calls]]
+#else
+#define DISABLE_TAIL_CALLS
+#endif
+
+// Annotates a type or member indicating the minimum possible alignment (one bit
+// for bitfields, one byte otherwise) should be used. This can be used to
+// eliminate padding inside objects, at the cost of potentially pessimizing
+// code, or even generating invalid code (depending on platform restrictions) if
+// underaligned objects have their addresses taken and passed elsewhere.
+//
+// This is similar to the more-broadly-supported `#pragma pack(1)`.
+//
+// See also:
+//   https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-packed-variable-attribute
+//
+// Usage:
+// ```
+//   struct PACKED_OBJ S1 {
+//     int8_t a;   // Alignment 1, offset 0, size 1
+//     int32_t b;  // Alignment 1, offset 1 (0 bytes padding), size 4
+//   };  // Overall alignment 1, 0 bytes trailing padding, overall size 5
+//
+//   struct S2 {
+//     int8_t a;              // Alignment 1, offset 0, size 1
+//     int32_t b;             // Alignment 4, offset 4 (3 bytes padding), size 4
+//     int8_t c;              // Alignment 1, offset 8 (0 bytes padding), size 1
+//     PACKED_OBJ int32_t d;  // Alignment 1, offset 9 (0 bytes padding), size 4
+//   };  // Overall alignment 4, 3 bytes trailing padding, overall size 16
+// ```
+#if __has_cpp_attribute(gnu::packed)
+#define PACKED_OBJ [[gnu::packed]]
+#else
+#define PACKED_OBJ
+#endif
+
+// Annotates a function indicating that the returned pointer will never be null.
+// This may allow the compiler to assume null checks on the caller side are
+// unnecessary.
+//
+// In practice, this is usually better-handled by returning a value or
+// reference, which enforce such guarantees at the type level.
+//
+// See also:
+//   https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-returns_005fnonnull-function-attribute
+//   https://clang.llvm.org/docs/AttributeReference.html#nullability-attributes
+//
+// Usage:
+// ```
+//   // The following function will never return `nullptr`.
+//   RETURNS_NONNULL int* Func();
+// ```
+#if __has_cpp_attribute(gnu::returns_nonnull)
+#define RETURNS_NONNULL [[gnu::returns_nonnull]]
+#else
+#define RETURNS_NONNULL
+#endif
+
+// Annotates a function indicating it is const, meaning that it has no
+// observable side effects and its return value depends only on its arguments.
+// Const functions may not read external memory other than unchanging objects
+// (e.g. non-volatile constants), and the compiler is free to replace calls to
+// them with the return values of earlier calls with the same arguments no
+// matter what other state might have changed in the meantime.
+//
+// This is a much stronger restriction than `const`-qualified functions, and is
+// rarely appropriate outside small local helpers, which are frequently
+// inlineable anyway and would not really benefit.
+//
+// WARNING: Misusing this attribute can lead to silent miscompilation, UB, and
+// difficult-to-diagnose bugs. For this and the above reason, usage should be
+// very rare.
+//
+// See also:
+//   https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-const-function-attribute
+//
+// Usage:
+// ```
+//   // The compiler may replace calls to this function with values returned
+//   // from earlier calls, assuming the args match.
+//   CONST_FUNCTION int Func(int);
+// ```
+#if __has_cpp_attribute(gnu::const)
+#define CONST_FUNCTION [[gnu::const]]
+#else
+#define CONST_FUNCTION
+#endif
+
+// Annotates a function indicating it is pure, meaning that it has no observable
+// side effects. Unlike functions annotated `CONST_FUNCTION`, pure functions may
+// still read external memory, and thus their return values may change between
+// calls. `strlen()` and `memcmp()` are examples of pure functions. Useful to
+// allow folding/reordering calls for optimization purposes.
+//
+// WARNING: Misusing this attribute can lead to silent miscompilation, UB, and
+// difficult-to-diagnose bugs. Because apparently-safe invocations can sometimes
+// have side effects (especially when invoking "overridable" functionality like
+// virtual or templated methods), such misuse is far more likely than it seems.
+// Therefore, this macro should generally be used only in key vocabulary types,
+// where the perf and ergonomic benefits of callers not needing to worry about
+// caching results in local variables in hot code outweighs the risks.
+//
+// See also:
+//   https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-pure-function-attribute
+//
+// Usage:
+// ```
+//   // Calls to this function may be subject to more aggressive common
+//   // subexpression (CSE) optimization.
+//   PURE_FUNCTION int Func(int);
+// ```
+#if __has_cpp_attribute(gnu::pure)
+#define PURE_FUNCTION [[gnu::pure]]
+#else
+#define PURE_FUNCTION
+#endif
+
+// Annotates a function or class data member indicating it can lead to
+// out-of-bounds accesses (OOB) if given incorrect inputs.
+//
+// For functions, this commonly includes functions which take pointers, sizes,
+// iterators, sentinels, etc. and cannot fully check their preconditions (e.g.
+// that the provided pointer actually points to an allocation of at least the
+// provided size). Useful to diagnose potential misuse via
+// `-Wunsafe-buffer-usage`, as well as to mark functions potentially in need of
+// safer alternatives.
+//
+// For fields, this would be used to annotate both pointer and size fields that
+// have not yet been converted to a span.
+//
+// All functions or fields annotated with this macro should come with a
+// `// PRECONDITIONS: ` comment that explains what the caller must guarantee
+// to ensure safe operation. Callers can then write `// SAFETY: ` comments
+// explaining why the specific preconditions have been met.
+//
+// Ideally, unsafe functions should also be paired with a safer version, e.g.
+// one that replaces pointer parameters with `span`s; otherwise, document safer
+// replacement coding patterns callers can migrate to.
+//
+// Annotating a function `UNSAFE_BUFFER_USAGE` means all call sites (that do not
+// disable the warning) must wrap calls in `UNSAFE_BUFFERS()`; see documentation
+// there. Annotating a field `UNSAFE_BUFFER_USAGE` means that `UNSAFE_BUFFERS()`
+// must wrap expressions that mutate of the field.
+//
+// See also:
+//   https://chromium.googlesource.com/chromium/src/+/main/docs/unsafe_buffers.md
+//   https://clang.llvm.org/docs/SafeBuffers.html
+//   https://clang.llvm.org/docs/DiagnosticsReference.html#wunsafe-buffer-usage
+//
+// Usage:
+// ```
+//   // Calls to this function must be wrapped in `UNSAFE_BUFFERS()`.
+//   UNSAFE_BUFFER_USAGE void Func(T* input, T* end);
+//
+//   struct S {
+//     // Changing this pointer requires `UNSAFE_BUFFERS()`.
+//     UNSAFE_BUFFER_USAGE int* p;
+//   };
+// ```
+#if __has_cpp_attribute(clang::unsafe_buffer_usage)
+#define UNSAFE_BUFFER_USAGE [[clang::unsafe_buffer_usage]]
+#else
+#define UNSAFE_BUFFER_USAGE
+#endif
+
+// Annotates code indicating that it should be permanently exempted from
+// `-Wunsafe-buffer-usage`. For temporary cases such as migrating callers to
+// safer patterns, use `UNSAFE_TODO()` instead; see documentation there.
+//
+// All calls to functions annotated with `UNSAFE_BUFFER_USAGE` must be marked
+// with one of these two macros; they can also be used around pointer
+// arithmetic, pointer subscripting, and the like.
+//
+// ** USE OF THIS MACRO SHOULD BE VERY RARE.** Using this macro indicates that
+// the compiler cannot verify that the code avoids OOB, and manual review is
+// required. Even with manual review, it's easy for assumptions to change and
+// security bugs to creep in over time. Prefer safer patterns instead.
+//
+// Usage should wrap the minimum necessary code, and *must* include a
+// `// SAFETY: ...` comment that explains how the code guarantees safety or
+// meets the requirements of called `UNSAFE_BUFFER_USAGE` functions. Guarantees
+// must be manually verifiable by the Chrome security team using only local
+// invariants; contact security@chromium.org to schedule such a review. Valid
+// invariants include:
+// - Runtime conditions or `GURL_CHECK()`s nearby
+// - Invariants guaranteed by types in the surrounding code
+// - Invariants guaranteed by function calls in the surrounding code
+// - Caller requirements, if the containing function is itself annotated with
+//   `UNSAFE_BUFFER_USAGE`; this is less safe and should be a last resort
+//
+// See also:
+//   https://chromium.googlesource.com/chromium/src/+/main/docs/unsafe_buffers.md
+//   https://clang.llvm.org/docs/SafeBuffers.html
+//   https://clang.llvm.org/docs/DiagnosticsReference.html#wunsafe-buffer-usage
+//
+// Usage:
+// ```
+//   // The following call will not trigger a compiler warning even if `Func()`
+//   // is annotated `UNSAFE_BUFFER_USAGE`.
+//   return UNSAFE_BUFFERS(Func(input, end));
+// ```
+//
+// Test for `__clang__` directly, as there's no `__has_pragma` or similar (see
+// https://github.com/llvm/llvm-project/issues/51887).
+#if defined(__clang__)
+// Disabling `clang-format` allows each `_Pragma` to be on its own line, as
+// recommended by https://gcc.gnu.org/onlinedocs/cpp/Pragmas.html.
+// clang-format off
+#define UNSAFE_BUFFERS(...)                  \
+  _Pragma("clang unsafe_buffer_usage begin") \
+  __VA_ARGS__                                \
+  _Pragma("clang unsafe_buffer_usage end")
+// clang-format on
+#else
+#define UNSAFE_BUFFERS(...) __VA_ARGS__
+#endif
+
+// Annotates code indicating that it should be temporarily exempted from
+// `-Wunsafe-buffer-usage`. While this is functionally the same as
+// `UNSAFE_BUFFERS()`, semantically it indicates that this is for migration
+// purposes, and should be cleaned up as soon as possible.
+//
+// Usage:
+// ```
+//   // The following call will not trigger a compiler warning even if `Func()`
+//   // is annotated `UNSAFE_BUFFER_USAGE`.
+//   return UNSAFE_TODO(Func(input, end));
+// ```
+#define UNSAFE_TODO(...) UNSAFE_BUFFERS(__VA_ARGS__)
+
+// Annotates a function restricting its availability based on compile-time
+// information in the evaluated context. Useful to convert runtime errors to
+// compile-time errors if functions' arguments are always known at compile time.
+//
+// SFINAE and `requires` clauses can restrict function availability based on the
+// unevaluated context (type information and syntactic correctness). This
+// provides a similar capability based on the evaluated context (variable
+// values). If the condition fails, or cannot be determined at compile time, the
+// function is excluded from the overload set.
+//
+// Some use cases could be satisfied without this by marking the function
+// `consteval` and breaking compile when the condition fails (e.g. via
+// `GURL_CHECK()`/`assert()`). However, `ENABLE_IF_ATTR()` is generally superior:
+//   - Not all desired functions can be made `consteval`; e.g. most
+//     constructors.
+//   - The error message in the macro case is clearer and more actionable.
+//   - `ENABLE_IF_ATTR()` interacts better with template metaprogramming.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#enable-if
+//   https://github.com/chromium/subspace/issues/266
+//
+// Usage:
+// ```
+//   void NotConsteval(int a) {
+//     assert(a > 0);
+//   }
+//   consteval void WithoutEnableIf(int a) {
+//     assert(a > 0);
+//   }
+//   void WithEnableIf(int a) ENABLE_IF_ATTR(a > 0, "arg must be positive") {}
+//   void Func(int i) {
+//     // Compiles; assertion fails at runtime.
+//     NotConsteval(-1);
+//
+//     // Will not compile; diagnosed as not a constant expression.
+//     WithoutEnableIf(-1);
+//
+//     // Will not compile; diagnosed as no matching function call with
+//     // "note: candidate disabled: arg must be positive".
+//     WithEnableIf(-1);
+//
+//     // Will not compile (same reason). Marking `Func()` as
+//     // `ENABLE_IF_ATTR(i > 0, ...)` will not help; the compiler's analysis is
+//     // not sufficiently sophisticated to propagate this constraint.
+//     WithEnableIf(i);
+//   }
+// ```
+#if HAS_ATTRIBUTE(enable_if)
+#define ENABLE_IF_ATTR(cond, msg) __attribute__((enable_if(cond, msg)))
+#else
+#define ENABLE_IF_ATTR(cond, msg)
+#endif
+
 #endif  // BASE_COMPILER_SPECIFIC_H_
diff --git a/base/containers/checked_iterators.h b/base/containers/checked_iterators.h
index d5a27ff..8b36c6f 100644
--- a/base/containers/checked_iterators.h
+++ b/base/containers/checked_iterators.h
@@ -1,16 +1,23 @@
 // Copyright 2018 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+//
+// This file intentionally uses the `GURL_CHECK()` macro instead of the `CHECK_op()`
+// macros, as `GURL_CHECK()` generates significantly less code and is more likely to
+// optimize reasonably, even in non-official release builds. Please do not
+// change the `GURL_CHECK()` calls back to `CHECK_op()` calls.
 
 #ifndef BASE_CONTAINERS_CHECKED_ITERATORS_H_
 #define BASE_CONTAINERS_CHECKED_ITERATORS_H_
 
+#include <concepts>
 #include <iterator>
 #include <memory>
 #include <type_traits>
 
-#include "polyfills/base/check_op.h"
-#include "base/containers/util.h"
+#include "polyfills/base/check.h"
+#include "base/compiler_specific.h"
+#include "base/containers/span_forward_internal.h"
 #include "polyfills/base/memory/raw_ptr_exclusion.h"
 #include "build/build_config.h"
 
@@ -23,29 +30,47 @@
   using value_type = std::remove_cv_t<T>;
   using pointer = T*;
   using reference = T&;
-  using iterator_category = std::random_access_iterator_tag;
-#if defined(__cpp_lib_ranges)
+  using iterator_category = std::contiguous_iterator_tag;
   using iterator_concept = std::contiguous_iterator_tag;
-#endif
 
   // Required for converting constructor below.
   template <typename U>
   friend class CheckedContiguousIterator;
 
-  // Required for certain libc++ algorithm optimizations that are not available
-  // for NaCl.
+  // Required to be able to get to the underlying pointer without triggering
+  // GURL_CHECK failures.
   template <typename Ptr>
   friend struct std::pointer_traits;
 
   constexpr CheckedContiguousIterator() = default;
 
-  constexpr CheckedContiguousIterator(T* start, const T* end)
-      : CheckedContiguousIterator(start, start, end) {}
+  // Constructs an iterator from `start` to `end`, starting at `start`.
+  //
+  // # Safety
+  // `start` and `end` must point to a single allocation.
+  //
+  // # Checks
+  // This function CHECKs that `start <= end` and will terminate otherwise.
+  UNSAFE_BUFFER_USAGE constexpr CheckedContiguousIterator(T* start,
+                                                          const T* end)
+      : CheckedContiguousIterator(AssumeValid(start, start, end)) {
+    GURL_CHECK(start <= end);
+  }
 
-  constexpr CheckedContiguousIterator(const T* start, T* current, const T* end)
-      : start_(start), current_(current), end_(end) {
-    GURL_CHECK_LE(start, current);
-    GURL_CHECK_LE(current, end);
+  // Constructs an iterator from `start` to `end`, starting at `current`.
+  //
+  // # Safety
+  // `start`, `current` and `end` must point to a single allocation.
+  //
+  // # Checks
+  // This function CHECKs that `start <= current <= end` and will terminate
+  // otherwise.
+  UNSAFE_BUFFER_USAGE constexpr CheckedContiguousIterator(const T* start,
+                                                          T* current,
+                                                          const T* end)
+      : CheckedContiguousIterator(AssumeValid(start, current, end)) {
+    GURL_CHECK(start <= current);
+    GURL_CHECK(current <= end);
   }
 
   constexpr CheckedContiguousIterator(const CheckedContiguousIterator& other) =
@@ -56,16 +81,15 @@
   // are unsafe. Furthermore, this is the same condition as used by the
   // converting constructors of std::span<T> and std::unique_ptr<T[]>.
   // See https://wg21.link/n4042 for details.
-  template <
-      typename U,
-      std::enable_if_t<std::is_convertible_v<U (*)[], T (*)[]>>* = nullptr>
+  template <typename U>
   constexpr CheckedContiguousIterator(const CheckedContiguousIterator<U>& other)
+    requires(std::convertible_to<U (*)[], T (*)[]>)
       : start_(other.start_), current_(other.current_), end_(other.end_) {
     // We explicitly don't delegate to the 3-argument constructor here. Its
     // CHECKs would be redundant, since we expect |other| to maintain its own
     // invariant. However, DCHECKs never hurt anybody. Presumably.
-    GURL_DCHECK_LE(other.start_, other.current_);
-    GURL_DCHECK_LE(other.current_, other.end_);
+    GURL_DCHECK(other.start_ <= other.current_);
+    GURL_DCHECK(other.current_ <= other.end_);
   }
 
   ~CheckedContiguousIterator() = default;
@@ -79,38 +103,18 @@
     return lhs.current_ == rhs.current_;
   }
 
-  friend constexpr bool operator!=(const CheckedContiguousIterator& lhs,
-                                   const CheckedContiguousIterator& rhs) {
+  friend constexpr auto operator<=>(const CheckedContiguousIterator& lhs,
+                                    const CheckedContiguousIterator& rhs) {
     lhs.CheckComparable(rhs);
-    return lhs.current_ != rhs.current_;
-  }
-
-  friend constexpr bool operator<(const CheckedContiguousIterator& lhs,
-                                  const CheckedContiguousIterator& rhs) {
-    lhs.CheckComparable(rhs);
-    return lhs.current_ < rhs.current_;
-  }
-
-  friend constexpr bool operator<=(const CheckedContiguousIterator& lhs,
-                                   const CheckedContiguousIterator& rhs) {
-    lhs.CheckComparable(rhs);
-    return lhs.current_ <= rhs.current_;
-  }
-  friend constexpr bool operator>(const CheckedContiguousIterator& lhs,
-                                  const CheckedContiguousIterator& rhs) {
-    lhs.CheckComparable(rhs);
-    return lhs.current_ > rhs.current_;
-  }
-
-  friend constexpr bool operator>=(const CheckedContiguousIterator& lhs,
-                                   const CheckedContiguousIterator& rhs) {
-    lhs.CheckComparable(rhs);
-    return lhs.current_ >= rhs.current_;
+    return lhs.current_ <=> rhs.current_;
   }
 
   constexpr CheckedContiguousIterator& operator++() {
-    GURL_CHECK_NE(current_, end_);
-    ++current_;
+    GURL_CHECK(current_ != end_);
+    // SAFETY: `current_ <= end_` is an invariant maintained internally, and the
+    // GURL_CHECK above ensures that we are not at the end yet, so incrementing stays
+    // in bounds of the allocation.
+    UNSAFE_BUFFERS(++current_);
     return *this;
   }
 
@@ -121,8 +125,11 @@
   }
 
   constexpr CheckedContiguousIterator& operator--() {
-    GURL_CHECK_NE(current_, start_);
-    --current_;
+    GURL_CHECK(current_ != start_);
+    // SAFETY: `current_ >= start_` is an invariant maintained internally, and
+    // the GURL_CHECK above ensures that we are not at the start yet, so decrementing
+    // stays in bounds of the allocation.
+    UNSAFE_BUFFERS(--current_);
     return *this;
   }
 
@@ -133,12 +140,17 @@
   }
 
   constexpr CheckedContiguousIterator& operator+=(difference_type rhs) {
-    if (rhs > 0) {
-      GURL_CHECK_LE(rhs, end_ - current_);
-    } else {
-      GURL_CHECK_LE(-rhs, current_ - start_);
-    }
-    current_ += rhs;
+    // NOTE: Since the max allocation size is PTRDIFF_MAX (in our compilers),
+    // subtracting two pointers from the same allocation can not underflow.
+    GURL_CHECK(rhs <= end_ - current_);
+    GURL_CHECK(rhs >= start_ - current_);
+    // SAFETY: `current_ <= end_` is an invariant maintained internally. The
+    // checks above ensure:
+    // `start_ - current_ <= rhs <= end_ - current_`.
+    // Which means:
+    // `start_ <= rhs + current <= end_`, so `current_` will remain in bounds of
+    // the allocation after adding `rhs`.
+    UNSAFE_BUFFERS(current_ += rhs);
     return *this;
   }
 
@@ -155,12 +167,17 @@
   }
 
   constexpr CheckedContiguousIterator& operator-=(difference_type rhs) {
-    if (rhs < 0) {
-      GURL_CHECK_LE(-rhs, end_ - current_);
-    } else {
-      GURL_CHECK_LE(rhs, current_ - start_);
-    }
-    current_ -= rhs;
+    // NOTE: Since the max allocation size is PTRDIFF_MAX (in our compilers),
+    // subtracting two pointers from the same allocation can not underflow.
+    GURL_CHECK(rhs >= current_ - end_);
+    GURL_CHECK(rhs <= current_ - start_);
+    // SAFETY: `start_ <= current_` is an invariant maintained internally. The
+    // checks above ensure:
+    // `current_ - end_ <= rhs <= current_ - start_`.
+    // Which means:
+    // `end_ >= current - rhs >= start_`, so `current_` will remain in bounds
+    // of the allocation after subtracting `rhs`.
+    UNSAFE_BUFFERS(current_ -= rhs);
     return *this;
   }
 
@@ -178,51 +195,54 @@
   }
 
   constexpr reference operator*() const {
-    GURL_CHECK_NE(current_, end_);
+    GURL_CHECK(current_ != end_);
     return *current_;
   }
 
   constexpr pointer operator->() const {
-    GURL_CHECK_NE(current_, end_);
+    GURL_CHECK(current_ != end_);
     return current_;
   }
 
   constexpr reference operator[](difference_type rhs) const {
-    GURL_CHECK_GE(rhs, 0);
-    GURL_CHECK_LT(rhs, end_ - current_);
-    return current_[rhs];
-  }
-
-  [[nodiscard]] static bool IsRangeMoveSafe(
-      const CheckedContiguousIterator& from_begin,
-      const CheckedContiguousIterator& from_end,
-      const CheckedContiguousIterator& to) {
-    if (from_end < from_begin)
-      return false;
-    const auto from_begin_uintptr = get_uintptr(from_begin.current_);
-    const auto from_end_uintptr = get_uintptr(from_end.current_);
-    const auto to_begin_uintptr = get_uintptr(to.current_);
-    const auto to_end_uintptr =
-        get_uintptr((to + std::distance(from_begin, from_end)).current_);
-
-    return to_begin_uintptr >= from_end_uintptr ||
-           to_end_uintptr <= from_begin_uintptr;
+    // NOTE: Since the max allocation size is PTRDIFF_MAX (in our compilers),
+    // subtracting two pointers from the same allocation can not underflow.
+    GURL_CHECK(rhs >= start_ - current_);
+    GURL_CHECK(rhs < end_ - current_);
+    // SAFETY: `start_ <= current_ <= end_` is an invariant maintained
+    // internally. The checks above ensure:
+    // `start_ - current_ <= rhs < end_ - current_`.
+    // Which means:
+    // `start_ <= current_ + rhs < end_`.
+    // So `current_[rhs]` will be a valid dereference of a pointer in the
+    // allocation (it is not the pointer toone-past-the-end).
+    return UNSAFE_BUFFERS(current_[rhs]);
   }
 
  private:
+  template <typename, size_t, typename>
+  friend class span;
+
+  // Helper to allow containers such as `span` to elide constructor `GURL_CHECK()`'s
+  // that begin <= current <= end.
+  struct AssumeValid {
+    RAW_PTR_EXCLUSION const T* start;
+    RAW_PTR_EXCLUSION T* current;
+    RAW_PTR_EXCLUSION const T* end;
+  };
+  constexpr explicit CheckedContiguousIterator(AssumeValid pointers)
+      : start_(pointers.start),
+        current_(pointers.current),
+        end_(pointers.end) {}
+
   constexpr void CheckComparable(const CheckedContiguousIterator& other) const {
-    GURL_CHECK_EQ(start_, other.start_);
-    GURL_CHECK_EQ(end_, other.end_);
+    GURL_CHECK(start_ == other.start_);
+    GURL_CHECK(end_ == other.end_);
   }
 
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #union, #constexpr-ctor-field-initializer
+  // RAW_PTR_EXCLUSION: The embedding class is stack-scoped.
   RAW_PTR_EXCLUSION const T* start_ = nullptr;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #union, #constexpr-ctor-field-initializer
   RAW_PTR_EXCLUSION T* current_ = nullptr;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #union, #constexpr-ctor-field-initializer
   RAW_PTR_EXCLUSION const T* end_ = nullptr;
 };
 
@@ -231,53 +251,15 @@
 
 }  // namespace base
 
-// Specialize both std::__is_cpp17_contiguous_iterator and std::pointer_traits
-// for CCI in case we compile with libc++ outside of NaCl. The former is
-// required to enable certain algorithm optimizations (e.g. std::copy can be a
-// simple std::memmove under certain circumstances), and is a precursor to
-// C++20's std::contiguous_iterator concept [1]. Once we actually use C++20 it
-// will be enough to add `using iterator_concept = std::contiguous_iterator_tag`
-// to the iterator class [2], and we can get rid of this non-standard
-// specialization.
+// Specialize std::pointer_traits so that we can obtain the underlying raw
+// pointer without resulting in GURL_CHECK failures. The important bit is the
+// `to_address(pointer)` overload, which is the standard blessed way to
+// customize `std::to_address(pointer)` in C++20 [1].
 //
-// The latter is required to obtain the underlying raw pointer without resulting
-// in GURL_CHECK failures. The important bit is the `to_address(pointer)` overload,
-// which is the standard blessed way to customize `std::to_address(pointer)` in
-// C++20 [3].
-//
-// [1] https://wg21.link/iterator.concept.contiguous
-// [2] https://wg21.link/std.iterator.tags
-// [3] https://wg21.link/pointer.traits.optmem
-
-#if defined(_LIBCPP_VERSION)
-
-// TODO(crbug.com/1284275): Remove when C++20 is on by default, as the use
-// of `iterator_concept` above should suffice.
-_LIBCPP_BEGIN_NAMESPACE_STD
-
-// TODO(crbug.com/1449299): https://reviews.llvm.org/D150801 renamed this from
-// `__is_cpp17_contiguous_iterator` to `__libcpp_is_contiguous_iterator`. Clean
-// up the old spelling after libc++ rolls.
-template <typename T>
-struct __is_cpp17_contiguous_iterator;
-template <typename T>
-struct __is_cpp17_contiguous_iterator<::gurl_base::CheckedContiguousIterator<T>>
-    : true_type {};
+// [1] https://wg21.link/pointer.traits.optmem
 
 template <typename T>
-struct __libcpp_is_contiguous_iterator;
-template <typename T>
-struct __libcpp_is_contiguous_iterator<::gurl_base::CheckedContiguousIterator<T>>
-    : true_type {};
-
-_LIBCPP_END_NAMESPACE_STD
-
-#endif
-
-namespace std {
-
-template <typename T>
-struct pointer_traits<::gurl_base::CheckedContiguousIterator<T>> {
+struct std::pointer_traits<::gurl_base::CheckedContiguousIterator<T>> {
   using pointer = ::gurl_base::CheckedContiguousIterator<T>;
   using element_type = T;
   using difference_type = ptrdiff_t;
@@ -294,6 +276,4 @@
   }
 };
 
-}  // namespace std
-
 #endif  // BASE_CONTAINERS_CHECKED_ITERATORS_H_
diff --git a/base/containers/contains.h b/base/containers/contains.h
index 430f79e..0a62949 100644
--- a/base/containers/contains.h
+++ b/base/containers/contains.h
@@ -5,91 +5,50 @@
 #ifndef BASE_CONTAINERS_CONTAINS_H_
 #define BASE_CONTAINERS_CONTAINS_H_
 
-#include <type_traits>
+// Provides `Contains()`, a general purpose utility to check whether a container
+// contains a value. This will probe whether a `contains` or `find` member
+// function on `container` exists, and fall back to a generic linear search over
+// `container`.
+
+#include <algorithm>
+#include <concepts>
+#include <ranges>
 #include <utility>
 
-#include "base/ranges/algorithm.h"
-#include "base/ranges/ranges.h"
-
 namespace gurl_base {
 
-namespace internal {
-
-// Small helper to detect whether a given type has a nested `key_type` typedef.
-// Used below to catch misuses of the API for associative containers.
-template <typename T, typename SFINAE = void>
-struct HasKeyType : std::false_type {};
-
-template <typename T>
-struct HasKeyType<T, std::void_t<typename T::key_type>> : std::true_type {};
-
-// Probe whether a `contains` member function exists and return the result of
-// `container.contains(value)` if this is a valid expression. This is the
-// highest priority option.
-template <typename Container, typename Value>
-constexpr auto ContainsImpl(const Container& container,
-                            const Value& value,
-                            priority_tag<2>)
-    -> decltype(container.contains(value)) {
-  return container.contains(value);
-}
-
-// Probe whether a `find` member function exists and whether its return value
-// can be compared with `container.end()`. Intended for STL style maps and sets
-// that lack a `contains` member function.
-template <typename Container, typename Value>
-constexpr auto ContainsImpl(const Container& container,
-                            const Value& value,
-                            priority_tag<1>)
-    -> decltype(container.find(value) != container.end()) {
-  return container.find(value) != container.end();
-}
-
-// Probe whether a `find` member function exists and whether its return value
-// can be compared with `Container::npos`. Intended for STL style strings that
-// lack a `contains` member function.
-template <typename Container, typename Value>
-constexpr auto ContainsImpl(const Container& container,
-                            const Value& value,
-                            priority_tag<1>)
-    -> decltype(container.find(value) != Container::npos) {
-  return container.find(value) != Container::npos;
-}
-
-// Generic fallback option, using a linear search over `container` to find
-// `value`. Has the lowest priority. This will not compile for associative
-// containers, as this likely is a performance bug.
-template <typename Container, typename Value>
-constexpr bool ContainsImpl(const Container& container,
-                            const Value& value,
-                            priority_tag<0>) {
-  static_assert(
-      !HasKeyType<Container>::value,
-      "Error: About to perform linear search on an associative container. "
-      "Either use a more generic comparator (e.g. std::less<>) or, if a linear "
-      "search is desired, provide an explicit projection parameter.");
-  return ranges::find(container, value) != ranges::end(container);
-}
-
-}  // namespace internal
-
 // A general purpose utility to check whether `container` contains `value`. This
 // will probe whether a `contains` or `find` member function on `container`
 // exists, and fall back to a generic linear search over `container`.
 template <typename Container, typename Value>
 constexpr bool Contains(const Container& container, const Value& value) {
-  return internal::ContainsImpl(container, value, internal::priority_tag<2>());
+  if constexpr (requires {
+                  { container.contains(value) } -> std::same_as<bool>;
+                }) {
+    return container.contains(value);
+  } else if constexpr (requires { container.find(value) != Container::npos; }) {
+    return container.find(value) != Container::npos;
+  } else if constexpr (requires { container.find(value) != container.end(); }) {
+    return container.find(value) != container.end();
+  } else {
+    static_assert(
+        !requires { typename Container::key_type; },
+        "Error: About to perform linear search on an associative container. "
+        "Either use a more generic comparator (e.g. std::less<>) or, if a "
+        "linear search is desired, provide an explicit projection parameter.");
+    return std::ranges::find(container, value) != std::ranges::end(container);
+  }
 }
 
-// Overload that allows to provide an additional projection invocable. This
-// projection will be applied to every element in `container` before comparing
-// it with `value`. This will always perform a linear search.
+// Overload that allows callers to provide an additional projection invocable.
+// This projection will be applied to every element in `container` before
+// comparing it with `value`. This will always perform a linear search.
 template <typename Container, typename Value, typename Proj>
 constexpr bool Contains(const Container& container,
                         const Value& value,
                         Proj proj) {
-  return ranges::find(container, value, std::move(proj)) !=
-         ranges::end(container);
+  return std::ranges::find(container, value, std::move(proj)) !=
+         std::ranges::end(container);
 }
 
 }  // namespace base
diff --git a/base/containers/contiguous_iterator.h b/base/containers/contiguous_iterator.h
deleted file mode 100644
index 8311b50..0000000
--- a/base/containers/contiguous_iterator.h
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_CONTAINERS_CONTIGUOUS_ITERATOR_H_
-#define BASE_CONTAINERS_CONTIGUOUS_ITERATOR_H_
-
-#include <array>
-#include <iterator>
-#include <string>
-#include <type_traits>
-#include <vector>
-
-#include "base/containers/checked_iterators.h"
-
-namespace gurl_base {
-
-namespace internal {
-
-template <typename T>
-struct PointsToObject : std::true_type {};
-// std::iter_value_t is not defined for `T*` where T is not an object type.
-template <typename T>
-struct PointsToObject<T*> : std::is_object<T> {};
-
-// A pointer is a contiguous iterator.
-// Reference: https://wg21.link/iterator.traits#5
-template <typename T>
-struct IsPointer : std::is_pointer<T> {};
-
-template <typename T, typename StringT = std::basic_string<iter_value_t<T>>>
-struct IsStringIterImpl
-    : std::disjunction<std::is_same<T, typename StringT::const_iterator>,
-                       std::is_same<T, typename StringT::iterator>> {};
-
-// An iterator to std::basic_string is contiguous.
-// Reference: https://wg21.link/basic.string.general#2
-//
-// Note: Requires indirection via `IsStringIterImpl` to avoid triggering a
-// `static_assert(is_trivial_v<value_type>)` inside libc++'s std::basic_string.
-template <typename T>
-struct IsStringIter
-    : std::conjunction<
-          std::disjunction<std::is_same<iter_value_t<T>, char>,
-                           std::is_same<iter_value_t<T>, wchar_t>,
-
-                           std::is_same<iter_value_t<T>, char16_t>,
-                           std::is_same<iter_value_t<T>, char32_t>>,
-          IsStringIterImpl<T>> {};
-
-// An iterator to std::array is contiguous.
-// Reference: https://wg21.link/array.overview#1
-template <typename T, typename ArrayT = std::array<iter_value_t<T>, 1>>
-struct IsArrayIter
-    : std::disjunction<std::is_same<T, typename ArrayT::const_iterator>,
-                       std::is_same<T, typename ArrayT::iterator>> {};
-
-// An iterator to a non-bool std::vector is contiguous.
-// Reference: https://wg21.link/vector.overview#2
-template <typename T, typename VectorT = std::vector<iter_value_t<T>>>
-struct IsVectorIter
-    : std::conjunction<
-          std::negation<std::is_same<iter_value_t<T>, bool>>,
-          std::disjunction<std::is_same<T, typename VectorT::const_iterator>,
-                           std::is_same<T, typename VectorT::iterator>>> {};
-
-// The result of passing a std::valarray to std::begin is a contiguous iterator.
-// Note: Since all common standard library implementations (i.e. libc++,
-// stdlibc++ and MSVC's STL) just use a pointer here, we perform a similar
-// optimization. The corresponding unittest still ensures that this is working
-// as intended.
-// Reference: https://wg21.link/valarray.range#1
-template <typename T>
-struct IsValueArrayIter : std::is_pointer<T> {};
-
-// base's CheckedContiguousIterator is a contiguous iterator.
-template <typename T, typename ValueT = iter_value_t<T>>
-struct IsCheckedContiguousIter
-    : std::disjunction<
-          std::is_same<T, gurl_base::CheckedContiguousConstIterator<ValueT>>,
-          std::is_same<T, gurl_base::CheckedContiguousIterator<ValueT>>> {};
-
-// Check that the iterator points to an actual object, and is one of the
-// iterator types mentioned above.
-template <typename T, bool B = PointsToObject<T>::value>
-struct IsContiguousIteratorImpl : std::false_type {};
-template <typename T>
-struct IsContiguousIteratorImpl<T, true>
-    : std::disjunction<IsPointer<T>,
-                       IsStringIter<T>,
-                       IsArrayIter<T>,
-                       IsVectorIter<T>,
-                       IsValueArrayIter<T>,
-                       IsCheckedContiguousIter<T>> {};
-
-}  // namespace internal
-
-// IsContiguousIterator is a type trait that determines whether a given type is
-// a contiguous iterator. It is similar to C++20's contiguous_iterator concept,
-// but due to a lack of the corresponding contiguous_iterator_tag relies on
-// explicitly instantiating the type with iterators that are supposed to be
-// contiguous iterators.
-// References:
-// - https://wg21.link/iterator.concept.contiguous
-// - https://wg21.link/std.iterator.tags#lib:contiguous_iterator_tag
-// - https://wg21.link/n4284
-template <typename T>
-struct IsContiguousIterator
-    : internal::IsContiguousIteratorImpl<remove_cvref_t<T>> {};
-
-}  // namespace base
-
-#endif  // BASE_CONTAINERS_CONTIGUOUS_ITERATOR_H_
diff --git a/base/containers/span.h b/base/containers/span.h
index 059a0c1..ae49a2f 100644
--- a/base/containers/span.h
+++ b/base/containers/span.h
@@ -1,6 +1,11 @@
 // Copyright 2017 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+//
+// This file intentionally uses the `GURL_CHECK()` macro instead of the `CHECK_op()`
+// macros, as `GURL_CHECK()` generates significantly less code and is more likely to
+// optimize reasonably, even in non-official release builds. Please do not
+// change the `GURL_CHECK()` calls back to `CHECK_op()` calls.
 
 #ifndef BASE_CONTAINERS_SPAN_H_
 #define BASE_CONTAINERS_SPAN_H_
@@ -8,583 +13,1701 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <array>
+#include <algorithm>
+#include <concepts>
+#include <initializer_list>
 #include <iterator>
 #include <limits>
 #include <memory>
+#include <optional>
+#include <ranges>
+#include <span>
 #include <type_traits>
 #include <utility>
 
 #include "polyfills/base/check.h"
 #include "base/compiler_specific.h"
 #include "base/containers/checked_iterators.h"
-#include "base/containers/contiguous_iterator.h"
+#include "base/containers/span_forward_internal.h"
+#include "base/numerics/integral_constant_like.h"
 #include "base/numerics/safe_conversions.h"
-#include "base/template_util.h"
+#include "base/types/to_address.h"
+
+// A span is a view of contiguous elements that can be accessed like an array,
+// intended for use as a parameter or local. Unlike direct use of pointers and
+// sizes, it enforces safe usage (and simplifies callers); unlike container
+// refs, it is agnostic to the element container, expressing only "access to
+// some sequence of elements". It is similar to `std::string_view`, but for
+// arbitrary elements instead of just characters, and additionally allowing
+// mutation if the element type is non-`const`.
+//
+// Spans can be constructed from arrays, range-like objects (generally, objects
+// which expose `begin()`, `end()`, `data()`, and `size()`), and initializer
+// lists. As with all view types, spans do not own the underlying memory, so
+// users must ensure they do not outlive their backing stores; storing a span as
+// a member object is usually incorrect. (For the rare case this is useful,
+// prefer `raw_span<>` so the underlying storage pointer will be protected by
+// MiraclePtr.)
+//
+// Since spans only consist of a pointer and (for dynamic-extent spans) a size,
+// they are lightweight; constructing and copying spans is cheap and they should
+// be passed by value.
+//
+// Scopes which only need read access to the underlying data should use
+// `span<const T>`, which can be implicitly constructed from `span<T>`.
+// Habitually using `span<const T>` also avoids confusing compile errors when
+// trying to construct spans from compile-time constants or non-borrowed ranges,
+// which won't convert to `span<T>`.
+//
+// Without span:
+// ```
+//   /* Read-only usage */
+//
+//   // Implementation must avoid OOB reads.
+//   std::string HexEncode(const uint8_t* data, size_t size) { ... }
+//
+//   // Must use a separate variable to avoid repeated generation calls below.
+//   std::vector<uint8_t> data_buffer = GenerateData();
+//   // Prone to accidentally passing the wrong size.
+//   std::string r = HexEncode(data_buffer.data(), data_buffer.size());
+//
+//   /* Mutable usage */
+//
+//   // Same concerns apply in this example.
+//   ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, Args...) { ... }
+//
+//   char str_buffer[100];
+//   SafeSNPrintf(str_buffer, sizeof(str_buffer), "Pi ~= %lf", 3.14);
+// ```
+//
+// With span:
+// ```
+//   /* Read-only usage */
+//
+//   // Automatically `GURL_CHECK()`s on attempted OOB accesses.
+//   std::string HexEncode(span<const uint8_t> data) { ... }
+//
+//   // Can pass return value directly, since it lives until the end of the full
+//   // expression, outlasting the function call. Can't pass wrong size.
+//   std::string r = HexEncode(GenerateData());
+//
+//   /* Mutable usage */
+//
+//   // Can write to `buf`, but only within bounds.
+//   ssize_t SafeSNPrintf(span<char> buf, const char* fmt, Args...) { ... }
+//
+//   char str_buffer[100];
+//   // Automatically infers span size as array size (i.e. 100).
+//   SafeSNPrintf(str_buffer, "Pi ~= %lf", 3.14);
+// ```
+//
+// Dynamic-extent vs. fixed-extent spans
+// -------------------------------------
+// By default spans have dynamic extent, which means that the size is available
+// at runtime via `size()`, a la other containers and views. By using a second
+// template parameter or passing a `std::integral_constant` to the second (size)
+// constructor arg, a span's extent can be fixed at compile time; this can move
+// some constraint checks to compile time and slightly improve codegen, at the
+// cost of verbosity and more template instantiations. Methods like `first()` or
+// `subspan()` also provide templated overloads that produce fixed-extent spans;
+// these are preferred when the size is known at compile time, in part because
+// e.g. `first(1)` is a compile-error (the `int` arg is not compatible with the
+// `StrictNumeric<size_t>` param; use `first(1u)` instead), but `first<1>()` is
+// not.
+//
+// A fixed-extent span implicitly converts to a dynamic-extent span (e.g.
+// `span<int, 6>` is implicitly convertible to `span<int>`), so most code that
+// operates on spans of arbitrary length can just accept a `span<T>`; there is
+// no need to add an additional overload for specially handling the `span<T, N>`
+// case.
+//
+// There are several ways to go from a dynamic-extent span to a fixed-extent
+// span:
+// - Explicit construction of `span<T, N>`, which `GURL_CHECK()`s if the size doesn't
+//   match.
+// - Construction of `span(T*, fixed_extent<N>)`, which is equivalent to the
+//   above.
+// - `to_fixed_extent<N>()`, which returns `std::nullopt` if the size doesn't
+//   match.
+// - `first<N>()`, `last<N>()`, and `subspan<Index, N>()`, which `GURL_CHECK()` if
+//   the size is insufficient.
+//
+// Spans, `const`, and pointer-type element types
+// ----------------------------------------------
+// Pointer-type elements can make translating `const` from container types to
+// spans confusing. Fundamentally, if you analogize types this way:
+//   `std::vector<T>`       => `span<T>`
+// Then this would be const version:
+//   `const std::vector<T>` => `span<const T>`
+//    (or, more verbosely:) => `span<std::add_const_t<T>>`
+//
+// However, note that if `T` is `int*`, then `const T` is `int* const`. So:
+//   `const std::vector<int*>`       => `span<int* const>`
+//   `std::vector<const int*>`       => `span<const int*>`
+//   `const std::vector<const int*>` => `span<const int* const>`
+//
+// (N.B. There is no entry above for `std::vector<int* const>`, since per the
+// C++ standard, `std::vector`'s element type must be non-const.)
+//
+// Byte spans, `std::has_unique_object_representations_v<>`, and conversions
+// -------------------------------------------------------------------------
+// Because byte spans are often used to copy and hash objects, the byte span
+// conversion functions (e.g. `as_bytes()`, `as_byte_span()`) require the
+// element type to meet `std::has_unique_object_representations_v<>`. For types
+// which do not meet this requirement but need conversion to a byte span, there
+// are two workarounds:
+//   1. If the type is safe to convert to a byte span in general, specialize
+//      `kCanSafelyConvertToByteSpan<T>` to be true for it. For example, Blink's
+//      `AtomicString` is not trivially copyable, but it is interned, so hashing
+//      and comparing the hashed values is safe.
+//   2. If the type is not safe in general but is safe for a particular use
+//      case, pass `gurl_base::allow_nonunique_obj` as the first arg to the byte span
+//      conversion functions. For example, floating-point values are not unique
+//      (among other reasons, because `+0` and `-0` are distinct but compare
+//      equal), but they are trivially copyable, so serializing them to disk and
+//      then deserializing is OK.
+//
+// Spans using `raw_ptr<T>` for internal storage
+// ---------------------------------------------
+// Provided via the type alias `raw_span<T[, N]>` (see base/memory/raw_span.h).
+// Use only for the uncommon case when a span should be a data member of an
+// object; for locals and params, use `span` (similarly to where you'd use a
+// `raw_ptr<T>` vs. a `T*`).
+//
+// Beware the risk of dangling pointers! The object owning the member span must
+// not access that span's data after the backing storage's lifetime ends. This
+// is the same risk as with all spans, but members tend to be longer-lived than
+// params/locals, and thus more prone to dangerous use.
+//
+// Differences from `std::span`
+// ----------------------------
+// https://eel.is/c++draft/views contains the latest C++ draft of `std::span`
+// and related utilities. Chromium aims to follow the draft except where noted
+// below; please report other divergences you find.
+//
+// Differences from [span.syn]:
+// - For convenience, provides `fixed_extent<N>` as an alias to
+//   `std::integral_constant<size_t, N>`, to aid in constructing fixed-extent
+//   spans from pointers.
+//
+// Differences from [span.overview]:
+// - `span` takes an optional third template argument that can be used to
+//   customize the underlying storage pointer type. This allows implementing
+//   `raw_span` as a specialization.
+//
+// Differences from [span.cons]:
+// - The constructor which takes an iterator and a count uses
+//   `StrictNumeric<size_type>` instead of `size_type` to prevent unsafe type
+//   conversions.
+// - The constructor from a built-in array does not need to block CTAD, since
+//   the corresponding explicit deduction guide is constrained enough to be
+//   picked over the implicit one.
+//   See https://cplusplus.github.io/LWG/issue3369 for background.
+// - Omits constructors from `std::array`, since separating these from the range
+//   constructor is only useful to mark them `noexcept`, and Chromium doesn't
+//   care about that.
+// - Fixed-extent constructor from range is only `explicit` for ranges whose
+//   extent cannot be statically computed. This matches the spirit of
+//   `std::span`, which handles these (so far as it is aware) via other
+//   overloads. Without this, we would not only need the dedicated constructors
+//   from `std::array`, we would also need dedicated constructors from
+//   fixed-extent `std::span`.
+// - Adds move construction and assignment. These can avoid refcount churn when
+//   the storage pointer is not `T*`. Not necessary for `std::span` since it
+//   does not allow customizing the storage pointer type.
+// - Provides implicit conversion in both directions between fixed-extent `span`
+//   and `std::span`. The general-purpose range constructors that would
+//   otherwise handle these cases are explicit for both fixed-extent span types.
+// - For convenience, provides `span::copy_from[_nonoverlapping]()` as wrappers
+//   around `std::ranges::copy()` that enforce equal-size spans.
+// - For convenience, provides `span::copy_prefix_from()` to allow copying into
+//   the beginning of the current span.
+//
+// Differences from [span.deduct]:
+// - The deduction guide from a range creates fixed-extent spans if the source
+//   extent is available at compile time. This also removes the need for an
+//   explicit deduction guide for built-in arrays.
+// - Deduce a const element type for non-borrowed ranges.
+//
+// Differences from [span.sub]:
+// - As in [span.cons], `size_t` parameters are changed to
+//   `StrictNumeric<size_type>`.
+// - There are separate overloads for one-arg and two-arg forms of subspan,
+//   and the two-arg form does not accept dynamic_extent as a count.
+// - For convenience, provides `span::split_at()` to split a single span into
+//   two at a given offset.
+// - For convenience, provides `span::take_first[_elem]()` to remove the first
+//   portion of a dynamic-extent span and return it.
+//
+// Differences from [span.obs]:
+// - For convenience, provides `span::operator==()` to check whether two spans
+//   refer to equal-sized ranges of equal objects. This was intentionally
+//   removed from `std::span` because it makes the type non-Regular; see
+//   http://wg21.link/p1085 for details.
+// - Similarly, provides `span::operator<=>()`, which performs lexicographic
+//   comparison between spans.
+// - Furthermore, provides support for Abseil hashing, consistent with the
+//   semantics of equality described above.
+//
+// Differences from [span.elem]:
+// - Because Chromium does not use exceptions, `span::at()` behaves identically
+//   to `span::operator[]()` (i.e. it `GURL_CHECK()`s on out-of-range indexes rather
+//   than throwing).
+// - For convenience, provides `span::get_at()` to return a pointer (rather than
+//   reference) to an element. This is necessary if the backing memory may be
+//   uninitialized, since forming a reference would be UB.
+//
+// Differences from [span.objectrep]:
+// - For convenience, provides `span::to_fixed_extent<N>()` to attempt
+//   conversion to a fixed-extent span, and return null on failure.
+// - Because Chromium bans `std::byte`, `as_[writable_]bytes()` use `uint8_t`
+//   instead of `std::byte` as the returned element type.
+// - For convenience, provides `as_[writable_]chars()` to convert to other
+//   "view of bytes"-like objects.
+// - For convenience, provides `[byte_]span_from_ref()` to convert single
+//   (non-range) objects to spans.
+// - For convenience, provides `[byte_]span_[with_nul_]from_cstring()` to
+//   convert `const char[]` literals to spans.
+// - For convenience, provides `as_[writable_]byte_span()` to convert
+//   spanifiable objects directly to byte spans.
+// - For safety, bans types which do not meet
+//   `std::has_unique_object_representations_v<>` from all byte span conversion
+//   functions by default. See more detailed comments above for workarounds.
 
 namespace gurl_base {
 
-// [views.constants]
-constexpr size_t dynamic_extent = std::numeric_limits<size_t>::max();
+// Provides a compile-time fixed extent to the `count` argument of the span
+// constructor.
+//
+// (Not in `std::`.)
+template <size_t N>
+using fixed_extent = std::integral_constant<size_t, N>;
 
-template <typename T,
-          size_t Extent = dynamic_extent,
-          typename InternalPtrType = T*>
-class span;
+}  // namespace base
+
+// Mark `span` as satisfying the `view` and `borrowed_range` concepts. This
+// should be done before the definition of `span`, so that any inlined calls to
+// range functionality use the correct specializations.
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+inline constexpr bool
+    std::ranges::enable_view<gurl_base::span<ElementType, Extent, InternalPtrType>> =
+        true;
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+inline constexpr bool std::ranges::enable_borrowed_range<
+    gurl_base::span<ElementType, Extent, InternalPtrType>> = true;
+
+namespace gurl_base {
+
+// Allows global use of a type for conversion to byte spans.
+template <typename T>
+inline constexpr bool kCanSafelyConvertToByteSpan =
+    std::has_unique_object_representations_v<T>;
+template <typename T, typename U>
+inline constexpr bool kCanSafelyConvertToByteSpan<std::pair<T, U>> =
+    kCanSafelyConvertToByteSpan<std::remove_cvref_t<T>> &&
+    kCanSafelyConvertToByteSpan<std::remove_cvref_t<U>>;
+
+// Type tag to provide to byte span conversion functions to bypass
+// `std::has_unique_object_representations_v<>` check.
+struct allow_nonunique_obj_t {
+  allow_nonunique_obj_t() = default;
+};
+inline constexpr allow_nonunique_obj_t allow_nonunique_obj{};
 
 namespace internal {
 
-template <size_t I>
-using size_constant = std::integral_constant<size_t, I>;
-
+// Exposition-only concept from [span.syn]
 template <typename T>
-struct ExtentImpl : size_constant<dynamic_extent> {};
-
-template <typename T, size_t N>
-struct ExtentImpl<T[N]> : size_constant<N> {};
-
-template <typename T, size_t N>
-struct ExtentImpl<std::array<T, N>> : size_constant<N> {};
-
-template <typename T, size_t N>
-struct ExtentImpl<gurl_base::span<T, N>> : size_constant<N> {};
-
+inline constexpr size_t MaybeStaticExt = dynamic_extent;
 template <typename T>
-using Extent = ExtentImpl<remove_cvref_t<T>>;
-
-template <typename T>
-struct IsSpanImpl : std::false_type {};
-
-template <typename T, size_t Extent>
-struct IsSpanImpl<span<T, Extent>> : std::true_type {};
-
-template <typename T>
-using IsNotSpan = std::negation<IsSpanImpl<std::decay_t<T>>>;
-
-template <typename T>
-struct IsStdArrayImpl : std::false_type {};
-
-template <typename T, size_t N>
-struct IsStdArrayImpl<std::array<T, N>> : std::true_type {};
-
-template <typename T>
-using IsNotStdArray = std::negation<IsStdArrayImpl<std::decay_t<T>>>;
-
-template <typename T>
-using IsNotCArray = std::negation<std::is_array<std::remove_reference_t<T>>>;
+  requires IntegralConstantLike<T>
+inline constexpr size_t MaybeStaticExt<T> = {T::value};
 
 template <typename From, typename To>
-using IsLegalDataConversion = std::is_convertible<From (*)[], To (*)[]>;
+concept LegalDataConversion = std::is_convertible_v<From (*)[], To (*)[]>;
 
-template <typename Iter, typename T>
-using IteratorHasConvertibleReferenceType =
-    IsLegalDataConversion<std::remove_reference_t<iter_reference_t<Iter>>, T>;
+// Akin to `std::constructible_from<span, T>`, but meant to be used in a
+// type-deducing context where we don't know what args would be deduced;
+// `std::constructible_from` can't be directly used in such a case since the
+// type parameters must be fully-specified (e.g. `span<int>`), requiring us to
+// have that knowledge already.
+template <typename T>
+concept SpanConstructibleFrom = requires(T&& t) { span(std::forward<T>(t)); };
 
-template <typename Iter, typename T>
-using EnableIfCompatibleContiguousIterator = std::enable_if_t<
-    std::conjunction_v<IsContiguousIterator<Iter>,
-                       IteratorHasConvertibleReferenceType<Iter, T>>>;
+// Returns the element type of `span(T)`.
+template <typename T>
+  requires SpanConstructibleFrom<T>
+using ElementTypeOfSpanConstructedFrom =
+    typename decltype(span(std::declval<T>()))::element_type;
 
-template <typename Container, typename T>
-using ContainerHasConvertibleData = IsLegalDataConversion<
-    std::remove_pointer_t<decltype(std::data(std::declval<Container>()))>,
-    T>;
+template <typename T, typename It>
+concept CompatibleIter =
+    std::contiguous_iterator<It> &&
+    LegalDataConversion<std::remove_reference_t<std::iter_reference_t<It>>, T>;
 
-template <typename Container>
-using ContainerHasIntegralSize =
-    std::is_integral<decltype(std::size(std::declval<Container>()))>;
+// True when `T` is a `span`.
+template <typename T>
+inline constexpr bool kIsSpan = false;
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+inline constexpr bool kIsSpan<span<ElementType, Extent, InternalPtrType>> =
+    true;
 
-template <typename From, size_t FromExtent, typename To, size_t ToExtent>
-using EnableIfLegalSpanConversion =
-    std::enable_if_t<(ToExtent == dynamic_extent || ToExtent == FromExtent) &&
-                     IsLegalDataConversion<From, To>::value>;
+template <typename T, typename R>
+concept CompatibleRange =
+    std::ranges::contiguous_range<R> && std::ranges::sized_range<R> &&
+    (std::ranges::borrowed_range<R> || (std::is_const_v<T>)) &&
+    // `span`s should go through the copy constructor.
+    (!kIsSpan<std::remove_cvref_t<R>> &&
+     // Arrays should go through the array constructors.
+     (!std::is_array_v<std::remove_cvref_t<R>>)) &&
+    LegalDataConversion<
+        std::remove_reference_t<std::ranges::range_reference_t<R>>,
+        T>;
 
-// SFINAE check if Array can be converted to a span<T>.
-template <typename Array, typename T, size_t Extent>
-using EnableIfSpanCompatibleArray =
-    std::enable_if_t<(Extent == dynamic_extent ||
-                      Extent == internal::Extent<Array>::value) &&
-                     ContainerHasConvertibleData<Array, T>::value>;
+// Whether source object extent `X` will work to create a span of fixed extent
+// `N`. This is not intended for use in dynamic-extent spans.
+template <size_t N, size_t X>
+concept FixedExtentConstructibleFromExtent = X == N || X == dynamic_extent;
 
-// SFINAE check if Container can be converted to a span<T>.
-template <typename Container, typename T>
-using IsSpanCompatibleContainer =
-    std::conjunction<IsNotSpan<Container>,
-                     IsNotStdArray<Container>,
-                     IsNotCArray<Container>,
-                     ContainerHasConvertibleData<Container, T>,
-                     ContainerHasIntegralSize<Container>>;
+// Computes a fixed extent if possible from a source container type `T`.
+template <typename T>
+inline constexpr size_t kComputedExtentImpl = dynamic_extent;
+template <typename T>
+  requires requires { std::tuple_size<T>(); }
+inline constexpr size_t kComputedExtentImpl<T> = std::tuple_size_v<T>;
+template <typename T, size_t N>
+inline constexpr size_t kComputedExtentImpl<T[N]> = N;
+template <typename T, size_t N>
+inline constexpr size_t kComputedExtentImpl<std::span<T, N>> = N;
+template <typename T, size_t N, typename InternalPtrType>
+inline constexpr size_t kComputedExtentImpl<span<T, N, InternalPtrType>> = N;
 
-template <typename Container, typename T>
-using EnableIfSpanCompatibleContainer =
-    std::enable_if_t<IsSpanCompatibleContainer<Container, T>::value>;
+// `std::ranges::subrange` implements the tuple protocol to allow decaying into
+// an (iterator, sentinel) pair.
+//
+// However, this is undesired here and inconsistent with subrange.size(). Thus
+// we force the extent to be dynamic.
+template <typename I, typename S, std::ranges::subrange_kind K>
+inline constexpr size_t kComputedExtentImpl<std::ranges::subrange<I, S, K>> =
+    dynamic_extent;
 
-template <typename Container, typename T, size_t Extent>
-using EnableIfSpanCompatibleContainerAndSpanIsDynamic =
-    std::enable_if_t<IsSpanCompatibleContainer<Container, T>::value &&
-                     Extent == dynamic_extent>;
+template <typename T>
+inline constexpr size_t kComputedExtent =
+    kComputedExtentImpl<std::remove_cvref_t<T>>;
 
-// A helper template for storing the size of a span. Spans with static extents
-// don't require additional storage, since the extent itself is specified in the
-// template parameter.
-template <size_t Extent>
-class ExtentStorage {
- public:
-  constexpr explicit ExtentStorage(size_t size) noexcept {}
-  constexpr size_t size() const noexcept { return Extent; }
-};
+template <typename T>
+concept CanSafelyConvertToByteSpan =
+    kCanSafelyConvertToByteSpan<std::remove_cvref_t<T>>;
 
-// Specialization of ExtentStorage for dynamic extents, which do require
-// explicit storage for the size.
-template <>
-struct ExtentStorage<dynamic_extent> {
-  constexpr explicit ExtentStorage(size_t size) noexcept : size_(size) {}
-  constexpr size_t size() const noexcept { return size_; }
+template <typename T>
+concept ByteSpanConstructibleFrom =
+    SpanConstructibleFrom<T> &&
+    CanSafelyConvertToByteSpan<ElementTypeOfSpanConstructedFrom<T>>;
 
- private:
-  size_t size_;
-};
+// Allows one-off use of a type that wouldn't normally convert to a byte span.
+template <typename T>
+concept CanSafelyConvertNonUniqueToByteSpan =
+    // Non-trivially-copyable elements usually aren't safe even to serialize;
+    // when they are that's normally unconditionally true and can be handled
+    // using `kCanSafelyConvertToByteSpan`.
+    std::is_trivially_copyable_v<T> &&
+    // If this fails, `allow_nonunique_obj` wasn't necessary.
+    !std::has_unique_object_representations_v<T>;
 
-// must_not_be_dynamic_extent prevents |dynamic_extent| from being returned in a
-// constexpr context.
-template <size_t kExtent>
-constexpr size_t must_not_be_dynamic_extent() {
-  static_assert(
-      kExtent != dynamic_extent,
-      "EXTENT should only be used for containers with a static extent.");
-  return kExtent;
+template <typename T>
+concept ByteSpanConstructibleFromNonUnique =
+    SpanConstructibleFrom<T> &&
+    CanSafelyConvertNonUniqueToByteSpan<ElementTypeOfSpanConstructedFrom<T>>;
+
+template <typename ByteType,
+          typename ElementType,
+          size_t Extent,
+          typename InternalPtrType>
+  requires((std::same_as<std::remove_const_t<ByteType>, char> ||
+            std::same_as<std::remove_const_t<ByteType>, unsigned char>) &&
+           (std::is_const_v<ByteType> || !std::is_const_v<ElementType>))
+constexpr auto as_byte_span(
+    span<ElementType, Extent, InternalPtrType> s) noexcept {
+  constexpr size_t kByteExtent =
+      Extent == dynamic_extent ? dynamic_extent : sizeof(ElementType) * Extent;
+  // SAFETY: `s.data()` points to at least `s.size_bytes()` bytes' worth of
+  // valid elements, so the size computed below must only contain valid
+  // elements. Since `ByteType` is an alias to a character type, it has a size
+  // of 1 byte, the resulting pointer has no alignment concerns, and it is not
+  // UB to access memory contents inside the allocation through it.
+  return UNSAFE_BUFFERS(span<ByteType, kByteExtent>(
+      reinterpret_cast<ByteType*>(s.data()), s.size_bytes()));
 }
 
 }  // namespace internal
 
-// A span is a value type that represents an array of elements of type T. Since
-// it only consists of a pointer to memory with an associated size, it is very
-// light-weight. It is cheap to construct, copy, move and use spans, so that
-// users are encouraged to use it as a pass-by-value parameter. A span does not
-// own the underlying memory, so care must be taken to ensure that a span does
-// not outlive the backing store.
-//
-// span is somewhat analogous to std::string_view, but with arbitrary element
-// types, allowing mutation if T is non-const.
-//
-// span is implicitly convertible from C++ arrays, as well as most [1]
-// container-like types that provide a data() and size() method (such as
-// std::vector<T>). A mutable span<T> can also be implicitly converted to an
-// immutable span<const T>.
-//
-// Consider using a span for functions that take a data pointer and size
-// parameter: it allows the function to still act on an array-like type, while
-// allowing the caller code to be a bit more concise.
-//
-// For read-only data access pass a span<const T>: the caller can supply either
-// a span<const T> or a span<T>, while the callee will have a read-only view.
-// For read-write access a mutable span<T> is required.
-//
-// Without span:
-//   Read-Only:
-//     // std::string HexEncode(const uint8_t* data, size_t size);
-//     std::vector<uint8_t> data_buffer = GenerateData();
-//     std::string r = HexEncode(data_buffer.data(), data_buffer.size());
-//
-//  Mutable:
-//     // ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, Args...);
-//     char str_buffer[100];
-//     SafeSNPrintf(str_buffer, sizeof(str_buffer), "Pi ~= %lf", 3.14);
-//
-// With span:
-//   Read-Only:
-//     // std::string HexEncode(gurl_base::span<const uint8_t> data);
-//     std::vector<uint8_t> data_buffer = GenerateData();
-//     std::string r = HexEncode(data_buffer);
-//
-//  Mutable:
-//     // ssize_t SafeSNPrintf(gurl_base::span<char>, const char* fmt, Args...);
-//     char str_buffer[100];
-//     SafeSNPrintf(str_buffer, "Pi ~= %lf", 3.14);
-//
-// Spans with "const" and pointers
-// -------------------------------
-//
-// Const and pointers can get confusing. Here are vectors of pointers and their
-// corresponding spans:
-//
-//   const std::vector<int*>        =>  gurl_base::span<int* const>
-//   std::vector<const int*>        =>  gurl_base::span<const int*>
-//   const std::vector<const int*>  =>  gurl_base::span<const int* const>
-//
-// Differences from the C++20 draft
-// --------------------------------
-//
-// http://eel.is/c++draft/views contains the latest C++20 draft of std::span.
-// Chromium tries to follow the draft as close as possible. Differences between
-// the draft and the implementation are documented in subsections below.
-//
-// Differences from [span.objectrep]:
-// - as_bytes() and as_writable_bytes() return spans of uint8_t instead of
-//   std::byte (std::byte is a C++17 feature)
-//
-// Differences from [span.cons]:
-// - Constructing a static span (i.e. Extent != dynamic_extent) from a dynamic
-//   sized container (e.g. std::vector) requires an explicit conversion (in the
-//   C++20 draft this is simply UB)
-//
-// Additions beyond the C++20 draft
-// - as_byte_span() function.
-//
-// Furthermore, all constructors and methods are marked noexcept due to the lack
-// of exceptions in Chromium.
-//
-// Due to the lack of class template argument deduction guides in C++14
-// appropriate make_span() utility functions are provided.
-
-// [span], class template span
-template <typename T, size_t Extent, typename InternalPtrType>
-class GSL_POINTER span : public internal::ExtentStorage<Extent> {
- private:
-  using ExtentStorage = internal::ExtentStorage<Extent>;
-
+// [span]: class `span` (non-dynamic `Extent`s)
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+class GSL_POINTER span {
  public:
-  using element_type = T;
-  using value_type = std::remove_cv_t<T>;
+  using element_type = ElementType;
+  using value_type = std::remove_cv_t<element_type>;
   using size_type = size_t;
   using difference_type = ptrdiff_t;
-  using pointer = T*;
-  using const_pointer = const T*;
-  using reference = T&;
-  using const_reference = const T&;
-  using iterator = CheckedContiguousIterator<T>;
+  using pointer = element_type*;
+  using const_pointer = const element_type*;
+  using reference = element_type&;
+  using const_reference = const element_type&;
+  using iterator = CheckedContiguousIterator<element_type>;
+  using const_iterator = CheckedContiguousConstIterator<element_type>;
   using reverse_iterator = std::reverse_iterator<iterator>;
-  static constexpr size_t extent = Extent;
+  // TODO(C++23): When `std::const_iterator<>` is available, switch to
+  // `std::const_iterator<reverse_iterator>` as the standard specifies.
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+  static constexpr size_type extent = Extent;
 
-  // [span.cons], span constructors, copy, assignment, and destructor
-  constexpr span() noexcept : ExtentStorage(0), data_(nullptr) {
-    static_assert(Extent == dynamic_extent || Extent == 0, "Invalid Extent");
+  // [span.cons]: Constructors, copy, and assignment
+  // Default constructor.
+  constexpr span() noexcept
+    requires(extent == 0)
+  = default;
+
+  // Iterator + count.
+  template <typename It>
+    requires(internal::CompatibleIter<element_type, It>)
+  // PRECONDITIONS: `first` must point to the first of at least `count`
+  // contiguous valid elements.
+  UNSAFE_BUFFER_USAGE constexpr span(It first, StrictNumeric<size_type> count)
+      : data_(to_address(first)) {
+    GURL_CHECK(size_type{count} == extent);
+
+    // Non-zero `count` implies non-null `data_`. Use `SpanOrSize<T>` to
+    // represent a size that might not be accompanied by the actual data.
+    GURL_DCHECK(count == 0 || !!data_);
   }
 
-  template <typename It,
-            typename = internal::EnableIfCompatibleContiguousIterator<It, T>>
-  constexpr span(It first, StrictNumeric<size_t> count) noexcept
-      : ExtentStorage(count),
-        // The use of to_address() here is to handle the case where the iterator
-        // `first` is pointing to the container's `end()`. In that case we can
-        // not use the address returned from the iterator, or dereference it
-        // through the iterator's `operator*`, but we can store it. We must
-        // assume in this case that `count` is 0, since the iterator does not
-        // point to valid data. Future hardening of iterators may disallow
-        // pulling the address from `end()`, as demonstrated by asserts() in
-        // libstdc++: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93960.
-        //
-        // The span API dictates that the `data()` is accessible when size is 0,
-        // since the pointer may be valid, so we cannot prevent storing and
-        // giving out an invalid pointer here without breaking API compatibility
-        // and our unit tests. Thus protecting against this can likely only be
-        // successful from inside iterators themselves, where the context about
-        // the pointer is known.
-        //
-        // We can not protect here generally against an invalid iterator/count
-        // being passed in, since we have no context to determine if the
-        // iterator or count are valid.
-        data_(std::to_address(first)) {
-    GURL_CHECK(Extent == dynamic_extent || Extent == count);
+  // Iterator + sentinel.
+  template <typename It, typename End>
+    requires(internal::CompatibleIter<element_type, It> &&
+             std::sized_sentinel_for<End, It> &&
+             !std::is_convertible_v<End, size_t>)
+  // PRECONDITIONS: `first` and `last` must be for the same allocation and all
+  // elements in the range [first, last) must be valid.
+  UNSAFE_BUFFER_USAGE constexpr span(It first, End last)
+      // SAFETY: The caller must guarantee that `first` and `last` point into
+      // the same allocation. In this case, the extent will be the number of
+      // elements between the iterators and thus a valid size for the pointer to
+      // the element at `first`.
+      //
+      // It is safe to check for underflow after subtraction because the
+      // underflow itself is not UB and `size_` is not converted to an invalid
+      // pointer (which would be UB) before the check.
+      : UNSAFE_BUFFERS(span(first, static_cast<size_type>(last - first))) {
+    // Verify `last - first` did not underflow.
+    GURL_CHECK(first <= last);
   }
 
-  template <typename It,
-            typename End,
-            typename = internal::EnableIfCompatibleContiguousIterator<It, T>,
-            typename = std::enable_if_t<!std::is_convertible_v<End, size_t>>>
-  constexpr span(It begin, End end) noexcept
-      // Subtracting two iterators gives a ptrdiff_t, but the result should be
-      // non-negative: see GURL_CHECK below.
-      : span(begin, static_cast<size_t>(end - begin)) {
-    // Note: GURL_CHECK_LE is not constexpr, hence regular GURL_CHECK must be used.
-    GURL_CHECK(begin <= end);
-  }
+  // Array of size `extent`.
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(element_type (&arr LIFETIME_BOUND)[extent]) noexcept
+      // SAFETY: The type signature guarantees `arr` contains `extent` elements.
+      : UNSAFE_BUFFERS(span(arr, extent)) {}
 
-  template <
-      size_t N,
-      typename = internal::EnableIfSpanCompatibleArray<T (&)[N], T, Extent>>
-  constexpr span(T (&array)[N]) noexcept : span(std::data(array), N) {}
+  // Range.
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(internal::CompatibleRange<element_type, R> &&
+             internal::FixedExtentConstructibleFromExtent<extent, N>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr explicit(N != extent) span(R&& range LIFETIME_BOUND)
+      // SAFETY: `std::ranges::size()` returns the number of elements
+      // `std::ranges::data()` will point to, so accessing those elements will
+      // be safe.
+      : UNSAFE_BUFFERS(
+            span(std::ranges::data(range), std::ranges::size(range))) {}
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(internal::CompatibleRange<element_type, R> &&
+             internal::FixedExtentConstructibleFromExtent<extent, N> &&
+             std::ranges::borrowed_range<R>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr explicit(N != extent) span(R&& range)
+      // SAFETY: `std::ranges::size()` returns the number of elements
+      // `std::ranges::data()` will point to, so accessing those elements will
+      // be safe.
+      : UNSAFE_BUFFERS(
+            span(std::ranges::data(range), std::ranges::size(range))) {}
 
-  template <
-      typename U,
-      size_t N,
-      typename =
-          internal::EnableIfSpanCompatibleArray<std::array<U, N>&, T, Extent>>
-  constexpr span(std::array<U, N>& array) noexcept
-      : span(std::data(array), N) {}
+  // Initializer list.
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr explicit span(std::initializer_list<value_type> il LIFETIME_BOUND)
+    requires(std::is_const_v<element_type>)
+      // SAFETY: `size()` is exactly the number of elements in the initializer
+      // list, so accessing that many will be safe.
+      : UNSAFE_BUFFERS(span(il.begin(), il.size())) {}
 
-  template <typename U,
-            size_t N,
-            typename = internal::
-                EnableIfSpanCompatibleArray<const std::array<U, N>&, T, Extent>>
-  constexpr span(const std::array<U, N>& array) noexcept
-      : span(std::data(array), N) {}
-
-  // Conversion from a container that has compatible std::data() and integral
-  // std::size().
-  template <
-      typename Container,
-      typename =
-          internal::EnableIfSpanCompatibleContainerAndSpanIsDynamic<Container&,
-                                                                    T,
-                                                                    Extent>>
-  constexpr span(Container& container) noexcept
-      : span(std::data(container), std::size(container)) {}
-
-  template <
-      typename Container,
-      typename = internal::EnableIfSpanCompatibleContainerAndSpanIsDynamic<
-          const Container&,
-          T,
-          Extent>>
-  constexpr span(const Container& container) noexcept
-      : span(std::data(container), std::size(container)) {}
-
+  // Copy and move.
   constexpr span(const span& other) noexcept = default;
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires((OtherExtent == dynamic_extent || extent == OtherExtent) &&
+             internal::LegalDataConversion<OtherElementType, element_type>)
+  constexpr explicit(OtherExtent == dynamic_extent)
+      span(const span<OtherElementType, OtherExtent, OtherInternalPtrType>&
+               other) noexcept
+      // SAFETY: `size()` is the number of elements that can be safely accessed
+      // at `data()`.
+      : UNSAFE_BUFFERS(span(other.data(), other.size())) {}
+  constexpr span(span&& other) noexcept = default;
 
-  // Conversions from spans of compatible types and extents: this allows a
-  // span<T> to be seamlessly used as a span<const T>, but not the other way
-  // around. If extent is not dynamic, OtherExtent has to be equal to Extent.
-  template <
-      typename U,
-      size_t OtherExtent,
-      typename =
-          internal::EnableIfLegalSpanConversion<U, OtherExtent, T, Extent>>
-  constexpr span(const span<U, OtherExtent>& other)
-      : span(other.data(), other.size()) {}
-
+  // Copy and move assignment.
   constexpr span& operator=(const span& other) noexcept = default;
-  ~span() noexcept = default;
+  constexpr span& operator=(span&& other) noexcept = default;
 
-  // [span.sub], span subviews
-  template <size_t Count>
-  constexpr span<T, Count> first() const noexcept {
-    static_assert(Count <= Extent, "Count must not exceed Extent");
-    GURL_CHECK(Extent != dynamic_extent || Count <= size());
-    return {data(), Count};
+  // Performs a deep copy of the elements referenced by `other` to those
+  // referenced by `this`. The spans must be the same size.
+  //
+  // If it's known the spans can not overlap, `copy_from_nonoverlapping()`
+  // provides an unsafe alternative that avoids intermediate copies.
+  //
+  // (Not in `std::`; inspired by Rust's `slice::copy_from_slice()`.)
+  constexpr void copy_from(span<const element_type, extent> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    if (std::is_constant_evaluated()) {
+      // Comparing pointers to different objects at compile time yields
+      // unspecified behavior, which would halt compilation. Instead,
+      // unconditionally use a separate buffer in the constexpr context. This
+      // would be inefficient at runtime, but that's irrelevant.
+
+      // operator[] does not exist if extent == 0.
+      if constexpr (extent > 0) {
+        // Hold each value to be copied in a union so `element_type` does not
+        // need to be default constructible.
+        union Holder {
+          constexpr Holder() {}
+          constexpr ~Holder() {}
+          element_type value;
+        };
+        // std::unique_ptr<T[]> isn't constexpr enough prior to C++23; another
+        // alternative is std::vector, but that requires including <vector> just
+        // for this edge case.
+        Holder* buffer = new Holder[extent];
+        for (size_t i = 0; i < extent; ++i) {
+          // SAFETY: `buffers` is allocated with `extent` elements, and the loop
+          // body only executes if `i < extent`.
+          std::construct_at(&UNSAFE_BUFFERS(buffer[i]).value, other[i]);
+        }
+        for (size_t i = 0; i < extent; ++i) {
+          // SAFETY: `buffers` is allocated with `extent` elements, and the loop
+          // body only executes if `i < extent`.
+          (*this)[i] = UNSAFE_BUFFERS(buffer[i]).value;
+          UNSAFE_BUFFERS(buffer[i]).value.~element_type();
+        }
+        delete[] buffer;
+      }
+    } else {
+      // Using `<=` to compare pointers to different allocations is UB;
+      // reinterpret_cast is the workaround.
+      if (reinterpret_cast<uintptr_t>(to_address(begin())) <=
+          reinterpret_cast<uintptr_t>(to_address(other.begin()))) {
+        std::ranges::copy(other, begin());
+      } else {
+        std::ranges::copy_backward(other, end());
+      }
+    }
+  }
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(!std::is_const_v<element_type> &&
+             // Fixed-extent ranges should implicitly convert to use the
+             // overload above; if they don't, it's because the extent doesn't
+             // match. Rejecting this here improves the resulting errors.
+             N == dynamic_extent &&
+             std::convertible_to<R &&, span<const element_type>>)
+  constexpr void copy_from(R&& other) {
+    // Note: The constructor `GURL_CHECK()`s that a dynamic-extent `other` has the
+    // right size.
+    copy_from(span<const element_type, extent>(std::forward<R>(other)));
   }
 
-  template <size_t Count>
-  constexpr span<T, Count> last() const noexcept {
-    static_assert(Count <= Extent, "Count must not exceed Extent");
-    GURL_CHECK(Extent != dynamic_extent || Count <= size());
-    return {data() + (size() - Count), Count};
+  // Like `copy_from()`, but may be more performant; however, the caller must
+  // guarantee the spans do not overlap, or this will invoke UB.
+  //
+  // (Not in `std::`; inspired by Rust's `slice::copy_from_slice()`.)
+  constexpr void copy_from_nonoverlapping(
+      span<const element_type, extent> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    // Comparing pointers to different objects at compile time yields
+    // unspecified behavior, which would halt compilation. Instead implement in
+    // terms of the guaranteed-safe behavior; performance is irrelevant in the
+    // constexpr context.
+    if (std::is_constant_evaluated()) {
+      copy_from(other);
+      return;
+    }
+
+    // See comments in `copy_from()` re: use of templated comparison objects.
+    GURL_DCHECK(reinterpret_cast<uintptr_t>(to_address(end())) <=
+               reinterpret_cast<uintptr_t>(to_address(other.begin())) ||
+           reinterpret_cast<uintptr_t>(to_address(begin())) >=
+               reinterpret_cast<uintptr_t>(to_address(other.end())));
+    std::ranges::copy(other, begin());
+  }
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(!std::is_const_v<element_type> && N == dynamic_extent &&
+             std::convertible_to<R &&, span<const element_type>>)
+  constexpr void copy_from_nonoverlapping(R&& other) {
+    // Note: The constructor `GURL_CHECK()`s that a dynamic-extent `other` has the
+    // right size.
+    copy_from_nonoverlapping(
+        span<const element_type, extent>(std::forward<R>(other)));
   }
 
+  // Like `copy_from()`, but allows the source to be smaller than this span, and
+  // will only copy as far as the source size, leaving the remaining elements of
+  // this span unwritten.
+  //
+  // (Not in `std::`; allows caller code to elide repeated size information and
+  // makes it easier to preserve fixed-extent spans in the process.)
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(!std::is_const_v<element_type> &&
+             (N <= extent || N == dynamic_extent) &&
+             std::convertible_to<R &&, span<const element_type>>)
+  constexpr void copy_prefix_from(R&& other) {
+    if constexpr (N == dynamic_extent) {
+      return first(other.size()).copy_from(other);
+    } else {
+      return first<N>().copy_from(other);
+    }
+  }
+
+  // Implicit conversion to fixed-extent `std::span<>`. (The fixed-extent
+  // `std::span` range constructor is explicit.)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  operator std::span<element_type, extent>() const {
+    return std::span<element_type, extent>(*this);
+  }
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  operator std::span<const element_type, extent>() const
+    requires(!std::is_const_v<element_type>)
+  {
+    return std::span<const element_type, extent>(*this);
+  }
+
+  // [span.sub]: Subviews
+  // First `count` elements.
+  template <size_t Count>
+  constexpr auto first() const
+    requires(Count <= extent)
+  {
+    // SAFETY: `data()` points to at least `extent` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(span<element_type, Count>(data(), Count));
+  }
+  constexpr auto first(StrictNumeric<size_type> count) const {
+    GURL_CHECK(size_type{count} <= extent);
+    // SAFETY: `data()` points to at least `extent` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(span<element_type>(data(), count));
+  }
+
+  // Last `count` elements.
+  template <size_t Count>
+  constexpr auto last() const
+    requires(Count <= extent)
+  {
+    // SAFETY: `data()` points to at least `extent` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(
+        span<element_type, Count>(data() + (extent - Count), Count));
+  }
+  constexpr auto last(StrictNumeric<size_type> count) const {
+    GURL_CHECK(size_type{count} <= extent);
+    // SAFETY: `data()` points to at least `extent` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + (extent - size_type{count}), count));
+  }
+
+  // `count` elements beginning at `offset`.
   template <size_t Offset, size_t Count = dynamic_extent>
-  constexpr span<T,
-                 (Count != dynamic_extent
-                      ? Count
-                      : (Extent != dynamic_extent ? Extent - Offset
-                                                  : dynamic_extent))>
-  subspan() const noexcept {
-    static_assert(Offset <= Extent, "Offset must not exceed Extent");
-    static_assert(Count == dynamic_extent || Count <= Extent - Offset,
-                  "Count must not exceed Extent - Offset");
-    GURL_CHECK(Extent != dynamic_extent || Offset <= size());
-    GURL_CHECK(Extent != dynamic_extent || Count == dynamic_extent ||
-          Count <= size() - Offset);
-    return {data() + Offset, Count != dynamic_extent ? Count : size() - Offset};
+  constexpr auto subspan() const
+    requires(Offset <= extent &&
+             (Count == dynamic_extent || Count <= extent - Offset))
+  {
+    if constexpr (Count == dynamic_extent) {
+      constexpr size_t kRemaining = extent - Offset;
+      // SAFETY: `data()` points to at least `extent` elements, so `Offset`
+      // specifies a valid element index or the past-the-end index, and
+      // `kRemaining` cannot index past-the-end elements.
+      return UNSAFE_BUFFERS(
+          span<element_type, kRemaining>(data() + Offset, kRemaining));
+    } else {
+      // SAFETY: `data()` points to at least `extent` elements, so `Offset`
+      // specifies a valid element index or the past-the-end index, and `Count`
+      // is no larger than the number of remaining valid elements.
+      return UNSAFE_BUFFERS(span<element_type, Count>(data() + Offset, Count));
+    }
+  }
+  constexpr auto subspan(StrictNumeric<size_type> offset) const {
+    GURL_CHECK(size_type{offset} <= extent);
+    const size_type remaining = extent - size_type{offset};
+    // SAFETY: `data()` points to at least `extent` elements, so `offset`
+    // specifies a valid element index or the past-the-end index, and
+    // `remaining` cannot index past-the-end elements.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + size_type{offset}, remaining));
+  }
+  constexpr auto subspan(StrictNumeric<size_type> offset,
+                         StrictNumeric<size_type> count) const {
+    // base does not allow dynamic_extent in two-arg subspan().
+    GURL_DCHECK(size_type{count} != dynamic_extent);
+    // Deliberately combine tests to minimize code size.
+    GURL_CHECK(size_type{offset} <= size() &&
+          size_type{count} <= size() - size_type{offset});
+    // SAFETY: `data()` points to at least `extent` elements, so `offset`
+    // specifies a valid element index or the past-the-end index, and `count` is
+    // no larger than the number of remaining valid elements.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + size_type{offset}, count));
   }
 
-  constexpr span<T, dynamic_extent> first(size_t count) const noexcept {
-    // Note: GURL_CHECK_LE is not constexpr, hence regular GURL_CHECK must be used.
-    GURL_CHECK(count <= size());
-    return {data(), count};
+  // Splits a span a given offset, returning a pair of spans that cover the
+  // ranges strictly before the offset and starting at the offset, respectively.
+  //
+  // (Not in `std::span`; inspired by Rust's `slice::split_at()` and
+  // `split_at_mut()`.)
+  template <size_t Offset>
+    requires(Offset <= extent)
+  constexpr auto split_at() const {
+    return std::pair(first<Offset>(), subspan<Offset, extent - Offset>());
+  }
+  constexpr auto split_at(StrictNumeric<size_type> offset) const {
+    return std::pair(first(offset), subspan(offset));
   }
 
-  constexpr span<T, dynamic_extent> last(size_t count) const noexcept {
-    // Note: GURL_CHECK_LE is not constexpr, hence regular GURL_CHECK must be used.
-    GURL_CHECK(count <= size());
-    return {data() + (size() - count), count};
+  // [span.obs]: Observers
+  // Size.
+  constexpr size_type size() const noexcept { return extent; }
+  constexpr size_type size_bytes() const noexcept {
+    return extent * sizeof(element_type);
   }
 
-  constexpr span<T, dynamic_extent> subspan(size_t offset,
-                                            size_t count = dynamic_extent) const
-      noexcept {
-    // Note: GURL_CHECK_LE is not constexpr, hence regular GURL_CHECK must be used.
-    GURL_CHECK(offset <= size());
-    GURL_CHECK(count == dynamic_extent || count <= size() - offset);
-    return {data() + offset, count != dynamic_extent ? count : size() - offset};
+  // Empty.
+  [[nodiscard]] constexpr bool empty() const noexcept { return extent == 0; }
+
+  // Returns true if `lhs` and `rhs` are equal-sized and are per-element equal.
+  //
+  // (Not in `std::span`; improves both ergonomics and safety.)
+  //
+  // NOTE: Using non-members here intentionally allows comparing types that
+  // implicitly convert to `span`.
+  friend constexpr bool operator==(span lhs, span rhs)
+    requires(std::is_const_v<element_type> &&
+             std::equality_comparable<const element_type>)
+  {
+    return std::ranges::equal(span<const element_type, extent>(lhs),
+                              span<const element_type, extent>(rhs));
+  }
+  friend constexpr bool operator==(span lhs,
+                                   span<const element_type, extent> rhs)
+    requires(!std::is_const_v<element_type> &&
+             std::equality_comparable<const element_type>)
+  {
+    return std::ranges::equal(span<const element_type, extent>(lhs), rhs);
+  }
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires((OtherExtent == dynamic_extent || extent == OtherExtent) &&
+             std::equality_comparable_with<const element_type,
+                                           const OtherElementType>)
+  friend constexpr bool operator==(
+      span lhs,
+      span<OtherElementType, OtherExtent, OtherInternalPtrType> rhs) {
+    return std::ranges::equal(span<const element_type, extent>(lhs),
+                              span<const OtherElementType, OtherExtent>(rhs));
   }
 
-  // [span.obs], span observers
-  constexpr size_t size() const noexcept { return ExtentStorage::size(); }
-  constexpr size_t size_bytes() const noexcept { return size() * sizeof(T); }
-  [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; }
-
-  // [span.elem], span element access
-  constexpr T& operator[](size_t idx) const noexcept {
-    // Note: GURL_CHECK_LT is not constexpr, hence regular GURL_CHECK must be used.
-    GURL_CHECK(idx < size());
-    return *(data() + idx);
+  // Performs lexicographical comparison of `lhs` and `rhs`.
+  //
+  // (Not in `std::span`; improves both ergonomics and safety.)
+  //
+  // NOTE: Using non-members here intentionally allows comparing types that
+  // implicitly convert to `span`.
+  friend constexpr auto operator<=>(span lhs, span rhs)
+    requires(std::is_const_v<element_type> &&
+             std::three_way_comparable<const element_type>)
+  {
+    const auto const_lhs = span<const element_type>(lhs);
+    const auto const_rhs = span<const element_type>(rhs);
+    return std::lexicographical_compare_three_way(
+        const_lhs.begin(), const_lhs.end(), const_rhs.begin(), const_rhs.end());
+  }
+  friend constexpr auto operator<=>(span lhs,
+                                    span<const element_type, extent> rhs)
+    requires(!std::is_const_v<element_type> &&
+             std::three_way_comparable<const element_type>)
+  {
+    return span<const element_type>(lhs) <=> rhs;
+  }
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires((OtherExtent == dynamic_extent || extent == OtherExtent) &&
+             std::three_way_comparable_with<const element_type,
+                                            const OtherElementType>)
+  friend constexpr auto operator<=>(
+      span lhs,
+      span<OtherElementType, OtherExtent, OtherInternalPtrType> rhs) {
+    const auto const_lhs = span<const element_type>(lhs);
+    const auto const_rhs = span<const OtherElementType, OtherExtent>(rhs);
+    return std::lexicographical_compare_three_way(
+        const_lhs.begin(), const_lhs.end(), const_rhs.begin(), const_rhs.end());
   }
 
-  constexpr T& front() const noexcept {
-    static_assert(Extent == dynamic_extent || Extent > 0,
-                  "Extent must not be 0");
-    GURL_CHECK(Extent != dynamic_extent || !empty());
-    return *data();
+  template <typename H>
+  friend H AbslHashValue(H h, span v) {
+    return H::combine_contiguous(std::move(h), v.data(), v.size());
   }
 
-  constexpr T& back() const noexcept {
-    static_assert(Extent == dynamic_extent || Extent > 0,
-                  "Extent must not be 0");
-    GURL_CHECK(Extent != dynamic_extent || !empty());
-    return *(data() + size() - 1);
+  // [span.elem]: Element access
+  // Reference to specific element.
+  // When `idx` is outside the span, the underlying call will `GURL_CHECK()`.
+  //
+  // Intentionally does not take `StrictNumeric<size_t>`, unlike all other APIs.
+  // There are far too many false positives on integer literals (e.g. `s[0]`),
+  // and while `ENABLE_IF_ATTR` can be used to work around those for Clang, that
+  // would leave the gcc build broken. The consequence of not upgrading this is
+  // that some errors will only be detected at runtime instead of compile time.
+  constexpr reference operator[](size_type idx) const
+    requires(extent > 0)
+  {
+    return at(idx);
+  }
+  // When `idx` is outside the span, the underlying call will `GURL_CHECK()`.
+  constexpr reference at(StrictNumeric<size_type> idx) const
+    requires(extent > 0)
+  {
+    return *get_at(idx);
   }
 
-  constexpr T* data() const noexcept { return data_; }
+  // Returns a pointer to an element in the span.
+  //
+  // (Not in `std::`; necessary when underlying memory is not yet initialized.)
+  constexpr pointer get_at(StrictNumeric<size_type> idx) const
+    requires(extent > 0)
+  {
+    GURL_CHECK(size_type{idx} < extent);
+    // SAFETY: `data()` points to at least `extent` elements, so `idx` must be
+    // the index of a valid element.
+    return UNSAFE_BUFFERS(data() + size_type{idx});
+  }
 
-  // [span.iter], span iterator support
+  // Reference to first/last elements.
+  // When `empty()`, the underlying call will `GURL_CHECK()`.
+  constexpr reference front() const
+    requires(extent > 0)
+  {
+    return operator[](0);
+  }
+  // When `empty()`, the underlying call will `GURL_CHECK()`.
+  constexpr reference back() const
+    requires(extent > 0)
+  {
+    return operator[](size() - 1);
+  }
+
+  // Underlying memory.
+  constexpr pointer data() const noexcept { return data_; }
+
+  // [span.iter]: Iterator support
+  // Forward iterators.
   constexpr iterator begin() const noexcept {
-    return iterator(data(), data() + size());
+    // SAFETY: `data()` points to at least `extent` elements, so `data() +
+    // extent` is no larger than just past the end of the corresponding
+    // allocation, which is a legal pointer to construct and compare to (though
+    // not dereference).
+    //
+    // Use `AssumeValid()` to elide unnecessary precondition `GURL_CHECK()`'s in the
+    // iterator constructor: `data() + extent` must not overflow given the above
+    // constraints, so the iterator's requirement that begin <= current <= end
+    // is guaranteed to be true.
+    return UNSAFE_BUFFERS(iterator(
+        typename iterator::AssumeValid(data(), data(), data() + extent)));
   }
-
+  constexpr const_iterator cbegin() const noexcept {
+    return const_iterator(begin());
+  }
   constexpr iterator end() const noexcept {
-    return iterator(data(), data() + size(), data() + size());
+    // SAFETY: `data()` points to at least `extent` elements, so `data() +
+    // extent` is no larger than just past the end of the corresponding
+    // allocation, which is a legal pointer to construct and compare to (though
+    // not dereference).
+    //
+    // Use `AssumeValid()` to elide unnecessary precondition `GURL_CHECK()`'s in the
+    // iterator constructor: `data() + extent` must not overflow given the above
+    // constraints, so the iterator's requirement that begin <= current <= end
+    // is guaranteed to be true.
+    return UNSAFE_BUFFERS(iterator(typename iterator::AssumeValid(
+        data(), data() + extent, data() + extent)));
+  }
+  constexpr const_iterator cend() const noexcept {
+    return const_iterator(end());
   }
 
+  // Reverse iterators.
   constexpr reverse_iterator rbegin() const noexcept {
     return reverse_iterator(end());
   }
-
+  constexpr const_reverse_iterator crbegin() const noexcept {
+    return const_iterator(rbegin());
+  }
   constexpr reverse_iterator rend() const noexcept {
     return reverse_iterator(begin());
   }
+  constexpr const_reverse_iterator crend() const noexcept {
+    return const_iterator(rend());
+  }
 
  private:
-  // This field is not a raw_ptr<> because it was filtered by the rewriter
-  // for: #constexpr-ctor-field-initializer, #global-scope, #union
-  InternalPtrType data_;
+  InternalPtrType data_ = nullptr;
 };
 
-// span<T, Extent>::extent can not be declared inline prior to C++17, hence this
-// definition is required.
-template <class T, size_t Extent, typename InternalPtrType>
-constexpr size_t span<T, Extent, InternalPtrType>::extent;
+// [span]: class <span> (dynamic `Extent`)
+template <typename ElementType, typename InternalPtrType>
+class GSL_POINTER span<ElementType, dynamic_extent, InternalPtrType> {
+ public:
+  using element_type = ElementType;
+  using value_type = std::remove_cv_t<element_type>;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
+  using pointer = element_type*;
+  using const_pointer = const element_type*;
+  using reference = element_type&;
+  using const_reference = const element_type&;
+  using iterator = CheckedContiguousIterator<element_type>;
+  using const_iterator = CheckedContiguousConstIterator<element_type>;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  // TODO(C++23): When `std::const_iterator<>` is available, switch to
+  // `std::const_iterator<reverse_iterator>` as the standard specifies.
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+  static constexpr size_type extent = dynamic_extent;
 
-template <typename It,
-          typename T = std::remove_reference_t<iter_reference_t<It>>>
-span(It, StrictNumeric<size_t>) -> span<T>;
+  // [span.cons]: Constructors, copy, and assignment
+  // Default constructor.
+  constexpr span() noexcept = default;
 
-template <typename It,
-          typename End,
-          typename = std::enable_if_t<!std::is_convertible_v<End, size_t>>,
-          typename T = std::remove_reference_t<iter_reference_t<It>>>
-span(It, End) -> span<T>;
+  // Iterator + count.
+  template <typename It>
+    requires(internal::CompatibleIter<element_type, It>)
+  // PRECONDITIONS: `first` must point to the first of at least `count`
+  // contiguous valid elements.
+  UNSAFE_BUFFER_USAGE constexpr span(It first, StrictNumeric<size_type> count)
+      : data_(to_address(first)), size_(count) {
+    // Non-zero `count` implies non-null `data_`. Use `SpanOrSize<T>` to
+    // represent a size that might not be accompanied by the actual data.
+    GURL_DCHECK(count == 0 || !!data_);
+  }
 
-template <typename T, size_t N>
-span(T (&)[N]) -> span<T, N>;
+  // Iterator + sentinel.
+  template <typename It, typename End>
+    requires(internal::CompatibleIter<element_type, It> &&
+             std::sized_sentinel_for<End, It> &&
+             !std::is_convertible_v<End, size_t>)
+  // PRECONDITIONS: `first` and `last` must be for the same allocation and all
+  // elements in the range [first, last) must be valid.
+  UNSAFE_BUFFER_USAGE constexpr span(It first, End last)
+      // SAFETY: The caller must guarantee that `first` and `last` point into
+      // the same allocation. In this case, `size_` will be the number of
+      // elements between the iterators and thus a valid size for the pointer to
+      // the element at `first`.
+      //
+      // It is safe to check for underflow after subtraction because the
+      // underflow itself is not UB and `size_` is not converted to an invalid
+      // pointer (which would be UB) before the check.
+      : UNSAFE_BUFFERS(span(first, static_cast<size_type>(last - first))) {
+    // Verify `last - first` did not underflow.
+    GURL_CHECK(first <= last);
+  }
 
-template <typename T, size_t N>
-span(std::array<T, N>&) -> span<T, N>;
+  // Array of size N.
+  template <size_t N>
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(element_type (&arr LIFETIME_BOUND)[N]) noexcept
+      // SAFETY: The type signature guarantees `arr` contains `N` elements.
+      : UNSAFE_BUFFERS(span(arr, N)) {}
 
-template <typename T, size_t N>
-span(const std::array<T, N>&) -> span<const T, N>;
+  // Range.
+  template <typename R>
+    requires(internal::CompatibleRange<element_type, R>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(R&& range LIFETIME_BOUND)
+      // SAFETY: `std::ranges::size()` returns the number of elements
+      // `std::ranges::data()` will point to, so accessing those elements will
+      // be safe.
+      : UNSAFE_BUFFERS(
+            span(std::ranges::data(range), std::ranges::size(range))) {}
+  template <typename R>
+    requires(internal::CompatibleRange<element_type, R> &&
+             std::ranges::borrowed_range<R>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(R&& range)
+      // SAFETY: `std::ranges::size()` returns the number of elements
+      // `std::ranges::data()` will point to, so accessing those elements will
+      // be safe.
+      : UNSAFE_BUFFERS(
+            span(std::ranges::data(range), std::ranges::size(range))) {}
 
-template <typename Container,
-          typename T = std::remove_pointer_t<
-              decltype(std::data(std::declval<Container>()))>,
-          size_t X = internal::Extent<Container>::value>
-span(Container&&) -> span<T, X>;
+  // Initializer list.
+  constexpr span(std::initializer_list<value_type> il LIFETIME_BOUND)
+    requires(std::is_const_v<element_type>)
+      // SAFETY: `size()` is exactly the number of elements in the initializer
+      // list, so accessing that many will be safe.
+      : UNSAFE_BUFFERS(span(il.begin(), il.size())) {}
 
-// [span.objectrep], views of object representation
-template <typename T, size_t X>
-inline span<const uint8_t,
-           (X == dynamic_extent ? dynamic_extent : sizeof(T) * X)>
-as_bytes(span<T, X> s) noexcept {
-  return {reinterpret_cast<const uint8_t*>(s.data()), s.size_bytes()};
-}
+  // Copy and move.
+  constexpr span(const span& other) noexcept = default;
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires(internal::LegalDataConversion<OtherElementType, element_type>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(
+      const span<OtherElementType, OtherExtent, OtherInternalPtrType>&
+          other) noexcept
+      : data_(other.data()), size_(other.size()) {}
+  constexpr span(span&& other) noexcept = default;
 
-template <typename T,
-          size_t X,
-          typename = std::enable_if_t<!std::is_const_v<T>>>
-inline span<uint8_t, (X == dynamic_extent ? dynamic_extent : sizeof(T) * X)>
-as_writable_bytes(span<T, X> s) noexcept {
-  return {reinterpret_cast<uint8_t*>(s.data()), s.size_bytes()};
-}
+  // Copy and move assignment.
+  constexpr span& operator=(const span& other) noexcept = default;
+  constexpr span& operator=(span&& other) noexcept = default;
 
-// Type-deducing helpers for constructing a span.
-template <int&... ExplicitArgumentBarrier, typename It>
-constexpr auto make_span(It it, StrictNumeric<size_t> size) noexcept {
-  using T = std::remove_reference_t<iter_reference_t<It>>;
-  return span<T>(it, size);
-}
+  // Performs a deep copy of the elements referenced by `other` to those
+  // referenced by `this`. The spans must be the same size.
+  //
+  // If it's known the spans can not overlap, `copy_from_nonoverlapping()`
+  // provides an unsafe alternative that avoids intermediate copies.
+  //
+  // (Not in `std::`; inspired by Rust's `slice::copy_from_slice()`.)
+  constexpr void copy_from(span<const element_type> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    GURL_CHECK(size() == other.size());
+    if (std::is_constant_evaluated()) {
+      // Comparing pointers to different objects at compile time yields
+      // unspecified behavior, which would halt compilation. Instead,
+      // unconditionally use a separate buffer in the constexpr context. This
+      // would be inefficient at runtime, but that's irrelevant.
 
-template <int&... ExplicitArgumentBarrier,
-          typename It,
-          typename End,
-          typename = std::enable_if_t<!std::is_convertible_v<End, size_t>>>
-constexpr auto make_span(It it, End end) noexcept {
-  using T = std::remove_reference_t<iter_reference_t<It>>;
-  return span<T>(it, end);
-}
+      // Hold each value to be copied in a union so `element_type` does not
+      // need to be default constructible.
+      union Holder {
+        constexpr Holder() {}
+        constexpr ~Holder() {}
+        element_type value;
+      };
+      // std::unique_ptr<T[]> isn't constexpr enough prior to C++23; another
+      // alternative is std::vector, but that requires including <vector> just
+      // for this edge case.
+      Holder* buffer = new Holder[other.size()];
+      for (size_t i = 0; i < other.size(); ++i) {
+        // SAFETY: `buffers` is allocated with `other.size()` elements, and the
+        // loop body only executes if `i < other.size()`.
+        std::construct_at(&UNSAFE_BUFFERS(buffer[i]).value, other[i]);
+      }
+      for (size_t i = 0; i < other.size(); ++i) {
+        // SAFETY: `buffers` is allocated with `other.size()` elements, and the
+        // loop body only executes if `i < other.size()`.
+        (*this)[i] = UNSAFE_BUFFERS(buffer[i]).value;
+        UNSAFE_BUFFERS(buffer[i]).value.~element_type();
+      }
+      delete[] buffer;
+    } else {
+      // Using `<=` to compare pointers to different allocations is UB;
+      // reinterpret_cast is the workaround.
+      if (reinterpret_cast<uintptr_t>(to_address(begin())) <=
+          reinterpret_cast<uintptr_t>(to_address(other.begin()))) {
+        std::ranges::copy(other, begin());
+      } else {
+        std::ranges::copy_backward(other, end());
+      }
+    }
+  }
 
-// make_span utility function that deduces both the span's value_type and extent
-// from the passed in argument.
+  // Like `copy_from()`, but may be more performant; however, the caller must
+  // guarantee the spans do not overlap, or this will invoke UB.
+  //
+  // (Not in `std::`; inspired by Rust's `slice::copy_from_slice()`.)
+  constexpr void copy_from_nonoverlapping(span<const element_type> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    // Comparing pointers to different objects at compile time yields
+    // unspecified behavior, which would halt compilation. Instead implement in
+    // terms of the guaranteed-safe behavior; performance is irrelevant in the
+    // constexpr context.
+    if (std::is_constant_evaluated()) {
+      copy_from(other);
+      return;
+    }
+
+    GURL_CHECK(size() == other.size());
+    // See comments in `copy_from()` re: use of templated comparison objects.
+    GURL_DCHECK(reinterpret_cast<uintptr_t>(to_address(end())) <=
+               reinterpret_cast<uintptr_t>(to_address(other.begin())) ||
+           reinterpret_cast<uintptr_t>(to_address(begin())) >=
+               reinterpret_cast<uintptr_t>(to_address(other.end())));
+    std::ranges::copy(other, begin());
+  }
+
+  // Like `copy_from()`, but allows the source to be smaller than this span, and
+  // will only copy as far as the source size, leaving the remaining elements of
+  // this span unwritten.
+  //
+  // (Not in `std::`; allows caller code to elide repeated size information and
+  // makes it easier to preserve fixed-extent spans in the process.)
+  constexpr void copy_prefix_from(span<const element_type> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    return first(other.size()).copy_from(other);
+  }
+
+  // [span.sub]: Subviews
+  // First `count` elements.
+  template <size_t Count>
+  constexpr auto first() const {
+    GURL_CHECK(Count <= size());
+    // SAFETY: `data()` points to at least `size()` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(span<element_type, Count>(data(), Count));
+  }
+  constexpr auto first(StrictNumeric<size_t> count) const {
+    GURL_CHECK(size_type{count} <= size());
+    // SAFETY: `data()` points to at least `size()` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(span<element_type>(data(), count));
+  }
+
+  // Last `count` elements.
+  template <size_t Count>
+  constexpr auto last() const {
+    GURL_CHECK(Count <= size());
+    // SAFETY: `data()` points to at least `size()` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(
+        span<element_type, Count>(data() + (size() - Count), Count));
+  }
+  constexpr auto last(StrictNumeric<size_type> count) const {
+    GURL_CHECK(size_type{count} <= size());
+    // SAFETY: `data()` points to at least `size()` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + (size() - size_type{count}), count));
+  }
+
+  // `count` elements beginning at `offset`.
+  template <size_t Offset, size_t Count = dynamic_extent>
+  constexpr auto subspan() const {
+    GURL_CHECK(Offset <= size());
+    const size_type remaining = size() - Offset;
+    if constexpr (Count == dynamic_extent) {
+      // SAFETY: `data()` points to at least `size()` elements, so `Offset`
+      // specifies a valid element index or the past-the-end index, and
+      // `remaining` cannot index past-the-end elements.
+      return UNSAFE_BUFFERS(
+          span<element_type, Count>(data() + Offset, remaining));
+    }
+    GURL_CHECK(Count <= remaining);
+    // SAFETY: `data()` points to at least `size()` elements, so `Offset`
+    // specifies a valid element index or the past-the-end index, and `Count` is
+    // no larger than the number of remaining valid elements.
+    return UNSAFE_BUFFERS(span<element_type, Count>(data() + Offset, Count));
+  }
+  constexpr auto subspan(StrictNumeric<size_type> offset) const {
+    GURL_CHECK(size_type{offset} <= size());
+    const size_type remaining = size() - size_type{offset};
+    // SAFETY: `data()` points to at least `size()` elements, so `offset`
+    // specifies a valid element index or the past-the-end index, and
+    // `remaining` cannot index past-the-end elements.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + size_type{offset}, remaining));
+  }
+  constexpr auto subspan(StrictNumeric<size_type> offset,
+                         StrictNumeric<size_type> count) const {
+    // base does not allow dynamic_extent in two-arg subspan().
+    GURL_DCHECK(size_type{count} != dynamic_extent);
+    // Deliberately combine tests to minimize code size.
+    GURL_CHECK(size_type{offset} <= size() &&
+          size_type{count} <= size() - size_type{offset});
+    // SAFETY: `data()` points to at least `size()` elements, so `offset`
+    // specifies a valid element index or the past-the-end index, and `count` is
+    // no larger than the number of remaining valid elements.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + size_type{offset}, count));
+  }
+
+  // Splits a span a given offset, returning a pair of spans that cover the
+  // ranges strictly before the offset and starting at the offset, respectively.
+  //
+  // (Not in `std::span`; inspired by Rust's `slice::split_at()` and
+  // `split_at_mut()`.)
+  template <size_t Offset>
+  constexpr auto split_at() const {
+    GURL_CHECK(Offset <= size());
+    return std::pair(first<Offset>(), subspan<Offset>());
+  }
+  constexpr auto split_at(StrictNumeric<size_type> offset) const {
+    return std::pair(first(offset), subspan(offset));
+  }
+
+  // Returns a span of the first N elements, removing them.
+  // When `Offset` is outside the span, the underlying call will `GURL_CHECK()`. For
+  // a non-fatal alternative, consider `SpanReader`.
+  //
+  // (Not in `std::span`; convenient for processing a stream of disparate
+  // objects or looping over elements.)
+  template <size_t Offset>
+  constexpr auto take_first() {
+    const auto [first, rest] = split_at<Offset>();
+    *this = rest;
+    return first;
+  }
+  // When `offset` is outside the span, the underlying call will `GURL_CHECK()`.
+  constexpr auto take_first(StrictNumeric<size_type> offset) {
+    const auto [first, rest] = split_at(offset);
+    *this = rest;
+    return first;
+  }
+
+  // Returns the first element, removing it.
+  // When `empty()`, the underlying call will `GURL_CHECK()`. For a non-fatal
+  // alternative, consider `SpanReader`.
+  //
+  // (Not in `std::span`; convenient for processing a stream of disparate
+  // objects or looping over elements.)
+  constexpr auto take_first_elem() { return take_first<1>().front(); }
+
+  // [span.obs]: Observers
+  // Size.
+  constexpr size_type size() const noexcept { return size_; }
+  constexpr size_type size_bytes() const noexcept {
+    return size() * sizeof(element_type);
+  }
+
+  // Empty.
+  [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; }
+
+  // Returns true if `lhs` and `rhs` are equal-sized and are per-element equal.
+  //
+  // (Not in `std::span`; improves both ergonomics and safety.)
+  //
+  // NOTE: Using non-members here intentionally allows comparing types that
+  // implicitly convert to `span`.
+  friend constexpr bool operator==(span lhs, span rhs)
+    requires(std::is_const_v<element_type> &&
+             std::equality_comparable<const element_type>)
+  {
+    return std::ranges::equal(span<const element_type>(lhs),
+                              span<const element_type>(rhs));
+  }
+  friend constexpr bool operator==(span lhs,
+                                   span<const element_type, extent> rhs)
+    requires(!std::is_const_v<element_type> &&
+             std::equality_comparable<const element_type>)
+  {
+    return std::ranges::equal(span<const element_type>(lhs), rhs);
+  }
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires(std::equality_comparable_with<const element_type,
+                                           const OtherElementType>)
+  friend constexpr bool operator==(
+      span lhs,
+      span<OtherElementType, OtherExtent, OtherInternalPtrType> rhs) {
+    return std::ranges::equal(span<const element_type>(lhs),
+                              span<const OtherElementType, OtherExtent>(rhs));
+  }
+
+  // Performs lexicographical comparison of `lhs` and `rhs`.
+  //
+  // (Not in `std::span`; improves both ergonomics and safety.)
+  //
+  // NOTE: Using non-members here intentionally allows comparing types that
+  // implicitly convert to `span`.
+  friend constexpr auto operator<=>(span lhs, span rhs)
+    requires(std::is_const_v<element_type> &&
+             std::three_way_comparable<const element_type>)
+  {
+    const auto const_lhs = span<const element_type>(lhs);
+    const auto const_rhs = span<const element_type>(rhs);
+    return std::lexicographical_compare_three_way(
+        const_lhs.begin(), const_lhs.end(), const_rhs.begin(), const_rhs.end());
+  }
+  friend constexpr auto operator<=>(span lhs,
+                                    span<const element_type, extent> rhs)
+    requires(!std::is_const_v<element_type> &&
+             std::three_way_comparable<const element_type>)
+  {
+    return span<const element_type>(lhs) <=> rhs;
+  }
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires(std::three_way_comparable_with<const element_type,
+                                            const OtherElementType>)
+  friend constexpr auto operator<=>(
+      span lhs,
+      span<OtherElementType, OtherExtent, OtherInternalPtrType> rhs) {
+    const auto const_lhs = span<const element_type>(lhs);
+    const auto const_rhs = span<const OtherElementType, OtherExtent>(rhs);
+    return std::lexicographical_compare_three_way(
+        const_lhs.begin(), const_lhs.end(), const_rhs.begin(), const_rhs.end());
+  }
+
+  template <typename H>
+  friend H AbslHashValue(H h, span v) {
+    return H::combine_contiguous(std::move(h), v.data(), v.size());
+  }
+
+  // [span.elem]: Element access
+  // Reference to a specific element.
+  // When `idx` is outside the span, the underlying call will `GURL_CHECK()`.
+  //
+  // Intentionally does not take `StrictNumeric<size_type>`; see comments on
+  // fixed-extent version for rationale.
+  constexpr reference operator[](size_type idx) const { return at(idx); }
+
+  // When `idx` is outside the span, the underlying call will `GURL_CHECK()`.
+  constexpr reference at(StrictNumeric<size_type> idx) const {
+    return *get_at(idx);
+  }
+
+  // Returns a pointer to an element in the span.
+  //
+  // (Not in `std::`; necessary when underlying memory is not yet initialized.)
+  constexpr pointer get_at(StrictNumeric<size_type> idx) const {
+    GURL_CHECK(size_type{idx} < size());
+    // SAFETY: `data()` points to at least `size()` elements, so `idx` must be
+    // the index of a valid element.
+    return UNSAFE_BUFFERS(data() + size_type{idx});
+  }
+
+  // Reference to first/last elements.
+  // When `empty()`, the underlying call will `GURL_CHECK()`.
+  constexpr reference front() const { return operator[](0); }
+  // When `empty()`, the underlying call will `GURL_CHECK()`.
+  constexpr reference back() const { return operator[](size() - 1); }
+
+  // Underlying memory.
+  constexpr pointer data() const noexcept { return data_; }
+
+  // [span.iter]: Iterator support
+  // Forward iterators.
+  constexpr iterator begin() const noexcept {
+    // SAFETY: `data()` points to at least `size()` elements, so `data() +
+    // size()` is no larger than just past the end of the corresponding
+    // allocation, which is a legal pointer to construct and compare to (though
+    // not dereference).
+    //
+    // Use `AssumeValid()` to elide unnecessary precondition `GURL_CHECK()`'s in the
+    // iterator constructor: `data() + size()` must not overflow given the above
+    // constraints, so the iterator's requirement that begin <= current <= end
+    // is guaranteed to be true.
+    return UNSAFE_BUFFERS(iterator(
+        typename iterator::AssumeValid(data(), data(), data() + size())));
+  }
+  constexpr const_iterator cbegin() const noexcept {
+    return const_iterator(begin());
+  }
+  constexpr iterator end() const noexcept {
+    // SAFETY: `data()` points to at least `size()` elements, so `data() +
+    // size()` is no larger than just past the end of the corresponding
+    // allocation, which is a legal pointer to construct and compare to (though
+    // not dereference).
+    //
+    // Use `AssumeValid()` to elide unnecessary precondition `GURL_CHECK()`'s in the
+    // iterator constructor: `data() + size()` must not overflow given the above
+    // constraints, so the iterator's requirement that begin <= current <= end
+    // is guaranteed to be true.
+    return UNSAFE_BUFFERS(iterator(typename iterator::AssumeValid(
+        data(), data() + size(), data() + size())));
+  }
+  constexpr const_iterator cend() const noexcept {
+    return const_iterator(end());
+  }
+
+  // Reverse iterators.
+  constexpr reverse_iterator rbegin() const noexcept {
+    return reverse_iterator(end());
+  }
+  constexpr const_reverse_iterator crbegin() const noexcept {
+    return const_iterator(rbegin());
+  }
+  constexpr reverse_iterator rend() const noexcept {
+    return reverse_iterator(begin());
+  }
+  constexpr const_reverse_iterator crend() const noexcept {
+    return const_iterator(rend());
+  }
+
+  // [span.objectrep]: Views of object representation
+  // Converts a dynamic-extent span to a fixed-extent span. Returns a
+  // `span<element_type, Extent>` iff `size() == Extent`; otherwise, returns
+  // `std::nullopt`.
+  //
+  // (Not in `std::`; provides a conditional conversion path.)
+  template <size_t Extent>
+  constexpr std::optional<span<element_type, Extent>> to_fixed_extent() const {
+    return size() == Extent ? std::optional(span<element_type, Extent>(*this))
+                            : std::nullopt;
+  }
+
+ private:
+  InternalPtrType data_ = nullptr;
+  size_t size_ = 0;
+};
+
+// [span.deduct]: Deduction guides
+template <typename It, typename EndOrSize>
+  requires(std::contiguous_iterator<It>)
+span(It, EndOrSize) -> span<std::remove_reference_t<std::iter_reference_t<It>>,
+                            internal::MaybeStaticExt<EndOrSize>>;
+
+template <typename R>
+  requires(std::ranges::contiguous_range<R>)
+span(R&&) -> span<std::remove_reference_t<std::ranges::range_reference_t<R>>,
+                  internal::kComputedExtent<R>>;
+
+// Deduction guide for contiguous and non-borrowed ranges. This adds const to
+// the element type, since mutable spans only support construction from borrowed
+// ranges.
 //
-// Usage: auto span = gurl_base::make_span(...);
-template <int&... ExplicitArgumentBarrier, typename Container>
-constexpr auto make_span(Container&& container) noexcept {
-  using T =
-      std::remove_pointer_t<decltype(std::data(std::declval<Container>()))>;
-  using Extent = internal::Extent<Container>;
-  return span<T, Extent::value>(std::forward<Container>(container));
+// (Not in `std::`; Restores behavior of gsl::span:
+// https://godbolt.org/z/11dz4dceY)
+template <typename R>
+  requires(std::ranges::contiguous_range<R> && !std::ranges::borrowed_range<R>)
+span(R&&)
+    -> span<const std::remove_reference_t<std::ranges::range_reference_t<R>>,
+            internal::kComputedExtent<R>>;
+
+// [span.objectrep]: Views of object representation
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType>)
+constexpr auto as_bytes(span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<const uint8_t>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType>)
+constexpr auto as_bytes(allow_nonunique_obj_t,
+                        span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<const uint8_t>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_bytes(span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<uint8_t>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_bytes(allow_nonunique_obj_t,
+                                 span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<uint8_t>(s);
 }
 
-// make_span utility functions that allow callers to explicit specify the span's
-// extent, the value_type is deduced automatically. This is useful when passing
-// a dynamically sized container to a method expecting static spans, when the
-// container is known to have the correct size.
+// Like `as_[writable_]bytes()`, but uses `[const] char` rather than `[const]
+// uint8_t`.
 //
-// Note: This will GURL_CHECK that N indeed matches size(container).
+// (Not in `std::`; eases span adoption in Chromium, which uses `char` in many
+// cases that rightfully should be `uint8_t`.)
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType>)
+constexpr auto as_chars(span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<const char>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType>)
+constexpr auto as_chars(allow_nonunique_obj_t,
+                        span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<const char>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_chars(span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<char>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_chars(allow_nonunique_obj_t,
+                                 span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<char>(s);
+}
+
+// Converts a `T&` to a `span<T, 1>`.
 //
-// Usage: auto static_span = gurl_base::make_span<N>(...);
-template <size_t N, int&... ExplicitArgumentBarrier, typename It>
-constexpr auto make_span(It it, StrictNumeric<size_t> size) noexcept {
-  using T = std::remove_reference_t<iter_reference_t<It>>;
-  return span<T, N>(it, size);
-}
-
-template <size_t N,
-          int&... ExplicitArgumentBarrier,
-          typename It,
-          typename End,
-          typename = std::enable_if_t<!std::is_convertible_v<End, size_t>>>
-constexpr auto make_span(It it, End end) noexcept {
-  using T = std::remove_reference_t<iter_reference_t<It>>;
-  return span<T, N>(it, end);
-}
-
-template <size_t N, int&... ExplicitArgumentBarrier, typename Container>
-constexpr auto make_span(Container&& container) noexcept {
-  using T =
-      std::remove_pointer_t<decltype(std::data(std::declval<Container>()))>;
-  return span<T, N>(std::data(container), std::size(container));
-}
-
-// Convenience function for converting an object which is itself convertible
-// to span into a span of bytes (i.e. span of const uint8_t). Typically used
-// to convert std::string or string-objects holding chars, or std::vector
-// or vector-like objects holding other scalar types, prior to passing them
-// into an API that requires byte spans.
+// (Not in `std::`; inspired by Rust's `slice::from_ref()`.)
 template <typename T>
-inline span<const uint8_t> as_byte_span(const T& arg) {
-  return as_bytes(make_span(arg));
+constexpr auto span_from_ref(const T& t LIFETIME_BOUND) {
+  // SAFETY: It's safe to read the memory at `t`'s address as long as the
+  // provided reference is valid.
+  return UNSAFE_BUFFERS(span<const T, 1>(std::addressof(t), 1u));
+}
+template <typename T>
+constexpr auto span_from_ref(T& t LIFETIME_BOUND) {
+  // SAFETY: It's safe to read the memory at `t`'s address as long as the
+  // provided reference is valid.
+  return UNSAFE_BUFFERS(span<T, 1>(std::addressof(t), 1u));
+}
+
+// Converts a `T&` to a `span<[const] uint8_t, sizeof(T)>`.
+//
+// (Not in `std::`.)
+template <typename T>
+  requires(internal::CanSafelyConvertToByteSpan<T>)
+constexpr auto byte_span_from_ref(const T& t LIFETIME_BOUND) {
+  return as_bytes(span_from_ref(t));
+}
+template <typename T>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<T>)
+constexpr auto byte_span_from_ref(allow_nonunique_obj_t,
+                                  const T& t LIFETIME_BOUND) {
+  return as_bytes(allow_nonunique_obj, span_from_ref(t));
+}
+template <typename T>
+  requires(internal::CanSafelyConvertToByteSpan<T>)
+constexpr auto byte_span_from_ref(T& t LIFETIME_BOUND) {
+  return as_writable_bytes(span_from_ref(t));
+}
+template <typename T>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<T>)
+constexpr auto byte_span_from_ref(allow_nonunique_obj_t, T& t LIFETIME_BOUND) {
+  return as_writable_bytes(allow_nonunique_obj, span_from_ref(t));
+}
+
+// Converts a `const CharT[]` literal to a `span<const CharT>`, omitting the
+// trailing '\0' (internal '\0's, if any, are preserved). For comparison:
+//   `span("hi")`                  => `span<const char, 3>({'h', 'i', '\0'})`
+//   `span(std::string_view("hi")) => `span<const char>({'h', 'i'})`
+//   `span_from_cstring("hi")`     => `span<const char, 2>({'h', 'i'})`
+//
+// (Not in `std::`; useful when reading and writing character subsequences in
+// larger files.)
+template <typename CharT, size_t Extent>
+constexpr auto span_from_cstring(const CharT (&str LIFETIME_BOUND)[Extent])
+    ENABLE_IF_ATTR(str[Extent - 1u] == CharT{0},
+                   "requires string literal as input") {
+  return span(str).template first<Extent - 1>();
+}
+
+// Converts a `const CharT[]` literal to a `span<const CharT>`, preserving the
+// trailing '\0'.
+//
+// (Not in `std::`; identical to constructor behavior, but more explicit.)
+template <typename CharT, size_t Extent>
+constexpr auto span_with_nul_from_cstring(
+    const CharT (&str LIFETIME_BOUND)[Extent])
+    ENABLE_IF_ATTR(str[Extent - 1u] == CharT{0},
+                   "requires string literal as input") {
+  return span(str);
+}
+
+// Like `span_from_cstring()`, but returns a byte span.
+//
+// (Not in `std::`.)
+template <typename CharT, size_t Extent>
+constexpr auto byte_span_from_cstring(const CharT (&str LIFETIME_BOUND)[Extent])
+    ENABLE_IF_ATTR(str[Extent - 1u] == CharT{0},
+                   "requires string literal as input") {
+  // Cannot call `span_from_cstring()` here, since the array contents do not
+  // carry through the function call, so the `ENABLE_IF_ATTR` will not be
+  // satisfied.
+  return as_bytes(span(str).template first<Extent - 1>());
+}
+
+// Like `span_with_nul_from_cstring()`, but returns a byte span.
+//
+// (Not in `std::`.)
+template <typename CharT, size_t Extent>
+constexpr auto byte_span_with_nul_from_cstring(
+    const CharT (&str LIFETIME_BOUND)[Extent])
+    ENABLE_IF_ATTR(str[Extent - 1u] == CharT{0},
+                   "requires string literal as input") {
+  // Cannot call `span_with_nul_from_cstring()` here, since the array contents
+  // do not carry through the function call, so the `ENABLE_IF_ATTR` will not be
+  // satisfied.
+  return as_bytes(span(str));
+}
+
+// Converts an object which can already explicitly convert to some kind of span
+// directly into a byte span.
+//
+// (Not in `std::`.)
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFrom<const T&>)
+constexpr auto as_byte_span(const T& t LIFETIME_BOUND) {
+  return as_bytes(span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFromNonUnique<const T&>)
+constexpr auto as_byte_span(allow_nonunique_obj_t, const T& t LIFETIME_BOUND) {
+  return as_bytes(allow_nonunique_obj, span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFrom<const T&> &&
+           std::ranges::borrowed_range<T>)
+constexpr auto as_byte_span(const T& t) {
+  return as_bytes(span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFromNonUnique<const T&> &&
+           std::ranges::borrowed_range<T>)
+constexpr auto as_byte_span(allow_nonunique_obj_t, const T& t) {
+  return as_bytes(allow_nonunique_obj, span(t));
+}
+// Array arguments require dedicated specializations because if only the
+// generalized functions are available, the compiler cannot deduce the template
+// parameter.
+template <int&... ExplicitArgumentBarrier, typename ElementType, size_t Extent>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType>)
+constexpr auto as_byte_span(const ElementType (&arr LIFETIME_BOUND)[Extent]) {
+  return as_bytes(span<const ElementType, Extent>(arr));
+}
+template <int&... ExplicitArgumentBarrier, typename ElementType, size_t Extent>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType>)
+constexpr auto as_byte_span(allow_nonunique_obj_t,
+                            const ElementType (&arr LIFETIME_BOUND)[Extent]) {
+  return as_bytes(allow_nonunique_obj, span<const ElementType, Extent>(arr));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFrom<T &&> &&
+           !std::is_const_v<internal::ElementTypeOfSpanConstructedFrom<T>>)
+// NOTE: `t` is not marked as lifetimebound because the "non-const
+// `element_type`" requirement above will in turn require `T` to be a borrowed
+// range.
+constexpr auto as_writable_byte_span(T&& t) {
+  return as_writable_bytes(span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFromNonUnique<T &&> &&
+           !std::is_const_v<internal::ElementTypeOfSpanConstructedFrom<T>>)
+constexpr auto as_writable_byte_span(allow_nonunique_obj_t, T&& t) {
+  return as_writable_bytes(allow_nonunique_obj, span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename ElementType, size_t Extent>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_byte_span(
+    ElementType (&arr LIFETIME_BOUND)[Extent]) {
+  return as_writable_bytes(span<ElementType, Extent>(arr));
+}
+template <int&... ExplicitArgumentBarrier, typename ElementType, size_t Extent>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_byte_span(
+    allow_nonunique_obj_t,
+    ElementType (&arr LIFETIME_BOUND)[Extent]) {
+  return as_writable_bytes(allow_nonunique_obj, span<ElementType, Extent>(arr));
 }
 
 }  // namespace base
 
-// EXTENT returns the size of any type that can be converted to a |gurl_base::span|
-// with definite extent, i.e. everything that is a contiguous storage of some
-// sort with static size. Specifically, this works for std::array in a constexpr
-// context. Note:
-//   * |std::size| should be preferred for plain arrays.
-//   * In run-time contexts, functions such as |std::array::size| should be
-//     preferred.
-#define EXTENT(x)                                        \
-  ::gurl_base::internal::must_not_be_dynamic_extent<decltype( \
-      ::gurl_base::make_span(x))::extent>()
-
 #endif  // BASE_CONTAINERS_SPAN_H_
diff --git a/base/containers/span_forward_internal.h b/base/containers/span_forward_internal.h
new file mode 100644
index 0000000..e70aec0
--- /dev/null
+++ b/base/containers/span_forward_internal.h
@@ -0,0 +1,29 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_CONTAINERS_SPAN_FORWARD_INTERNAL_H_
+#define BASE_CONTAINERS_SPAN_FORWARD_INTERNAL_H_
+
+#include <stddef.h>
+
+#include <limits>
+
+namespace gurl_base {
+
+// [span.syn]: Constants
+inline constexpr size_t dynamic_extent = std::numeric_limits<size_t>::max();
+
+// [views.span]: class template `span<>`
+template <typename ElementType,
+          size_t Extent = dynamic_extent,
+          // Storage pointer customization. By default this is not a
+          // `raw_ptr<>`, since `span` is mostly used for stack variables. Use
+          // `raw_span` instead for class fields, which sets this to
+          // `raw_ptr<T>`.
+          typename InternalPtrType = ElementType*>
+class span;
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_SPAN_FORWARD_INTERNAL_H_
diff --git a/base/containers/util.h b/base/containers/util.h
deleted file mode 100644
index 928263c..0000000
--- a/base/containers/util.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_CONTAINERS_UTIL_H_
-#define BASE_CONTAINERS_UTIL_H_
-
-#include <stdint.h>
-
-namespace gurl_base {
-
-// TODO(crbug.com/817982): What we really need is for checked_math.h to be
-// able to do checked arithmetic on pointers.
-template <typename T>
-inline uintptr_t get_uintptr(const T* t) {
-  return reinterpret_cast<uintptr_t>(t);
-}
-
-}  // namespace base
-
-#endif  // BASE_CONTAINERS_UTIL_H_
diff --git a/base/cxx20_is_constant_evaluated.h b/base/cxx20_is_constant_evaluated.h
deleted file mode 100644
index 41fb247..0000000
--- a/base/cxx20_is_constant_evaluated.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_CXX20_IS_CONSTANT_EVALUATED_H_
-#define BASE_CXX20_IS_CONSTANT_EVALUATED_H_
-
-namespace gurl_base {
-
-// Implementation of C++20's std::is_constant_evaluated.
-//
-// References:
-// - https://en.cppreference.com/w/cpp/types/is_constant_evaluated
-// - https://wg21.link/meta.const.eval
-constexpr bool is_constant_evaluated() noexcept {
-  return __builtin_is_constant_evaluated();
-}
-
-}  // namespace base
-
-#endif  // BASE_CXX20_IS_CONSTANT_EVALUATED_H_
diff --git a/base/debug/crash_logging.cc b/base/debug/crash_logging.cc
index 68d1c96..3703cec 100644
--- a/base/debug/crash_logging.cc
+++ b/base/debug/crash_logging.cc
@@ -5,8 +5,9 @@
 #include "base/debug/crash_logging.h"
 
 #include <ostream>
+#include <string_view>
 
-#include "base/strings/string_piece.h"
+#include "polyfills/base/check_op.h"
 #include "build/build_config.h"
 
 namespace gurl_base::debug {
@@ -19,18 +20,15 @@
 
 CrashKeyString* AllocateCrashKeyString(const char name[],
                                        CrashKeySize value_length) {
-  if (!g_crash_key_impl)
-    return nullptr;
-
-    // TODO(https://crbug.com/1341077): It would be great if the DCHECKs below
-    // could also be enabled on Android, but debugging tryjob failures was a bit
-    // difficult... :-/
+  // TODO(crbug.com/40850825): It would be great if the DCHECKs below
+  // could also be enabled on Android, but debugging tryjob failures was a bit
+  // difficult... :-/
 #if GURL_DCHECK_IS_ON() && !BUILDFLAG(IS_ANDROID)
-  gurl_base::StringPiece name_piece = name;
+  std::string_view name_piece = name;
 
   // Some `CrashKeyImplementation`s reserve certain characters and disallow
   // using them in crash key names.  See also https://crbug.com/1341077.
-  GURL_DCHECK_EQ(gurl_base::StringPiece::npos, name_piece.find(':'))
+  GURL_DCHECK_EQ(std::string_view::npos, name_piece.find(':'))
       << "; name_piece = " << name_piece;
 
   // Some `CrashKeyImplementation`s support only short crash key names (e.g. see
@@ -40,32 +38,39 @@
   GURL_DCHECK_LT(name_piece.size(), 40u);
 #endif
 
+  if (!g_crash_key_impl) {
+    return nullptr;
+  }
+
   return g_crash_key_impl->Allocate(name, value_length);
 }
 
-void SetCrashKeyString(CrashKeyString* crash_key, gurl_base::StringPiece value) {
-  if (!g_crash_key_impl || !crash_key)
+void SetCrashKeyString(CrashKeyString* crash_key, std::string_view value) {
+  if (!g_crash_key_impl || !crash_key) {
     return;
+  }
 
   g_crash_key_impl->Set(crash_key, value);
 }
 
 void ClearCrashKeyString(CrashKeyString* crash_key) {
-  if (!g_crash_key_impl || !crash_key)
+  if (!g_crash_key_impl || !crash_key) {
     return;
+  }
 
   g_crash_key_impl->Clear(crash_key);
 }
 
 void OutputCrashKeysToStream(std::ostream& out) {
-  if (!g_crash_key_impl)
+  if (!g_crash_key_impl) {
     return;
+  }
 
   g_crash_key_impl->OutputCrashKeysToStream(out);
 }
 
 ScopedCrashKeyString::ScopedCrashKeyString(CrashKeyString* crash_key,
-                                           gurl_base::StringPiece value)
+                                           std::string_view value)
     : crash_key_(crash_key) {
   SetCrashKeyString(crash_key_, value);
 }
diff --git a/base/debug/crash_logging.h b/base/debug/crash_logging.h
index b7073a5..d0e5079 100644
--- a/base/debug/crash_logging.h
+++ b/base/debug/crash_logging.h
@@ -9,12 +9,12 @@
 
 #include <iosfwd>
 #include <memory>
+#include <string_view>
 #include <type_traits>
 
 #include "polyfills/base/base_export.h"
 #include "polyfills/base/memory/raw_ptr.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 namespace debug {
@@ -88,7 +88,7 @@
 // if AllocateCrashKeyString() returned null. If |value| is longer than the
 // size with which the key was allocated, it will be truncated.
 BASE_EXPORT void SetCrashKeyString(CrashKeyString* crash_key,
-                                   gurl_base::StringPiece value);
+                                   std::string_view value);
 
 // Clears any value that was stored in |crash_key|. The |crash_key| may be
 // null.
@@ -99,9 +99,9 @@
 
 // A scoper that sets the specified key to value for the lifetime of the
 // object, and clears it on destruction.
-class BASE_EXPORT ScopedCrashKeyString {
+class BASE_EXPORT [[nodiscard]] ScopedCrashKeyString {
  public:
-  ScopedCrashKeyString(CrashKeyString* crash_key, gurl_base::StringPiece value);
+  ScopedCrashKeyString(CrashKeyString* crash_key, std::string_view value);
   ScopedCrashKeyString(ScopedCrashKeyString&& other);
   ~ScopedCrashKeyString();
 
@@ -131,8 +131,8 @@
                                           key_size)                     \
   static_assert(::std::size(category "-" name) < 40,                    \
                 "Crash key names must be shorter than 40 characters."); \
-  static_assert(::gurl_base::StringPiece(category "-" name).find(':') ==     \
-                    ::gurl_base::StringPiece::npos,                          \
+  static_assert(::std::string_view(category "-" name).find(':') ==      \
+                    ::std::string_view::npos,                           \
                 "Crash key names must not contain the ':' character."); \
   ::gurl_base::debug::ScopedCrashKeyString scoped_crash_key_helper##nonce(   \
       [] {                                                              \
@@ -191,7 +191,7 @@
   virtual ~CrashKeyImplementation() = default;
 
   virtual CrashKeyString* Allocate(const char name[], CrashKeySize size) = 0;
-  virtual void Set(CrashKeyString* crash_key, gurl_base::StringPiece value) = 0;
+  virtual void Set(CrashKeyString* crash_key, std::string_view value) = 0;
   virtual void Clear(CrashKeyString* crash_key) = 0;
   virtual void OutputCrashKeysToStream(std::ostream& out) = 0;
 };
diff --git a/base/debug/leak_annotations.h b/base/debug/leak_annotations.h
index 506e1e0..13fa31b 100644
--- a/base/debug/leak_annotations.h
+++ b/base/debug/leak_annotations.h
@@ -18,7 +18,7 @@
 // ANNOTATE_LEAKING_OBJECT_PTR(X): the heap object referenced by pointer X will
 // be annotated as a leak.
 
-#if defined(LEAK_SANITIZER) && !BUILDFLAG(IS_NACL)
+#if defined(LEAK_SANITIZER)
 
 #include <sanitizer/lsan_interface.h>
 
@@ -33,8 +33,9 @@
   ~ScopedLeakSanitizerDisabler() { __lsan_enable(); }
 };
 
-#define ANNOTATE_SCOPED_MEMORY_LEAK \
-    ScopedLeakSanitizerDisabler leak_sanitizer_disabler; static_cast<void>(0)
+#define ANNOTATE_SCOPED_MEMORY_LEAK                    \
+  ScopedLeakSanitizerDisabler leak_sanitizer_disabler; \
+  static_cast<void>(0)
 
 #define ANNOTATE_LEAKING_OBJECT_PTR(X) __lsan_ignore_object(X);
 
diff --git a/base/functional/identity.h b/base/functional/identity.h
deleted file mode 100644
index a0b66a6..0000000
--- a/base/functional/identity.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_FUNCTIONAL_IDENTITY_H_
-#define BASE_FUNCTIONAL_IDENTITY_H_
-
-#include <utility>
-
-namespace gurl_base {
-
-// Implementation of C++20's std::identity.
-//
-// Reference:
-// - https://en.cppreference.com/w/cpp/utility/functional/identity
-// - https://wg21.link/func.identity
-struct identity {
-  template <typename T>
-  constexpr T&& operator()(T&& t) const noexcept {
-    return std::forward<T>(t);
-  }
-
-  using is_transparent = void;
-};
-
-}  // namespace base
-
-#endif  // BASE_FUNCTIONAL_IDENTITY_H_
diff --git a/base/functional/invoke.h b/base/functional/invoke.h
deleted file mode 100644
index c2161ce..0000000
--- a/base/functional/invoke.h
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_FUNCTIONAL_INVOKE_H_
-#define BASE_FUNCTIONAL_INVOKE_H_
-
-#include <type_traits>
-#include <utility>
-
-namespace gurl_base {
-
-namespace internal {
-
-// Helper struct and alias to deduce the class type from a member function
-// pointer or member object pointer.
-template <typename DecayedF>
-struct member_pointer_class {};
-
-template <typename ReturnT, typename ClassT>
-struct member_pointer_class<ReturnT ClassT::*> {
-  using type = ClassT;
-};
-
-template <typename DecayedF>
-using member_pointer_class_t = typename member_pointer_class<DecayedF>::type;
-
-// Utility struct to detect specializations of std::reference_wrapper.
-template <typename T>
-struct is_reference_wrapper : std::false_type {};
-
-template <typename T>
-struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
-
-// Small helpers used below in internal::invoke to make the SFINAE more concise.
-template <typename F>
-const bool& IsMemFunPtr = std::is_member_function_pointer_v<std::decay_t<F>>;
-
-template <typename F>
-const bool& IsMemObjPtr = std::is_member_object_pointer_v<std::decay_t<F>>;
-
-template <typename F,
-          typename T,
-          typename MemPtrClass = member_pointer_class_t<std::decay_t<F>>>
-const bool& IsMemPtrToBaseOf = std::is_base_of_v<MemPtrClass, std::decay_t<T>>;
-
-template <typename T>
-const bool& IsRefWrapper = is_reference_wrapper<std::decay_t<T>>::value;
-
-template <bool B>
-using EnableIf = std::enable_if_t<B, bool>;
-
-// Invokes a member function pointer on a reference to an object of a suitable
-// type. Covers bullet 1 of the INVOKE definition.
-//
-// Reference: https://wg21.link/func.require#1.1
-template <typename F,
-          typename T1,
-          typename... Args,
-          EnableIf<IsMemFunPtr<F> && IsMemPtrToBaseOf<F, T1>> = true>
-constexpr decltype(auto) InvokeImpl(F&& f, T1&& t1, Args&&... args) {
-  return (std::forward<T1>(t1).*f)(std::forward<Args>(args)...);
-}
-
-// Invokes a member function pointer on a std::reference_wrapper to an object of
-// a suitable type. Covers bullet 2 of the INVOKE definition.
-//
-// Reference: https://wg21.link/func.require#1.2
-template <typename F,
-          typename T1,
-          typename... Args,
-          EnableIf<IsMemFunPtr<F> && IsRefWrapper<T1>> = true>
-constexpr decltype(auto) InvokeImpl(F&& f, T1&& t1, Args&&... args) {
-  return (t1.get().*f)(std::forward<Args>(args)...);
-}
-
-// Invokes a member function pointer on a pointer-like type to an object of a
-// suitable type. Covers bullet 3 of the INVOKE definition.
-//
-// Reference: https://wg21.link/func.require#1.3
-template <typename F,
-          typename T1,
-          typename... Args,
-          EnableIf<IsMemFunPtr<F> && !IsMemPtrToBaseOf<F, T1> &&
-                   !IsRefWrapper<T1>> = true>
-constexpr decltype(auto) InvokeImpl(F&& f, T1&& t1, Args&&... args) {
-  return ((*std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
-}
-
-// Invokes a member object pointer on a reference to an object of a suitable
-// type. Covers bullet 4 of the INVOKE definition.
-//
-// Reference: https://wg21.link/func.require#1.4
-template <typename F,
-          typename T1,
-          EnableIf<IsMemObjPtr<F> && IsMemPtrToBaseOf<F, T1>> = true>
-constexpr decltype(auto) InvokeImpl(F&& f, T1&& t1) {
-  return std::forward<T1>(t1).*f;
-}
-
-// Invokes a member object pointer on a std::reference_wrapper to an object of
-// a suitable type. Covers bullet 5 of the INVOKE definition.
-//
-// Reference: https://wg21.link/func.require#1.5
-template <typename F,
-          typename T1,
-          EnableIf<IsMemObjPtr<F> && IsRefWrapper<T1>> = true>
-constexpr decltype(auto) InvokeImpl(F&& f, T1&& t1) {
-  return t1.get().*f;
-}
-
-// Invokes a member object pointer on a pointer-like type to an object of a
-// suitable type. Covers bullet 6 of the INVOKE definition.
-//
-// Reference: https://wg21.link/func.require#1.6
-template <typename F,
-          typename T1,
-          EnableIf<IsMemObjPtr<F> && !IsMemPtrToBaseOf<F, T1> &&
-                   !IsRefWrapper<T1>> = true>
-constexpr decltype(auto) InvokeImpl(F&& f, T1&& t1) {
-  return (*std::forward<T1>(t1)).*f;
-}
-
-// Invokes a regular function or function object. Covers bullet 7 of the INVOKE
-// definition.
-//
-// Reference: https://wg21.link/func.require#1.7
-template <typename F, typename... Args>
-constexpr decltype(auto) InvokeImpl(F&& f, Args&&... args) {
-  return std::forward<F>(f)(std::forward<Args>(args)...);
-}
-
-}  // namespace internal
-
-// Implementation of C++17's std::invoke. This is not based on implementation
-// referenced in original std::invoke proposal, but rather a manual
-// implementation, so that it can be constexpr.
-//
-// References:
-// - https://wg21.link/n4169#implementability
-// - https://en.cppreference.com/w/cpp/utility/functional/invoke
-// - https://wg21.link/func.invoke
-template <typename F, typename... Args>
-constexpr decltype(auto) invoke(F&& f, Args&&... args) {
-  return internal::InvokeImpl(std::forward<F>(f), std::forward<Args>(args)...);
-}
-
-}  // namespace base
-
-#endif  // BASE_FUNCTIONAL_INVOKE_H_
diff --git a/base/functional/not_fn.h b/base/functional/not_fn.h
deleted file mode 100644
index 4bf248f..0000000
--- a/base/functional/not_fn.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_FUNCTIONAL_NOT_FN_H_
-#define BASE_FUNCTIONAL_NOT_FN_H_
-
-#include <type_traits>
-#include <utility>
-
-#include "base/functional/invoke.h"
-
-namespace gurl_base {
-
-namespace internal {
-
-template <typename F>
-struct NotFnImpl {
-  F f;
-
-  template <typename... Args>
-  constexpr decltype(auto) operator()(Args&&... args) & noexcept {
-    return !gurl_base::invoke(f, std::forward<Args>(args)...);
-  }
-
-  template <typename... Args>
-  constexpr decltype(auto) operator()(Args&&... args) const& noexcept {
-    return !gurl_base::invoke(f, std::forward<Args>(args)...);
-  }
-
-  template <typename... Args>
-  constexpr decltype(auto) operator()(Args&&... args) && noexcept {
-    return !gurl_base::invoke(std::move(f), std::forward<Args>(args)...);
-  }
-
-  template <typename... Args>
-  constexpr decltype(auto) operator()(Args&&... args) const&& noexcept {
-    return !gurl_base::invoke(std::move(f), std::forward<Args>(args)...);
-  }
-};
-
-}  // namespace internal
-
-// Implementation of C++17's std::not_fn.
-//
-// Reference:
-// - https://en.cppreference.com/w/cpp/utility/functional/not_fn
-// - https://wg21.link/func.not.fn
-template <typename F>
-constexpr internal::NotFnImpl<std::decay_t<F>> not_fn(F&& f) {
-  return {std::forward<F>(f)};
-}
-
-}  // namespace base
-
-#endif  // BASE_FUNCTIONAL_NOT_FN_H_
diff --git a/base/memory/raw_ptr_exclusion.h b/base/memory/raw_ptr_exclusion.h
index e4d355d..e5d3fb8 100644
--- a/base/memory/raw_ptr_exclusion.h
+++ b/base/memory/raw_ptr_exclusion.h
@@ -8,6 +8,6 @@
 // Although `raw_ptr` is part of the standalone PA distribution, it is
 // easier to use the shorter path in `//base/memory`. We retain this
 // facade header for ease of typing.
-#include "base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_exclusion.h"  // IWYU pragma: export
+#include "partition_alloc/pointers/raw_ptr_exclusion.h"  // IWYU pragma: export
 
 #endif  // BASE_MEMORY_RAW_PTR_EXCLUSION_H_
diff --git a/base/memory/stack_allocated.h b/base/memory/stack_allocated.h
new file mode 100644
index 0000000..d4ca25a
--- /dev/null
+++ b/base/memory/stack_allocated.h
@@ -0,0 +1,59 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_STACK_ALLOCATED_H_
+#define BASE_MEMORY_STACK_ALLOCATED_H_
+
+#include <stddef.h>
+
+#if defined(__clang__)
+#define STACK_ALLOCATED_IGNORE(reason) \
+  __attribute__((annotate("stack_allocated_ignore")))
+#else  // !defined(__clang__)
+#define STACK_ALLOCATED_IGNORE(reason)
+#endif  // !defined(__clang__)
+
+// If a class or one of its ancestor classes is annotated with STACK_ALLOCATED()
+// in its class definition, then instances of the class may not be allocated on
+// the heap or as a member variable of a non-stack-allocated class.
+#define STACK_ALLOCATED()                                         \
+ public:                                                          \
+  using IsStackAllocatedTypeMarker [[maybe_unused]] = int;        \
+                                                                  \
+ private:                                                         \
+  void* operator new(size_t) = delete;                            \
+  void* operator new(size_t, ::gurl_base::NotNullTag, void*) = delete; \
+  void* operator new(size_t, void*) = delete
+
+namespace gurl_base {
+
+// NotNullTag was originally added to WebKit here:
+//     https://trac.webkit.org/changeset/103243/webkit
+// ...with the stated goal of improving the performance of the placement new
+// operator and potentially enabling the -fomit-frame-pointer compiler flag.
+//
+// TODO(szager): The placement new operator which uses this tag is currently
+// defined in third_party/blink/renderer/platform/wtf/allocator/allocator.h,
+// in the global namespace. It should probably move to /base.
+//
+// It's unknown at the time of writing whether it still provides any benefit
+// (or if it ever did). It is used by placing the kNotNull tag before the
+// address of the object when calling placement new.
+//
+// If the kNotNull tag is specified to placement new for a null pointer,
+// Undefined Behaviour can result.
+//
+// Example:
+//
+// union { int i; } u;
+//
+// // Typically placement new looks like this.
+// new (&u.i) int(3);
+// // But we can promise `&u.i` is not null like this.
+// new (gurl_base::NotNullTag::kNotNull, &u.i) int(3);
+enum class NotNullTag { kNotNull };
+
+}  // namespace base
+
+#endif  // BASE_MEMORY_STACK_ALLOCATED_H_
diff --git a/base/no_destructor.h b/base/no_destructor.h
index d2bb766..fbb184b 100644
--- a/base/no_destructor.h
+++ b/base/no_destructor.h
@@ -22,10 +22,16 @@
 //
 // ## Caveats
 //
-// - Must only be used as a function-local static variable. Declaring a global
-//   variable of type `gurl_base::NoDestructor<T>` will still generate a global
-//   constructor; declaring a local or member variable will lead to memory leaks
-//   or other surprising and undesirable behaviour.
+// - Must not be used for locals or fields; by definition, this does not run
+//   destructors, and this will likely lead to memory leaks and other
+//   surprising and undesirable behaviour.
+//
+// - If `T` is not constexpr constructible, must be a function-local static
+//   variable, since a global `NoDestructor<T>` will still generate a static
+//   initializer.
+//
+// - If `T` is constinit constructible, may be used as a global, but mark the
+//   global `constinit`.
 //
 // - If the data is rarely used, consider creating it on demand rather than
 //   caching it for the lifetime of the program. Though `gurl_base::NoDestructor<T>`
@@ -76,6 +82,11 @@
 template <typename T>
 class NoDestructor {
  public:
+  static_assert(!(std::is_trivially_constructible_v<T> &&
+                  std::is_trivially_destructible_v<T>),
+                "T is trivially constructible and destructible; please use a "
+                "constinit object of type T directly instead");
+
   static_assert(
       !std::is_trivially_destructible_v<T>,
       "T is trivially destructible; please use a function-local static "
@@ -111,7 +122,7 @@
   alignas(T) char storage_[sizeof(T)];
 
 #if defined(LEAK_SANITIZER)
-  // TODO(https://crbug.com/812277): This is a hack to work around the fact
+  // TODO(crbug.com/40562930): This is a hack to work around the fact
   // that LSan doesn't seem to treat NoDestructor as a root for reachability
   // analysis. This means that code like this:
   //   static gurl_base::NoDestructor<std::vector<int>> v({1, 2, 3});
diff --git a/base/numerics/angle_conversions.h b/base/numerics/angle_conversions.h
new file mode 100644
index 0000000..7cc673f
--- /dev/null
+++ b/base/numerics/angle_conversions.h
@@ -0,0 +1,27 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_ANGLE_CONVERSIONS_H_
+#define BASE_NUMERICS_ANGLE_CONVERSIONS_H_
+
+#include <concepts>
+#include <numbers>
+
+namespace gurl_base {
+
+template <typename T>
+  requires std::floating_point<T>
+constexpr T DegToRad(T deg) {
+  return deg * std::numbers::pi_v<T> / 180;
+}
+
+template <typename T>
+  requires std::floating_point<T>
+constexpr T RadToDeg(T rad) {
+  return rad * 180 / std::numbers::pi_v<T>;
+}
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_ANGLE_CONVERSIONS_H_
diff --git a/base/numerics/basic_ops_impl.h b/base/numerics/basic_ops_impl.h
new file mode 100644
index 0000000..fcdff71
--- /dev/null
+++ b/base/numerics/basic_ops_impl.h
@@ -0,0 +1,158 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
+#pragma allow_unsafe_libc_calls
+#endif
+
+#ifndef BASE_NUMERICS_BASIC_OPS_IMPL_H_
+#define BASE_NUMERICS_BASIC_OPS_IMPL_H_
+
+#include <array>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <span>
+#include <type_traits>
+
+namespace gurl_base::internal {
+
+// The correct type to perform math operations on given values of type `T`. This
+// may be a larger type than `T` to avoid promotion to `int` which involves sign
+// conversion!
+template <class T>
+  requires(std::is_integral_v<T>)
+using MathType = std::conditional_t<
+    sizeof(T) >= sizeof(int),
+    T,
+    std::conditional_t<std::is_signed_v<T>, int, unsigned int>>;
+
+// Reverses the byte order of the integer.
+template <class T>
+  requires(std::is_unsigned_v<T> && std::is_integral_v<T>)
+inline constexpr T SwapBytes(T value) {
+  // MSVC intrinsics are not constexpr, so we provide our own constexpr
+  // implementation. We provide it unconditionally so we can test it on all
+  // platforms for correctness.
+  if (std::is_constant_evaluated()) {
+    if constexpr (sizeof(T) == 1u) {
+      return value;
+    } else if constexpr (sizeof(T) == 2u) {
+      MathType<T> a = (MathType<T>(value) >> 0) & MathType<T>{0xff};
+      MathType<T> b = (MathType<T>(value) >> 8) & MathType<T>{0xff};
+      return static_cast<T>((a << 8) | (b << 0));
+    } else if constexpr (sizeof(T) == 4u) {
+      T a = (value >> 0) & T{0xff};
+      T b = (value >> 8) & T{0xff};
+      T c = (value >> 16) & T{0xff};
+      T d = (value >> 24) & T{0xff};
+      return (a << 24) | (b << 16) | (c << 8) | (d << 0);
+    } else {
+      static_assert(sizeof(T) == 8u);
+      T a = (value >> 0) & T{0xff};
+      T b = (value >> 8) & T{0xff};
+      T c = (value >> 16) & T{0xff};
+      T d = (value >> 24) & T{0xff};
+      T e = (value >> 32) & T{0xff};
+      T f = (value >> 40) & T{0xff};
+      T g = (value >> 48) & T{0xff};
+      T h = (value >> 56) & T{0xff};
+      return (a << 56) | (b << 48) | (c << 40) | (d << 32) |  //
+             (e << 24) | (f << 16) | (g << 8) | (h << 0);
+    }
+  }
+
+#if _MSC_VER
+  if constexpr (sizeof(T) == 1u) {
+    return value;
+  } else if constexpr (sizeof(T) == sizeof(unsigned short)) {
+    using U = unsigned short;
+    return _byteswap_ushort(U{value});
+  } else if constexpr (sizeof(T) == sizeof(unsigned long)) {
+    using U = unsigned long;
+    return _byteswap_ulong(U{value});
+  } else {
+    static_assert(sizeof(T) == 8u);
+    return _byteswap_uint64(value);
+  }
+#else
+  if constexpr (sizeof(T) == 1u) {
+    return value;
+  } else if constexpr (sizeof(T) == 2u) {
+    return __builtin_bswap16(uint16_t{value});
+  } else if constexpr (sizeof(T) == 4u) {
+    return __builtin_bswap32(value);
+  } else {
+    static_assert(sizeof(T) == 8u);
+    return __builtin_bswap64(value);
+  }
+#endif
+}
+
+// Signed values are byte-swapped as unsigned values.
+template <class T>
+  requires(std::is_signed_v<T> && std::is_integral_v<T>)
+inline constexpr T SwapBytes(T value) {
+  return static_cast<T>(SwapBytes(static_cast<std::make_unsigned_t<T>>(value)));
+}
+
+// Converts from a byte array to an integer.
+template <class T>
+  requires(std::is_unsigned_v<T> && std::is_integral_v<T>)
+inline constexpr T FromLittleEndian(std::span<const uint8_t, sizeof(T)> bytes) {
+  T val;
+  if (std::is_constant_evaluated()) {
+    val = T{0};
+    for (size_t i = 0u; i < sizeof(T); i += 1u) {
+      // SAFETY: `i < sizeof(T)` (the number of bytes in T), so `(8 * i)` is
+      // less than the number of bits in T.
+      val |= MathType<T>(bytes[i]) << (8u * i);
+    }
+  } else {
+    // SAFETY: `bytes` has sizeof(T) bytes, and `val` is of type `T` so has
+    // sizeof(T) bytes, and the two can not alias as `val` is a stack variable.
+    memcpy(&val, bytes.data(), sizeof(T));
+  }
+  return val;
+}
+
+template <class T>
+  requires(std::is_signed_v<T> && std::is_integral_v<T>)
+inline constexpr T FromLittleEndian(std::span<const uint8_t, sizeof(T)> bytes) {
+  return static_cast<T>(FromLittleEndian<std::make_unsigned_t<T>>(bytes));
+}
+
+// Converts to a byte array from an integer.
+template <class T>
+  requires(std::is_unsigned_v<T> && std::is_integral_v<T>)
+inline constexpr std::array<uint8_t, sizeof(T)> ToLittleEndian(T val) {
+  auto bytes = std::array<uint8_t, sizeof(T)>();
+  if (std::is_constant_evaluated()) {
+    for (size_t i = 0u; i < sizeof(T); i += 1u) {
+      const auto last_byte = static_cast<uint8_t>(val & 0xff);
+      // The low bytes go to the front of the array in little endian.
+      bytes[i] = last_byte;
+      // If `val` is one byte, this shift would be UB. But it's also not needed
+      // since the loop will not run again.
+      if constexpr (sizeof(T) > 1u) {
+        val >>= 8u;
+      }
+    }
+  } else {
+    // SAFETY: `bytes` has sizeof(T) bytes, and `val` is of type `T` so has
+    // sizeof(T) bytes, and the two can not alias as `val` is a stack variable.
+    memcpy(bytes.data(), &val, sizeof(T));
+  }
+  return bytes;
+}
+
+template <class T>
+  requires(std::is_signed_v<T> && std::is_integral_v<T>)
+inline constexpr std::array<uint8_t, sizeof(T)> ToLittleEndian(T val) {
+  return ToLittleEndian(static_cast<std::make_unsigned_t<T>>(val));
+}
+}  // namespace gurl_base::internal
+
+#endif  //  BASE_NUMERICS_BASIC_OPS_IMPL_H_
diff --git a/base/numerics/byte_conversions.h b/base/numerics/byte_conversions.h
new file mode 100644
index 0000000..9643092
--- /dev/null
+++ b/base/numerics/byte_conversions.h
@@ -0,0 +1,712 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_BYTE_CONVERSIONS_H_
+#define BASE_NUMERICS_BYTE_CONVERSIONS_H_
+
+#include <array>
+#include <bit>
+#include <cstdint>
+#include <cstring>
+#include <span>
+#include <type_traits>
+
+#include "base/numerics/basic_ops_impl.h"
+
+// Chromium only builds and runs on Little Endian machines.
+static_assert(std::endian::native == std::endian::little);
+
+namespace gurl_base {
+
+// Returns a value with all bytes in |x| swapped, i.e. reverses the endianness.
+// TODO(pkasting): Once C++23 is available, replace with std::byteswap.
+template <class T>
+  requires(std::is_integral_v<T>)
+[[nodiscard]] inline constexpr T ByteSwap(T value) {
+  return internal::SwapBytes(value);
+}
+
+// Returns a uint8_t with the value in `bytes` interpreted as the native endian
+// encoding of the integer for the machine.
+//
+// This is suitable for decoding integers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+//
+// Note that since a single byte can have only one ordering, this just copies
+// the byte out of the span. This provides a consistent function for the
+// operation nonetheless.
+inline constexpr uint8_t U8FromNativeEndian(
+    std::span<const uint8_t, 1u> bytes) {
+  return bytes[0];
+}
+// Returns a uint16_t with the value in `bytes` interpreted as the native endian
+// encoding of the integer for the machine.
+//
+// This is suitable for decoding integers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+inline constexpr uint16_t U16FromNativeEndian(
+    std::span<const uint8_t, 2u> bytes) {
+  return internal::FromLittleEndian<uint16_t>(bytes);
+}
+// Returns a uint32_t with the value in `bytes` interpreted as the native endian
+// encoding of the integer for the machine.
+//
+// This is suitable for decoding integers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+inline constexpr uint32_t U32FromNativeEndian(
+    std::span<const uint8_t, 4u> bytes) {
+  return internal::FromLittleEndian<uint32_t>(bytes);
+}
+// Returns a uint64_t with the value in `bytes` interpreted as the native endian
+// encoding of the integer for the machine.
+//
+// This is suitable for decoding integers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+inline constexpr uint64_t U64FromNativeEndian(
+    std::span<const uint8_t, 8u> bytes) {
+  return internal::FromLittleEndian<uint64_t>(bytes);
+}
+// Returns a int8_t with the value in `bytes` interpreted as the native endian
+// encoding of the integer for the machine.
+//
+// This is suitable for decoding integers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+//
+// Note that since a single byte can have only one ordering, this just copies
+// the byte out of the span. This provides a consistent function for the
+// operation nonetheless.
+inline constexpr int8_t I8FromNativeEndian(std::span<const uint8_t, 1u> bytes) {
+  return static_cast<int8_t>(bytes[0]);
+}
+// Returns a int16_t with the value in `bytes` interpreted as the native endian
+// encoding of the integer for the machine.
+//
+// This is suitable for decoding integers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+inline constexpr int16_t I16FromNativeEndian(
+    std::span<const uint8_t, 2u> bytes) {
+  return internal::FromLittleEndian<int16_t>(bytes);
+}
+// Returns a int32_t with the value in `bytes` interpreted as the native endian
+// encoding of the integer for the machine.
+//
+// This is suitable for decoding integers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+inline constexpr int32_t I32FromNativeEndian(
+    std::span<const uint8_t, 4u> bytes) {
+  return internal::FromLittleEndian<int32_t>(bytes);
+}
+// Returns a int64_t with the value in `bytes` interpreted as the native endian
+// encoding of the integer for the machine.
+//
+// This is suitable for decoding integers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+inline constexpr int64_t I64FromNativeEndian(
+    std::span<const uint8_t, 8u> bytes) {
+  return internal::FromLittleEndian<int64_t>(bytes);
+}
+
+// Returns a float with the value in `bytes` interpreted as the native endian
+// encoding of the number for the machine.
+//
+// This is suitable for decoding numbers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+inline constexpr float FloatFromNativeEndian(
+    std::span<const uint8_t, 4u> bytes) {
+  return std::bit_cast<float>(U32FromNativeEndian(bytes));
+}
+// Returns a double with the value in `bytes` interpreted as the native endian
+// encoding of the number for the machine.
+//
+// This is suitable for decoding numbers that were always kept in native
+// encoding, such as when stored in shared-memory (or through IPC) as a byte
+// buffer. Prefer an explicit little endian when storing and reading data from
+// storage, and explicit big endian for network order.
+inline constexpr double DoubleFromNativeEndian(
+    std::span<const uint8_t, 8u> bytes) {
+  return std::bit_cast<double>(U64FromNativeEndian(bytes));
+}
+
+// Returns a uint8_t with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+//
+// Note that since a single byte can have only one ordering, this just copies
+// the byte out of the span. This provides a consistent function for the
+// operation nonetheless.
+inline constexpr uint8_t U8FromLittleEndian(
+    std::span<const uint8_t, 1u> bytes) {
+  return bytes[0];
+}
+// Returns a uint16_t with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+inline constexpr uint16_t U16FromLittleEndian(
+    std::span<const uint8_t, 2u> bytes) {
+  return internal::FromLittleEndian<uint16_t>(bytes);
+}
+// Returns a uint32_t with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+inline constexpr uint32_t U32FromLittleEndian(
+    std::span<const uint8_t, 4u> bytes) {
+  return internal::FromLittleEndian<uint32_t>(bytes);
+}
+// Returns a uint64_t with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+inline constexpr uint64_t U64FromLittleEndian(
+    std::span<const uint8_t, 8u> bytes) {
+  return internal::FromLittleEndian<uint64_t>(bytes);
+}
+// Returns a int8_t with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+//
+// Note that since a single byte can have only one ordering, this just copies
+// the byte out of the span. This provides a consistent function for the
+// operation nonetheless.
+inline constexpr int8_t I8FromLittleEndian(std::span<const uint8_t, 1u> bytes) {
+  return static_cast<int8_t>(bytes[0]);
+}
+// Returns a int16_t with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+inline constexpr int16_t I16FromLittleEndian(
+    std::span<const uint8_t, 2u> bytes) {
+  return internal::FromLittleEndian<int16_t>(bytes);
+}
+// Returns a int32_t with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+inline constexpr int32_t I32FromLittleEndian(
+    std::span<const uint8_t, 4u> bytes) {
+  return internal::FromLittleEndian<int32_t>(bytes);
+}
+// Returns a int64_t with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+inline constexpr int64_t I64FromLittleEndian(
+    std::span<const uint8_t, 8u> bytes) {
+  return internal::FromLittleEndian<int64_t>(bytes);
+}
+// Returns a float with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding numbers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+inline constexpr float FloatFromLittleEndian(
+    std::span<const uint8_t, 4u> bytes) {
+  return std::bit_cast<float>(U32FromLittleEndian(bytes));
+}
+// Returns a double with the value in `bytes` interpreted as a little-endian
+// encoding of the integer.
+//
+// This is suitable for decoding numbers encoded explicitly in little endian,
+// which is a good practice with storing and reading data from storage. Use
+// the native-endian versions when working with values that were always in
+// memory, such as when stored in shared-memory (or through IPC) as a byte
+// buffer.
+inline constexpr double DoubleFromLittleEndian(
+    std::span<const uint8_t, 8u> bytes) {
+  return std::bit_cast<double>(U64FromLittleEndian(bytes));
+}
+
+// Returns a uint8_t with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+//
+// Note that since a single byte can have only one ordering, this just copies
+// the byte out of the span. This provides a consistent function for the
+// operation nonetheless.
+inline constexpr uint8_t U8FromBigEndian(std::span<const uint8_t, 1u> bytes) {
+  return bytes[0];
+}
+// Returns a uint16_t with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+inline constexpr uint16_t U16FromBigEndian(std::span<const uint8_t, 2u> bytes) {
+  return ByteSwap(internal::FromLittleEndian<uint16_t>(bytes));
+}
+// Returns a uint32_t with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+inline constexpr uint32_t U32FromBigEndian(std::span<const uint8_t, 4u> bytes) {
+  return ByteSwap(internal::FromLittleEndian<uint32_t>(bytes));
+}
+// Returns a uint64_t with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+inline constexpr uint64_t U64FromBigEndian(std::span<const uint8_t, 8u> bytes) {
+  return ByteSwap(internal::FromLittleEndian<uint64_t>(bytes));
+}
+// Returns a int8_t with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+//
+// Note that since a single byte can have only one ordering, this just copies
+// the byte out of the span. This provides a consistent function for the
+// operation nonetheless.
+inline constexpr int8_t I8FromBigEndian(std::span<const uint8_t, 1u> bytes) {
+  return static_cast<int8_t>(bytes[0]);
+}
+// Returns a int16_t with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+inline constexpr int16_t I16FromBigEndian(std::span<const uint8_t, 2u> bytes) {
+  return ByteSwap(internal::FromLittleEndian<int16_t>(bytes));
+}
+// Returns a int32_t with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+inline constexpr int32_t I32FromBigEndian(std::span<const uint8_t, 4u> bytes) {
+  return ByteSwap(internal::FromLittleEndian<int32_t>(bytes));
+}
+// Returns a int64_t with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding integers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+inline constexpr int64_t I64FromBigEndian(std::span<const uint8_t, 8u> bytes) {
+  return ByteSwap(internal::FromLittleEndian<int64_t>(bytes));
+}
+// Returns a float with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding numbers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+inline constexpr float FloatFromBigEndian(std::span<const uint8_t, 4u> bytes) {
+  return std::bit_cast<float>(U32FromBigEndian(bytes));
+}
+// Returns a double with the value in `bytes` interpreted as a big-endian
+// encoding of the integer.
+//
+// This is suitable for decoding numbers encoded explicitly in big endian, such
+// as for network order. Use the native-endian versions when working with values
+// that were always in memory, such as when stored in shared-memory (or through
+// IPC) as a byte buffer.
+inline constexpr double DoubleFromBigEndian(
+    std::span<const uint8_t, 8u> bytes) {
+  return std::bit_cast<double>(U64FromBigEndian(bytes));
+}
+
+// Returns a byte array holding the value of a uint8_t encoded as the native
+// endian encoding of the integer for the machine.
+//
+// This is suitable for encoding integers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 1u> U8ToNativeEndian(uint8_t val) {
+  return {val};
+}
+// Returns a byte array holding the value of a uint16_t encoded as the native
+// endian encoding of the integer for the machine.
+//
+// This is suitable for encoding integers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 2u> U16ToNativeEndian(uint16_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a uint32_t encoded as the native
+// endian encoding of the integer for the machine.
+//
+// This is suitable for encoding integers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 4u> U32ToNativeEndian(uint32_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a uint64_t encoded as the native
+// endian encoding of the integer for the machine.
+//
+// This is suitable for encoding integers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 8u> U64ToNativeEndian(uint64_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a int8_t encoded as the native
+// endian encoding of the integer for the machine.
+//
+// This is suitable for encoding integers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 1u> I8ToNativeEndian(int8_t val) {
+  return {static_cast<uint8_t>(val)};
+}
+// Returns a byte array holding the value of a int16_t encoded as the native
+// endian encoding of the integer for the machine.
+//
+// This is suitable for encoding integers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 2u> I16ToNativeEndian(int16_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a int32_t encoded as the native
+// endian encoding of the integer for the machine.
+//
+// This is suitable for encoding integers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 4u> I32ToNativeEndian(int32_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a int64_t encoded as the native
+// endian encoding of the integer for the machine.
+//
+// This is suitable for encoding integers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 8u> I64ToNativeEndian(int64_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a float encoded as the native
+// endian encoding of the number for the machine.
+//
+// This is suitable for encoding numbers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 4u> FloatToNativeEndian(float val) {
+  return U32ToNativeEndian(std::bit_cast<uint32_t>(val));
+}
+// Returns a byte array holding the value of a double encoded as the native
+// endian encoding of the number for the machine.
+//
+// This is suitable for encoding numbers that will always be kept in native
+// encoding, such as for storing in shared-memory (or sending through IPC) as a
+// byte buffer. Prefer an explicit little endian when storing data into external
+// storage, and explicit big endian for network order.
+inline constexpr std::array<uint8_t, 8u> DoubleToNativeEndian(double val) {
+  return U64ToNativeEndian(std::bit_cast<uint64_t>(val));
+}
+
+// Returns a byte array holding the value of a uint8_t encoded as the
+// little-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 1u> U8ToLittleEndian(uint8_t val) {
+  return {val};
+}
+// Returns a byte array holding the value of a uint16_t encoded as the
+// little-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 2u> U16ToLittleEndian(uint16_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a uint32_t encoded as the
+// little-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 4u> U32ToLittleEndian(uint32_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a uint64_t encoded as the
+// little-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 8u> U64ToLittleEndian(uint64_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a int8_t encoded as the
+// little-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 1u> I8ToLittleEndian(int8_t val) {
+  return {static_cast<uint8_t>(val)};
+}
+// Returns a byte array holding the value of a int16_t encoded as the
+// little-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 2u> I16ToLittleEndian(int16_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a int32_t encoded as the
+// little-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 4u> I32ToLittleEndian(int32_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a int64_t encoded as the
+// little-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 8u> I64ToLittleEndian(int64_t val) {
+  return internal::ToLittleEndian(val);
+}
+// Returns a byte array holding the value of a float encoded as the
+// little-endian encoding of the number.
+//
+// This is suitable for encoding numbers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 4u> FloatToLittleEndian(float val) {
+  return internal::ToLittleEndian(std::bit_cast<uint32_t>(val));
+}
+// Returns a byte array holding the value of a double encoded as the
+// little-endian encoding of the number.
+//
+// This is suitable for encoding numbers explicitly in little endian, which is
+// a good practice with storing and reading data from storage. Use the
+// native-endian versions when working with values that will always be in
+// memory, such as when stored in shared-memory (or passed through IPC) as a
+// byte buffer.
+inline constexpr std::array<uint8_t, 8u> DoubleToLittleEndian(double val) {
+  return internal::ToLittleEndian(std::bit_cast<uint64_t>(val));
+}
+
+// Returns a byte array holding the value of a uint8_t encoded as the big-endian
+// encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 1u> U8ToBigEndian(uint8_t val) {
+  return {val};
+}
+// Returns a byte array holding the value of a uint16_t encoded as the
+// big-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 2u> U16ToBigEndian(uint16_t val) {
+  return internal::ToLittleEndian(ByteSwap(val));
+}
+// Returns a byte array holding the value of a uint32_t encoded as the
+// big-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 4u> U32ToBigEndian(uint32_t val) {
+  return internal::ToLittleEndian(ByteSwap(val));
+}
+// Returns a byte array holding the value of a uint64_t encoded as the
+// big-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 8u> U64ToBigEndian(uint64_t val) {
+  return internal::ToLittleEndian(ByteSwap(val));
+}
+// Returns a byte array holding the value of a int8_t encoded as the big-endian
+// encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 1u> I8ToBigEndian(int8_t val) {
+  return {static_cast<uint8_t>(val)};
+}
+// Returns a byte array holding the value of a int16_t encoded as the
+// big-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 2u> I16ToBigEndian(int16_t val) {
+  return internal::ToLittleEndian(ByteSwap(val));
+}
+// Returns a byte array holding the value of a int32_t encoded as the
+// big-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 4u> I32ToBigEndian(int32_t val) {
+  return internal::ToLittleEndian(ByteSwap(val));
+}
+// Returns a byte array holding the value of a int64_t encoded as the
+// big-endian encoding of the integer.
+//
+// This is suitable for encoding integers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 8u> I64ToBigEndian(int64_t val) {
+  return internal::ToLittleEndian(ByteSwap(val));
+}
+// Returns a byte array holding the value of a float encoded as the big-endian
+// encoding of the number.
+//
+// This is suitable for encoding numbers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 4u> FloatToBigEndian(float val) {
+  return internal::ToLittleEndian(ByteSwap(std::bit_cast<uint32_t>(val)));
+}
+// Returns a byte array holding the value of a double encoded as the big-endian
+// encoding of the number.
+//
+// This is suitable for encoding numbers explicitly in big endian, such as for
+// network order. Use the native-endian versions when working with values that
+// are always in memory, such as when stored in shared-memory (or passed through
+// IPC) as a byte buffer. Use the little-endian encoding for storing and reading
+// from storage.
+inline constexpr std::array<uint8_t, 8u> DoubleToBigEndian(double val) {
+  return internal::ToLittleEndian(ByteSwap(std::bit_cast<uint64_t>(val)));
+}
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_BYTE_CONVERSIONS_H_
diff --git a/base/numerics/checked_math.h b/base/numerics/checked_math.h
index 11156db..c715b4f 100644
--- a/base/numerics/checked_math.h
+++ b/base/numerics/checked_math.h
@@ -5,25 +5,22 @@
 #ifndef BASE_NUMERICS_CHECKED_MATH_H_
 #define BASE_NUMERICS_CHECKED_MATH_H_
 
-#include <stddef.h>
+#include <stdint.h>
 
 #include <limits>
 #include <type_traits>
 
-#include "base/numerics/checked_math_impl.h"
+#include "base/numerics/checked_math_impl.h"  // IWYU pragma: export
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math_shared_impl.h"  // IWYU pragma: export
 
 namespace gurl_base {
 namespace internal {
 
 template <typename T>
+  requires std::is_arithmetic_v<T>
 class CheckedNumeric {
-  static_assert(std::is_arithmetic_v<T>,
-                "CheckedNumeric<T>: T must be a numeric type.");
-
  public:
-  template <typename Src>
-  friend class CheckedNumeric;
-
   using type = T;
 
   constexpr CheckedNumeric() = default;
@@ -33,16 +30,10 @@
   constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs)
       : state_(rhs.state_.value(), rhs.IsValid()) {}
 
-  // Strictly speaking, this is not necessary, but declaring this allows class
-  // template argument deduction to be used so that it is possible to simply
-  // write `CheckedNumeric(777)` instead of `CheckedNumeric<int>(777)`.
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  constexpr CheckedNumeric(T value) : state_(value) {}
-
   // This is not an explicit constructor because we implicitly upgrade regular
   // numerics to CheckedNumerics to make them easier to use.
-  template <typename Src,
-            typename = std::enable_if_t<std::is_arithmetic_v<Src>>>
+  template <typename Src>
+    requires(std::is_arithmetic_v<Src>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr CheckedNumeric(Src value) : state_(value) {}
 
@@ -73,9 +64,11 @@
 #endif
   constexpr bool
   AssignIfValid(Dst* result) const {
-    return BASE_NUMERICS_LIKELY(IsValid<Dst>())
-               ? ((*result = static_cast<Dst>(state_.value())), true)
-               : false;
+    if (IsValid<Dst>()) [[likely]] {
+      *result = static_cast<Dst>(state_.value());
+      return true;
+    }
+    return false;
   }
 
   // ValueOrDie() - The primary accessor for the underlying value. If the
@@ -88,9 +81,10 @@
   // the underlying value, and it is not available through other means.
   template <typename Dst = T, class CheckHandler = CheckOnFailure>
   constexpr StrictNumeric<Dst> ValueOrDie() const {
-    return BASE_NUMERICS_LIKELY(IsValid<Dst>())
-               ? static_cast<Dst>(state_.value())
-               : CheckHandler::template HandleFailure<Dst>();
+    if (IsValid<Dst>()) [[likely]] {
+      return static_cast<Dst>(state_.value());
+    }
+    return CheckHandler::template HandleFailure<Dst>();
   }
 
   // ValueOrDefault(T default_value) - A convenience method that returns the
@@ -100,17 +94,18 @@
   // parameter. WARNING: This function may fail to compile or GURL_CHECK at runtime
   // if the supplied default_value is not within range of the destination type.
   template <typename Dst = T, typename Src>
-  constexpr StrictNumeric<Dst> ValueOrDefault(const Src default_value) const {
-    return BASE_NUMERICS_LIKELY(IsValid<Dst>())
-               ? static_cast<Dst>(state_.value())
-               : checked_cast<Dst>(default_value);
+  constexpr StrictNumeric<Dst> ValueOrDefault(Src default_value) const {
+    if (IsValid<Dst>()) [[likely]] {
+      return static_cast<Dst>(state_.value());
+    }
+    return checked_cast<Dst>(default_value);
   }
 
   // Returns a checked numeric of the specified type, cast from the current
   // CheckedNumeric. If the current state is invalid or the destination cannot
   // represent the result then the returned CheckedNumeric will be invalid.
   template <typename Dst>
-  constexpr CheckedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
+  constexpr CheckedNumeric<UnderlyingType<Dst>> Cast() const {
     return *this;
   }
 
@@ -144,7 +139,7 @@
 
   constexpr CheckedNumeric operator-() const {
     // Use an optimized code path for a known run-time variable.
-    if (!IsConstantEvaluated() && std::is_signed_v<T> &&
+    if (!std::is_constant_evaluated() && std::is_signed_v<T> &&
         std::is_floating_point_v<T>) {
       return FastRuntimeNegate();
     }
@@ -167,13 +162,13 @@
 
   template <typename U>
   constexpr CheckedNumeric<typename MathWrapper<CheckedMaxOp, T, U>::type> Max(
-      const U rhs) const {
+      U rhs) const {
     return CheckMax(*this, rhs);
   }
 
   template <typename U>
   constexpr CheckedNumeric<typename MathWrapper<CheckedMinOp, T, U>::type> Min(
-      const U rhs) const {
+      U rhs) const {
     return CheckMin(*this, rhs);
   }
 
@@ -192,8 +187,8 @@
   }
 
   constexpr CheckedNumeric operator++(int) {
-    CheckedNumeric value = *this;
-    *this += 1;
+    const CheckedNumeric value = *this;
+    ++*this;
     return value;
   }
 
@@ -203,18 +198,15 @@
   }
 
   constexpr CheckedNumeric operator--(int) {
-    // TODO(pkasting): Consider std::exchange() once it's constexpr in C++20.
     const CheckedNumeric value = *this;
-    *this -= 1;
+    --*this;
     return value;
   }
 
   // These perform the actual math operations on the CheckedNumerics.
   // Binary arithmetic operations.
-  template <template <typename, typename, typename> class M,
-            typename L,
-            typename R>
-  static constexpr CheckedNumeric MathOp(const L lhs, const R rhs) {
+  template <template <typename, typename> class M, typename L, typename R>
+  static constexpr CheckedNumeric MathOp(L lhs, R rhs) {
     using Math = typename MathWrapper<M, L, R>::math;
     T result = 0;
     const bool is_valid =
@@ -224,8 +216,8 @@
   }
 
   // Assignment arithmetic operations.
-  template <template <typename, typename, typename> class M, typename R>
-  constexpr CheckedNumeric& MathOp(const R rhs) {
+  template <template <typename, typename> class M, typename R>
+  constexpr CheckedNumeric& MathOp(R rhs) {
     using Math = typename MathWrapper<M, T, R>::math;
     T result = 0;  // Using T as the destination saves a range check.
     const bool is_valid =
@@ -236,6 +228,10 @@
   }
 
  private:
+  template <typename U>
+    requires std::is_arithmetic_v<U>
+  friend class CheckedNumeric;
+
   CheckedNumericState<T> state_;
 
   CheckedNumeric FastRuntimeNegate() const {
@@ -258,23 +254,26 @@
 
   template <typename Src>
   struct Wrapper<CheckedNumeric<Src>> {
-    static constexpr bool is_valid(const CheckedNumeric<Src> v) {
+    static constexpr bool is_valid(CheckedNumeric<Src> v) {
       return v.IsValid();
     }
-    static constexpr Src value(const CheckedNumeric<Src> v) {
+    static constexpr Src value(CheckedNumeric<Src> v) {
       return v.state_.value();
     }
   };
 
   template <typename Src>
   struct Wrapper<StrictNumeric<Src>> {
-    static constexpr bool is_valid(const StrictNumeric<Src>) { return true; }
-    static constexpr Src value(const StrictNumeric<Src> v) {
+    static constexpr bool is_valid(StrictNumeric<Src>) { return true; }
+    static constexpr Src value(StrictNumeric<Src> v) {
       return static_cast<Src>(v);
     }
   };
 };
 
+template <typename T>
+CheckedNumeric(T) -> CheckedNumeric<T>;
+
 // Convenience functions to avoid the ugly template disambiguator syntax.
 template <typename Dst, typename Src>
 constexpr bool IsValidForType(const CheckedNumeric<Src> value) {
@@ -288,38 +287,34 @@
 }
 
 template <typename Dst, typename Src, typename Default>
-constexpr StrictNumeric<Dst> ValueOrDefaultForType(
-    const CheckedNumeric<Src> value,
-    const Default default_value) {
+constexpr StrictNumeric<Dst> ValueOrDefaultForType(CheckedNumeric<Src> value,
+                                                   Default default_value) {
   return value.template ValueOrDefault<Dst>(default_value);
 }
 
 // Convenience wrapper to return a new CheckedNumeric from the provided
 // arithmetic or CheckedNumericType.
 template <typename T>
-constexpr CheckedNumeric<typename UnderlyingType<T>::type> MakeCheckedNum(
-    const T value) {
+constexpr CheckedNumeric<UnderlyingType<T>> MakeCheckedNum(T value) {
   return value;
 }
 
 // These implement the variadic wrapper for the math operations.
-template <template <typename, typename, typename> class M,
-          typename L,
-          typename R>
+template <template <typename, typename> class M, typename L, typename R>
 constexpr CheckedNumeric<typename MathWrapper<M, L, R>::type> CheckMathOp(
-    const L lhs,
-    const R rhs) {
+    L lhs,
+    R rhs) {
   using Math = typename MathWrapper<M, L, R>::math;
   return CheckedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
                                                                         rhs);
 }
 
 // General purpose wrapper template for arithmetic operations.
-template <template <typename, typename, typename> class M,
+template <template <typename, typename> class M,
           typename L,
           typename R,
           typename... Args>
-constexpr auto CheckMathOp(const L lhs, const R rhs, const Args... args) {
+constexpr auto CheckMathOp(L lhs, R rhs, Args... args) {
   return CheckMathOp<M>(CheckMathOp<M>(lhs, rhs), args...);
 }
 
@@ -340,7 +335,7 @@
 // arithmetic with our result types. Since wrapping on a pointer is always
 // bad, we trigger the GURL_CHECK condition here.
 template <typename L, typename R>
-L* operator+(L* lhs, const StrictNumeric<R> rhs) {
+L* operator+(L* lhs, StrictNumeric<R> rhs) {
   const uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs),
                                     CheckMul(sizeof(L), static_cast<R>(rhs)))
                                .template ValueOrDie<uintptr_t>();
@@ -348,7 +343,7 @@
 }
 
 template <typename L, typename R>
-L* operator-(L* lhs, const StrictNumeric<R> rhs) {
+L* operator-(L* lhs, StrictNumeric<R> rhs) {
   const uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs),
                                     CheckMul(sizeof(L), static_cast<R>(rhs)))
                                .template ValueOrDie<uintptr_t>();
diff --git a/base/numerics/checked_math_impl.h b/base/numerics/checked_math_impl.h
index abba503..e1a421e 100644
--- a/base/numerics/checked_math_impl.h
+++ b/base/numerics/checked_math_impl.h
@@ -5,24 +5,24 @@
 #ifndef BASE_NUMERICS_CHECKED_MATH_IMPL_H_
 #define BASE_NUMERICS_CHECKED_MATH_IMPL_H_
 
-#include <stddef.h>
+// IWYU pragma: private, include "base/numerics/checked_math.h"
+
 #include <stdint.h>
 
-#include <climits>
 #include <cmath>
-#include <cstdlib>
+#include <concepts>
 #include <limits>
 #include <type_traits>
 
 #include "base/numerics/safe_conversions.h"
-#include "base/numerics/safe_math_shared_impl.h"
+#include "base/numerics/safe_math_shared_impl.h"  // IWYU pragma: export
 
 namespace gurl_base {
 namespace internal {
 
 template <typename T>
 constexpr bool CheckedAddImpl(T x, T y, T* result) {
-  static_assert(std::is_integral_v<T>, "Type must be integral");
+  static_assert(std::integral<T>, "Type must be integral");
   // Since the value of x+y is undefined if we have a signed type, we compute
   // it using the unsigned type of the same size.
   using UnsignedDst = typename std::make_unsigned<T>::type;
@@ -41,44 +41,43 @@
   return true;
 }
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedAddOp {};
 
 template <typename T, typename U>
-struct CheckedAddOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedAddOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
-    if constexpr (CheckedAddFastOp<T, U>::is_supported)
+    if constexpr (CheckedAddFastOp<T, U>::is_supported) {
       return CheckedAddFastOp<T, U>::Do(x, y, result);
+    }
 
     // Double the underlying type up to a full machine word.
-    using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+    using FastPromotion = FastIntegerArithmeticPromotion<T, U>;
     using Promotion =
-        typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
-                                   IntegerBitsPlusSign<intptr_t>::value),
-                                  typename BigEnoughPromotion<T, U>::type,
-                                  FastPromotion>::type;
+        std::conditional_t<(kIntegerBitsPlusSign<FastPromotion> >
+                            kIntegerBitsPlusSign<intptr_t>),
+                           BigEnoughPromotion<T, U>, FastPromotion>;
     // Fail if either operand is out of range for the promoted type.
     // TODO(jschuh): This could be made to work for a broader range of values.
-    if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
-                               !IsValueInRangeForNumericType<Promotion>(y))) {
+    if (!IsValueInRangeForNumericType<Promotion>(x) ||
+        !IsValueInRangeForNumericType<Promotion>(y)) [[unlikely]] {
       return false;
     }
 
     Promotion presult = {};
     bool is_valid = true;
-    if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+    if constexpr (kIsIntegerArithmeticSafe<Promotion, T, U>) {
       presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
     } else {
       is_valid = CheckedAddImpl(static_cast<Promotion>(x),
                                 static_cast<Promotion>(y), &presult);
     }
-    if (!is_valid || !IsValueInRangeForNumericType<V>(presult))
+    if (!is_valid || !IsValueInRangeForNumericType<V>(presult)) {
       return false;
+    }
     *result = static_cast<V>(presult);
     return true;
   }
@@ -86,7 +85,7 @@
 
 template <typename T>
 constexpr bool CheckedSubImpl(T x, T y, T* result) {
-  static_assert(std::is_integral_v<T>, "Type must be integral");
+  static_assert(std::integral<T>, "Type must be integral");
   // Since the value of x+y is undefined if we have a signed type, we compute
   // it using the unsigned type of the same size.
   using UnsignedDst = typename std::make_unsigned<T>::type;
@@ -105,44 +104,43 @@
   return true;
 }
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedSubOp {};
 
 template <typename T, typename U>
-struct CheckedSubOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedSubOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
-    if constexpr (CheckedSubFastOp<T, U>::is_supported)
+    if constexpr (CheckedSubFastOp<T, U>::is_supported) {
       return CheckedSubFastOp<T, U>::Do(x, y, result);
+    }
 
     // Double the underlying type up to a full machine word.
-    using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+    using FastPromotion = FastIntegerArithmeticPromotion<T, U>;
     using Promotion =
-        typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
-                                   IntegerBitsPlusSign<intptr_t>::value),
-                                  typename BigEnoughPromotion<T, U>::type,
-                                  FastPromotion>::type;
+        std::conditional_t<(kIntegerBitsPlusSign<FastPromotion> >
+                            kIntegerBitsPlusSign<intptr_t>),
+                           BigEnoughPromotion<T, U>, FastPromotion>;
     // Fail if either operand is out of range for the promoted type.
     // TODO(jschuh): This could be made to work for a broader range of values.
-    if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
-                               !IsValueInRangeForNumericType<Promotion>(y))) {
+    if (!IsValueInRangeForNumericType<Promotion>(x) ||
+        !IsValueInRangeForNumericType<Promotion>(y)) [[unlikely]] {
       return false;
     }
 
     Promotion presult = {};
     bool is_valid = true;
-    if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+    if constexpr (kIsIntegerArithmeticSafe<Promotion, T, U>) {
       presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
     } else {
       is_valid = CheckedSubImpl(static_cast<Promotion>(x),
                                 static_cast<Promotion>(y), &presult);
     }
-    if (!is_valid || !IsValueInRangeForNumericType<V>(presult))
+    if (!is_valid || !IsValueInRangeForNumericType<V>(presult)) {
       return false;
+    }
     *result = static_cast<V>(presult);
     return true;
   }
@@ -150,7 +148,7 @@
 
 template <typename T>
 constexpr bool CheckedMulImpl(T x, T y, T* result) {
-  static_assert(std::is_integral_v<T>, "Type must be integral");
+  static_assert(std::integral<T>, "Type must be integral");
   // Since the value of x*y is potentially undefined if we have a signed type,
   // we compute it using the unsigned type of the same size.
   using UnsignedDst = typename std::make_unsigned<T>::type;
@@ -171,44 +169,44 @@
   return true;
 }
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedMulOp {};
 
 template <typename T, typename U>
-struct CheckedMulOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedMulOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
-    if constexpr (CheckedMulFastOp<T, U>::is_supported)
+    if constexpr (CheckedMulFastOp<T, U>::is_supported) {
       return CheckedMulFastOp<T, U>::Do(x, y, result);
+    }
 
-    using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+    using Promotion = FastIntegerArithmeticPromotion<T, U>;
     // Verify the destination type can hold the result (always true for 0).
-    if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
-                                !IsValueInRangeForNumericType<Promotion>(y)) &&
-                               x && y)) {
+    if ((!IsValueInRangeForNumericType<Promotion>(x) ||
+         !IsValueInRangeForNumericType<Promotion>(y)) &&
+        x && y) [[unlikely]] {
       return false;
     }
 
     Promotion presult = {};
     bool is_valid = true;
-    if (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
+    if constexpr (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
       // The fast op may be available with the promoted type.
       // The casts here are safe because of the "value in range" conditional
       // above.
       is_valid = CheckedMulFastOp<Promotion, Promotion>::Do(
           static_cast<Promotion>(x), static_cast<Promotion>(y), &presult);
-    } else if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+    } else if constexpr (kIsIntegerArithmeticSafe<Promotion, T, U>) {
       presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
     } else {
       is_valid = CheckedMulImpl(static_cast<Promotion>(x),
                                 static_cast<Promotion>(y), &presult);
     }
-    if (!is_valid || !IsValueInRangeForNumericType<V>(presult))
+    if (!is_valid || !IsValueInRangeForNumericType<V>(presult)) {
       return false;
+    }
     *result = static_cast<V>(presult);
     return true;
   }
@@ -216,99 +214,93 @@
 
 // Division just requires a check for a zero denominator or an invalid negation
 // on signed min/-1.
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedDivOp {};
 
 template <typename T, typename U>
-struct CheckedDivOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedDivOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
-    if (BASE_NUMERICS_UNLIKELY(!y))
+    if (!y) [[unlikely]] {
       return false;
+    }
 
     // The overflow check can be compiled away if we don't have the exact
     // combination of types needed to trigger this case.
-    using Promotion = typename BigEnoughPromotion<T, U>::type;
-    if (BASE_NUMERICS_UNLIKELY(
-            (std::is_signed_v<T> && std::is_signed_v<U> &&
-             IsTypeInRangeForNumericType<T, Promotion>::value &&
-             static_cast<Promotion>(x) ==
-                 std::numeric_limits<Promotion>::lowest() &&
-             y == static_cast<U>(-1)))) {
+    using Promotion = BigEnoughPromotion<T, U>;
+    if (std::is_signed_v<T> && std::is_signed_v<U> &&
+        kIsTypeInRangeForNumericType<T, Promotion> &&
+        static_cast<Promotion>(x) == std::numeric_limits<Promotion>::lowest() &&
+        y == static_cast<U>(-1)) [[unlikely]] {
       return false;
     }
 
     // This branch always compiles away if the above branch wasn't removed.
-    if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
-                                !IsValueInRangeForNumericType<Promotion>(y)) &&
-                               x)) {
+    if ((!IsValueInRangeForNumericType<Promotion>(x) ||
+         !IsValueInRangeForNumericType<Promotion>(y)) &&
+        x) [[unlikely]] {
       return false;
     }
 
     const Promotion presult = Promotion(x) / Promotion(y);
-    if (!IsValueInRangeForNumericType<V>(presult))
+    if (!IsValueInRangeForNumericType<V>(presult)) {
       return false;
+    }
     *result = static_cast<V>(presult);
     return true;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedModOp {};
 
 template <typename T, typename U>
-struct CheckedModOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedModOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
-    if (BASE_NUMERICS_UNLIKELY(!y))
+    if (!y) [[unlikely]] {
       return false;
+    }
 
-    using Promotion = typename BigEnoughPromotion<T, U>::type;
-    if (BASE_NUMERICS_UNLIKELY(
-            (std::is_signed_v<T> && std::is_signed_v<U> &&
-             IsTypeInRangeForNumericType<T, Promotion>::value &&
-             static_cast<Promotion>(x) ==
-                 std::numeric_limits<Promotion>::lowest() &&
-             y == static_cast<U>(-1)))) {
+    using Promotion = BigEnoughPromotion<T, U>;
+    if (std::is_signed_v<T> && std::is_signed_v<U> &&
+        kIsTypeInRangeForNumericType<T, Promotion> &&
+        static_cast<Promotion>(x) == std::numeric_limits<Promotion>::lowest() &&
+        y == static_cast<U>(-1)) [[unlikely]] {
       *result = 0;
       return true;
     }
 
     const Promotion presult =
         static_cast<Promotion>(x) % static_cast<Promotion>(y);
-    if (!IsValueInRangeForNumericType<V>(presult))
+    if (!IsValueInRangeForNumericType<V>(presult)) {
       return false;
+    }
     *result = static_cast<Promotion>(presult);
     return true;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedLshOp {};
 
 // Left shift. Shifts less than 0 or greater than or equal to the number
 // of bits in the promoted type are undefined. Shifts of negative values
 // are undefined. Otherwise it is defined when the result fits.
 template <typename T, typename U>
-struct CheckedLshOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedLshOp<T, U> {
   using result_type = T;
   template <typename V>
   static constexpr bool Do(T x, U shift, V* result) {
     // Disallow negative numbers and verify the shift is in bounds.
-    if (BASE_NUMERICS_LIKELY(!IsValueNegative(x) &&
-                             as_unsigned(shift) <
-                                 as_unsigned(std::numeric_limits<T>::digits))) {
+    if (!IsValueNegative(x) &&
+        as_unsigned(shift) < as_unsigned(std::numeric_limits<T>::digits))
+        [[likely]] {
       // Shift as unsigned to avoid undefined behavior.
       *result = static_cast<V>(as_unsigned(x) << shift);
       // If the shift can be reversed, we know it was valid.
@@ -325,95 +317,87 @@
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedRshOp {};
 
 // Right shift. Shifts less than 0 or greater than or equal to the number
 // of bits in the promoted type are undefined. Otherwise, it is always defined,
 // but a right shift of a negative value is implementation-dependent.
 template <typename T, typename U>
-struct CheckedRshOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedRshOp<T, U> {
   using result_type = T;
   template <typename V>
   static constexpr bool Do(T x, U shift, V* result) {
     // Use sign conversion to push negative values out of range.
-    if (BASE_NUMERICS_UNLIKELY(as_unsigned(shift) >=
-                               IntegerBitsPlusSign<T>::value)) {
+    if (as_unsigned(shift) >= kIntegerBitsPlusSign<T>) [[unlikely]] {
       return false;
     }
 
     const T tmp = x >> shift;
-    if (!IsValueInRangeForNumericType<V>(tmp))
+    if (!IsValueInRangeForNumericType<V>(tmp)) {
       return false;
+    }
     *result = static_cast<V>(tmp);
     return true;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedAndOp {};
 
 // For simplicity we support only unsigned integer results.
 template <typename T, typename U>
-struct CheckedAndOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename std::make_unsigned<
-      typename MaxExponentPromotion<T, U>::type>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedAndOp<T, U> {
+  using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
     const result_type tmp =
         static_cast<result_type>(x) & static_cast<result_type>(y);
-    if (!IsValueInRangeForNumericType<V>(tmp))
+    if (!IsValueInRangeForNumericType<V>(tmp)) {
       return false;
+    }
     *result = static_cast<V>(tmp);
     return true;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedOrOp {};
 
 // For simplicity we support only unsigned integers.
 template <typename T, typename U>
-struct CheckedOrOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename std::make_unsigned<
-      typename MaxExponentPromotion<T, U>::type>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedOrOp<T, U> {
+  using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
     const result_type tmp =
         static_cast<result_type>(x) | static_cast<result_type>(y);
-    if (!IsValueInRangeForNumericType<V>(tmp))
+    if (!IsValueInRangeForNumericType<V>(tmp)) {
       return false;
+    }
     *result = static_cast<V>(tmp);
     return true;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedXorOp {};
 
 // For simplicity we support only unsigned integers.
 template <typename T, typename U>
-struct CheckedXorOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename std::make_unsigned<
-      typename MaxExponentPromotion<T, U>::type>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct CheckedXorOp<T, U> {
+  using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
     const result_type tmp =
         static_cast<result_type>(x) ^ static_cast<result_type>(y);
-    if (!IsValueInRangeForNumericType<V>(tmp))
+    if (!IsValueInRangeForNumericType<V>(tmp)) {
       return false;
+    }
     *result = static_cast<V>(tmp);
     return true;
   }
@@ -421,22 +405,21 @@
 
 // Max doesn't really need to be implemented this way because it can't fail,
 // but it makes the code much cleaner to use the MathOp wrappers.
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedMaxOp {};
 
 template <typename T, typename U>
-struct CheckedMaxOp<
-    T,
-    U,
-    std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
+struct CheckedMaxOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
     const result_type tmp = IsGreater<T, U>::Test(x, y)
                                 ? static_cast<result_type>(x)
                                 : static_cast<result_type>(y);
-    if (!IsValueInRangeForNumericType<V>(tmp))
+    if (!IsValueInRangeForNumericType<V>(tmp)) {
       return false;
+    }
     *result = static_cast<V>(tmp);
     return true;
   }
@@ -444,22 +427,21 @@
 
 // Min doesn't really need to be implemented this way because it can't fail,
 // but it makes the code much cleaner to use the MathOp wrappers.
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct CheckedMinOp {};
 
 template <typename T, typename U>
-struct CheckedMinOp<
-    T,
-    U,
-    std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>>> {
-  using result_type = typename LowestValuePromotion<T, U>::type;
+  requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
+struct CheckedMinOp<T, U> {
+  using result_type = LowestValuePromotion<T, U>;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
     const result_type tmp = IsLess<T, U>::Test(x, y)
                                 ? static_cast<result_type>(x)
                                 : static_cast<result_type>(y);
-    if (!IsValueInRangeForNumericType<V>(tmp))
+    if (!IsValueInRangeForNumericType<V>(tmp)) {
       return false;
+    }
     *result = static_cast<V>(tmp);
     return true;
   }
@@ -467,21 +449,19 @@
 
 // This is just boilerplate that wraps the standard floating point arithmetic.
 // A macro isn't the nicest solution, but it beats rewriting these repeatedly.
-#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)                                 \
-  template <typename T, typename U>                                         \
-  struct Checked##NAME##Op<T, U,                                            \
-                           std::enable_if_t<std::is_floating_point_v<T> ||  \
-                                            std::is_floating_point_v<U>>> { \
-    using result_type = typename MaxExponentPromotion<T, U>::type;          \
-    template <typename V>                                                   \
-    static constexpr bool Do(T x, U y, V* result) {                         \
-      using Promotion = typename MaxExponentPromotion<T, U>::type;          \
-      const Promotion presult = x OP y;                                     \
-      if (!IsValueInRangeForNumericType<V>(presult))                        \
-        return false;                                                       \
-      *result = static_cast<V>(presult);                                    \
-      return true;                                                          \
-    }                                                                       \
+#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)                    \
+  template <typename T, typename U>                            \
+    requires(std::floating_point<T> || std::floating_point<U>) \
+  struct Checked##NAME##Op<T, U> {                             \
+    using result_type = MaxExponentPromotion<T, U>;            \
+    template <typename V>                                      \
+    static constexpr bool Do(T x, U y, V* result) {            \
+      const result_type presult = x OP y;                      \
+      if (!IsValueInRangeForNumericType<V>(presult))           \
+        return false;                                          \
+      *result = static_cast<V>(presult);                       \
+      return true;                                             \
+    }                                                          \
   };
 
 BASE_FLOAT_ARITHMETIC_OPS(Add, +)
@@ -503,10 +483,10 @@
 template <typename NumericType>
 struct GetNumericRepresentation {
   static const NumericRepresentation value =
-      std::is_integral_v<NumericType>
+      std::integral<NumericType>
           ? NUMERIC_INTEGER
-          : (std::is_floating_point_v<NumericType> ? NUMERIC_FLOATING
-                                                   : NUMERIC_UNKNOWN);
+          : (std::floating_point<NumericType> ? NUMERIC_FLOATING
+                                              : NUMERIC_UNKNOWN);
 };
 
 template <typename T,
@@ -536,9 +516,9 @@
   // Ensures that a type conversion does not trigger undefined behavior.
   template <typename Src>
   static constexpr T WellDefinedConversionOrZero(Src value, bool is_valid) {
-    using SrcType = typename internal::UnderlyingType<Src>::type;
-    return (std::is_integral_v<SrcType> || is_valid) ? static_cast<T>(value)
-                                                     : 0;
+    return (std::integral<UnderlyingType<Src>> || is_valid)
+               ? static_cast<T>(value)
+               : 0;
   }
 
   // is_valid_ precedes value_ because member initializers in the constructors
@@ -563,8 +543,9 @@
       : CheckedNumericState(rhs.value(), rhs.is_valid()) {}
 
   constexpr bool is_valid() const {
-    // Written this way because std::isfinite is not reliably constexpr.
-    return IsConstantEvaluated()
+    // Written this way because std::isfinite is not constexpr before C++23.
+    // TODO(C++23): Use `std::isfinite()` unconditionally.
+    return std::is_constant_evaluated()
                ? value_ <= std::numeric_limits<T>::max() &&
                      value_ >= std::numeric_limits<T>::lowest()
                : std::isfinite(value_);
@@ -576,9 +557,8 @@
   // Ensures that a type conversion does not trigger undefined behavior.
   template <typename Src>
   static constexpr T WellDefinedConversionOrNaN(Src value, bool is_valid) {
-    using SrcType = typename internal::UnderlyingType<Src>::type;
-    return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
-                NUMERIC_RANGE_CONTAINED ||
+    return (kStaticDstRangeRelationToSrcRange<T, UnderlyingType<Src>> ==
+                NumericRangeRepresentation::kContained ||
             is_valid)
                ? static_cast<T>(value)
                : std::numeric_limits<T>::quiet_NaN();
diff --git a/base/numerics/clamped_math.h b/base/numerics/clamped_math.h
index a4cbea4..acc9245 100644
--- a/base/numerics/clamped_math.h
+++ b/base/numerics/clamped_math.h
@@ -5,47 +5,34 @@
 #ifndef BASE_NUMERICS_CLAMPED_MATH_H_
 #define BASE_NUMERICS_CLAMPED_MATH_H_
 
-#include <stddef.h>
-
-#include <limits>
 #include <type_traits>
 
-#include "base/numerics/clamped_math_impl.h"
+#include "base/numerics/clamped_math_impl.h"  // IWYU pragma: export
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math_shared_impl.h"  // IWYU pragma: export
 
 namespace gurl_base {
 namespace internal {
 
 template <typename T>
+  requires std::is_arithmetic_v<T>
 class ClampedNumeric {
-  static_assert(std::is_arithmetic_v<T>,
-                "ClampedNumeric<T>: T must be a numeric type.");
-
  public:
   using type = T;
 
-  constexpr ClampedNumeric() : value_(0) {}
+  constexpr ClampedNumeric() = default;
 
   // Copy constructor.
   template <typename Src>
   constexpr ClampedNumeric(const ClampedNumeric<Src>& rhs)
       : value_(saturated_cast<T>(rhs.value_)) {}
 
-  template <typename Src>
-  friend class ClampedNumeric;
-
-  // Strictly speaking, this is not necessary, but declaring this allows class
-  // template argument deduction to be used so that it is possible to simply
-  // write `ClampedNumeric(777)` instead of `ClampedNumeric<int>(777)`.
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  constexpr ClampedNumeric(T value) : value_(value) {}
-
   // This is not an explicit constructor because we implicitly upgrade regular
   // numerics to ClampedNumerics to make them easier to use.
   template <typename Src>
+    requires(IsNumeric<Src>)
   // NOLINTNEXTLINE(google-explicit-constructor)
-  constexpr ClampedNumeric(Src value) : value_(saturated_cast<T>(value)) {
-    static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
-  }
+  constexpr ClampedNumeric(Src value) : value_(saturated_cast<T>(value)) {}
 
   // This is not an explicit constructor because we want a seamless conversion
   // from StrictNumeric types.
@@ -57,7 +44,7 @@
   // Returns a ClampedNumeric of the specified type, cast from the current
   // ClampedNumeric, and saturated to the destination type.
   template <typename Dst>
-  constexpr ClampedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
+  constexpr ClampedNumeric<UnderlyingType<Dst>> Cast() const {
     return *this;
   }
 
@@ -101,7 +88,7 @@
 
   template <typename U>
   constexpr ClampedNumeric<typename MathWrapper<ClampedMaxOp, T, U>::type> Max(
-      const U rhs) const {
+      U rhs) const {
     using result_type = typename MathWrapper<ClampedMaxOp, T, U>::type;
     return ClampedNumeric<result_type>(
         ClampedMaxOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
@@ -109,7 +96,7 @@
 
   template <typename U>
   constexpr ClampedNumeric<typename MathWrapper<ClampedMinOp, T, U>::type> Min(
-      const U rhs) const {
+      U rhs) const {
     using result_type = typename MathWrapper<ClampedMinOp, T, U>::type;
     return ClampedNumeric<result_type>(
         ClampedMinOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
@@ -148,18 +135,16 @@
 
   // These perform the actual math operations on the ClampedNumerics.
   // Binary arithmetic operations.
-  template <template <typename, typename, typename> class M,
-            typename L,
-            typename R>
-  static constexpr ClampedNumeric MathOp(const L lhs, const R rhs) {
+  template <template <typename, typename> class M, typename L, typename R>
+  static constexpr ClampedNumeric MathOp(L lhs, R rhs) {
     using Math = typename MathWrapper<M, L, R>::math;
     return ClampedNumeric<T>(
         Math::template Do<T>(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs)));
   }
 
   // Assignment arithmetic operations.
-  template <template <typename, typename, typename> class M, typename R>
-  constexpr ClampedNumeric& MathOp(const R rhs) {
+  template <template <typename, typename> class M, typename R>
+  constexpr ClampedNumeric& MathOp(R rhs) {
     using Math = typename MathWrapper<M, T, R>::math;
     *this =
         ClampedNumeric<T>(Math::template Do<T>(value_, Wrapper<R>::value(rhs)));
@@ -167,9 +152,9 @@
   }
 
   template <typename Dst>
-  constexpr operator Dst() const {
-    return saturated_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(
-        value_);
+    requires std::is_arithmetic_v<ArithmeticOrUnderlyingEnum<Dst>>
+  constexpr operator Dst() const {  // NOLINT(google-explicit-constructor)
+    return saturated_cast<ArithmeticOrUnderlyingEnum<Dst>>(value_);
   }
 
   // This method extracts the raw integer value without saturating it to the
@@ -178,44 +163,46 @@
   constexpr T RawValue() const { return value_; }
 
  private:
-  T value_;
+  template <typename U>
+    requires std::is_arithmetic_v<U>
+  friend class ClampedNumeric;
+
+  T value_ = 0;
 
   // These wrappers allow us to handle state the same way for both
   // ClampedNumeric and POD arithmetic types.
   template <typename Src>
   struct Wrapper {
-    static constexpr typename UnderlyingType<Src>::type value(Src value) {
-      return value;
-    }
+    static constexpr UnderlyingType<Src> value(Src value) { return value; }
   };
 };
 
+template <typename T>
+ClampedNumeric(T) -> ClampedNumeric<T>;
+
 // Convenience wrapper to return a new ClampedNumeric from the provided
 // arithmetic or ClampedNumericType.
 template <typename T>
-constexpr ClampedNumeric<typename UnderlyingType<T>::type> MakeClampedNum(
-    const T value) {
+constexpr ClampedNumeric<UnderlyingType<T>> MakeClampedNum(T value) {
   return value;
 }
 
 // These implement the variadic wrapper for the math operations.
-template <template <typename, typename, typename> class M,
-          typename L,
-          typename R>
+template <template <typename, typename> class M, typename L, typename R>
 constexpr ClampedNumeric<typename MathWrapper<M, L, R>::type> ClampMathOp(
-    const L lhs,
-    const R rhs) {
+    L lhs,
+    R rhs) {
   using Math = typename MathWrapper<M, L, R>::math;
   return ClampedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
                                                                         rhs);
 }
 
 // General purpose wrapper template for arithmetic operations.
-template <template <typename, typename, typename> class M,
+template <template <typename, typename> class M,
           typename L,
           typename R,
           typename... Args>
-constexpr auto ClampMathOp(const L lhs, const R rhs, const Args... args) {
+constexpr auto ClampMathOp(L lhs, R rhs, Args... args) {
   return ClampMathOp<M>(ClampMathOp<M>(lhs, rhs), args...);
 }
 
diff --git a/base/numerics/clamped_math_impl.h b/base/numerics/clamped_math_impl.h
index 8533046..6ddd3ec 100644
--- a/base/numerics/clamped_math_impl.h
+++ b/base/numerics/clamped_math_impl.h
@@ -5,46 +5,43 @@
 #ifndef BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
 #define BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
 
-#include <stddef.h>
-#include <stdint.h>
+// IWYU pragma: private, include "base/numerics/clamped_math.h"
 
-#include <climits>
-#include <cmath>
-#include <cstdlib>
+#include <concepts>
 #include <limits>
 #include <type_traits>
 
 #include "base/numerics/checked_math.h"
 #include "base/numerics/safe_conversions.h"
-#include "base/numerics/safe_math_shared_impl.h"
+#include "base/numerics/safe_math_shared_impl.h"  // IWYU pragma: export
 
 namespace gurl_base {
 namespace internal {
 
-template <
-    typename T,
-    std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>>* = nullptr>
+template <typename T>
+  requires(std::signed_integral<T>)
 constexpr T SaturatedNegWrapper(T value) {
-  return IsConstantEvaluated() || !ClampedNegFastOp<T>::is_supported
+  return std::is_constant_evaluated() || !ClampedNegFastOp<T>::is_supported
              ? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
                     ? NegateWrapper(value)
                     : std::numeric_limits<T>::max())
              : ClampedNegFastOp<T>::Do(value);
 }
 
-template <
-    typename T,
-    std::enable_if_t<std::is_integral_v<T> && !std::is_signed_v<T>>* = nullptr>
+template <typename T>
+  requires(std::unsigned_integral<T>)
 constexpr T SaturatedNegWrapper(T value) {
   return T(0);
 }
 
-template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
+template <typename T>
+  requires(std::floating_point<T>)
 constexpr T SaturatedNegWrapper(T value) {
   return -value;
 }
 
-template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
+template <typename T>
+  requires(std::integral<T>)
 constexpr T SaturatedAbsWrapper(T value) {
   // The calculation below is a static identity for unsigned types, but for
   // signed integer types it provides a non-branching, saturated absolute value.
@@ -59,229 +56,204 @@
       IsValueNegative<T>(static_cast<T>(SafeUnsignedAbs(value))));
 }
 
-template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
+template <typename T>
+  requires(std::floating_point<T>)
 constexpr T SaturatedAbsWrapper(T value) {
   return value < 0 ? -value : value;
 }
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedAddOp {};
 
 template <typename T, typename U>
-struct ClampedAddOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct ClampedAddOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V = result_type>
+    requires(std::same_as<V, result_type> || kIsTypeInRangeForNumericType<U, V>)
   static constexpr V Do(T x, U y) {
-    if (!IsConstantEvaluated() && ClampedAddFastOp<T, U>::is_supported)
+    if (!std::is_constant_evaluated() && ClampedAddFastOp<T, U>::is_supported) {
       return ClampedAddFastOp<T, U>::template Do<V>(x, y);
-
-    static_assert(std::is_same_v<V, result_type> ||
-                      IsTypeInRangeForNumericType<U, V>::value,
-                  "The saturation result cannot be determined from the "
-                  "provided types.");
+    }
     const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
     V result = {};
-    return BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result)))
-               ? result
-               : saturated;
+    if (CheckedAddOp<T, U>::Do(x, y, &result)) [[likely]] {
+      return result;
+    }
+    return saturated;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedSubOp {};
 
 template <typename T, typename U>
-struct ClampedSubOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct ClampedSubOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V = result_type>
+    requires(std::same_as<V, result_type> || kIsTypeInRangeForNumericType<U, V>)
   static constexpr V Do(T x, U y) {
-    if (!IsConstantEvaluated() && ClampedSubFastOp<T, U>::is_supported)
+    if (!std::is_constant_evaluated() && ClampedSubFastOp<T, U>::is_supported) {
       return ClampedSubFastOp<T, U>::template Do<V>(x, y);
-
-    static_assert(std::is_same_v<V, result_type> ||
-                      IsTypeInRangeForNumericType<U, V>::value,
-                  "The saturation result cannot be determined from the "
-                  "provided types.");
+    }
     const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
     V result = {};
-    return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result)))
-               ? result
-               : saturated;
+    if (CheckedSubOp<T, U>::Do(x, y, &result)) [[likely]] {
+      return result;
+    }
+    return saturated;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedMulOp {};
 
 template <typename T, typename U>
-struct ClampedMulOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct ClampedMulOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
-    if (!IsConstantEvaluated() && ClampedMulFastOp<T, U>::is_supported)
+    if (!std::is_constant_evaluated() && ClampedMulFastOp<T, U>::is_supported) {
       return ClampedMulFastOp<T, U>::template Do<V>(x, y);
-
+    }
     V result = {};
     const V saturated =
         CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
-    return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result)))
-               ? result
-               : saturated;
+    if (CheckedMulOp<T, U>::Do(x, y, &result)) [[likely]] {
+      return result;
+    }
+    return saturated;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedDivOp {};
 
 template <typename T, typename U>
-struct ClampedDivOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct ClampedDivOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
     V result = {};
-    if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result))))
+    if ((CheckedDivOp<T, U>::Do(x, y, &result))) [[likely]] {
       return result;
+    }
     // Saturation goes to max, min, or NaN (if x is zero).
     return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y))
              : SaturationDefaultLimits<V>::NaN();
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedModOp {};
 
 template <typename T, typename U>
-struct ClampedModOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct ClampedModOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
     V result = {};
-    return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result)))
-               ? result
-               : x;
+    if (CheckedModOp<T, U>::Do(x, y, &result)) [[likely]] {
+      return result;
+    }
+    return x;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedLshOp {};
 
 // Left shift. Non-zero values saturate in the direction of the sign. A zero
 // shifted by any value always results in zero.
 template <typename T, typename U>
-struct ClampedLshOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
+  requires(std::integral<T> && std::unsigned_integral<U>)
+struct ClampedLshOp<T, U> {
   using result_type = T;
   template <typename V = result_type>
   static constexpr V Do(T x, U shift) {
-    static_assert(!std::is_signed_v<U>, "Shift value must be unsigned.");
-    if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
+    if (shift < std::numeric_limits<T>::digits) [[likely]] {
       // Shift as unsigned to avoid undefined behavior.
       V result = static_cast<V>(as_unsigned(x) << shift);
       // If the shift can be reversed, we know it was valid.
-      if (BASE_NUMERICS_LIKELY(result >> shift == x))
+      if (result >> shift == x) [[likely]] {
         return result;
+      }
     }
     return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedRshOp {};
 
 // Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
 template <typename T, typename U>
-struct ClampedRshOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
+  requires(std::integral<T> && std::unsigned_integral<U>)
+struct ClampedRshOp<T, U> {
   using result_type = T;
   template <typename V = result_type>
   static constexpr V Do(T x, U shift) {
-    static_assert(!std::is_signed_v<U>, "Shift value must be unsigned.");
     // Signed right shift is odd, because it saturates to -1 or 0.
     const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
-    return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
-               ? saturated_cast<V>(x >> shift)
-               : saturated;
+    if (shift < kIntegerBitsPlusSign<T>) [[likely]] {
+      return saturated_cast<V>(x >> shift);
+    }
+    return saturated;
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedAndOp {};
 
 template <typename T, typename U>
-struct ClampedAndOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename std::make_unsigned<
-      typename MaxExponentPromotion<T, U>::type>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct ClampedAndOp<T, U> {
+  using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
   template <typename V>
   static constexpr V Do(T x, U y) {
     return static_cast<result_type>(x) & static_cast<result_type>(y);
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedOrOp {};
 
 // For simplicity we promote to unsigned integers.
 template <typename T, typename U>
-struct ClampedOrOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename std::make_unsigned<
-      typename MaxExponentPromotion<T, U>::type>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct ClampedOrOp<T, U> {
+  using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
   template <typename V>
   static constexpr V Do(T x, U y) {
     return static_cast<result_type>(x) | static_cast<result_type>(y);
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedXorOp {};
 
 // For simplicity we support only unsigned integers.
 template <typename T, typename U>
-struct ClampedXorOp<
-    T,
-    U,
-    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
-  using result_type = typename std::make_unsigned<
-      typename MaxExponentPromotion<T, U>::type>::type;
+  requires(std::integral<T> && std::integral<U>)
+struct ClampedXorOp<T, U> {
+  using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
   template <typename V>
   static constexpr V Do(T x, U y) {
     return static_cast<result_type>(x) ^ static_cast<result_type>(y);
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedMaxOp {};
 
 template <typename T, typename U>
-struct ClampedMaxOp<
-    T,
-    U,
-    std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>>> {
-  using result_type = typename MaxExponentPromotion<T, U>::type;
+  requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
+struct ClampedMaxOp<T, U> {
+  using result_type = MaxExponentPromotion<T, U>;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
     return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x)
@@ -289,15 +261,13 @@
   }
 };
 
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
 struct ClampedMinOp {};
 
 template <typename T, typename U>
-struct ClampedMinOp<
-    T,
-    U,
-    std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>>> {
-  using result_type = typename LowestValuePromotion<T, U>::type;
+  requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
+struct ClampedMinOp<T, U> {
+  using result_type = LowestValuePromotion<T, U>;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
     return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x)
@@ -307,16 +277,15 @@
 
 // This is just boilerplate that wraps the standard floating point arithmetic.
 // A macro isn't the nicest solution, but it beats rewriting these repeatedly.
-#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)                                 \
-  template <typename T, typename U>                                         \
-  struct Clamped##NAME##Op<T, U,                                            \
-                           std::enable_if_t<std::is_floating_point_v<T> ||  \
-                                            std::is_floating_point_v<U>>> { \
-    using result_type = typename MaxExponentPromotion<T, U>::type;          \
-    template <typename V = result_type>                                     \
-    static constexpr V Do(T x, U y) {                                       \
-      return saturated_cast<V>(x OP y);                                     \
-    }                                                                       \
+#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)                    \
+  template <typename T, typename U>                            \
+    requires(std::floating_point<T> || std::floating_point<U>) \
+  struct Clamped##NAME##Op<T, U> {                             \
+    using result_type = MaxExponentPromotion<T, U>;            \
+    template <typename V = result_type>                        \
+    static constexpr V Do(T x, U y) {                          \
+      return saturated_cast<V>(x OP y);                        \
+    }                                                          \
   };
 
 BASE_FLOAT_ARITHMETIC_OPS(Add, +)
diff --git a/base/numerics/integral_constant_like.h b/base/numerics/integral_constant_like.h
new file mode 100644
index 0000000..679d841
--- /dev/null
+++ b/base/numerics/integral_constant_like.h
@@ -0,0 +1,25 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NUMERICS_INTEGRAL_CONSTANT_LIKE_H_
+#define BASE_NUMERICS_INTEGRAL_CONSTANT_LIKE_H_
+
+#include <concepts>
+#include <type_traits>
+
+namespace gurl_base {
+
+// Exposition-only concept from [span.syn]
+template <typename T>
+concept IntegralConstantLike =
+    std::is_integral_v<decltype(T::value)> &&
+    !std::is_same_v<bool, std::remove_const_t<decltype(T::value)>> &&
+    std::convertible_to<T, decltype(T::value)> &&
+    std::equality_comparable_with<T, decltype(T::value)> &&
+    std::bool_constant<T() == T::value>::value &&
+    std::bool_constant<static_cast<decltype(T::value)>(T()) == T::value>::value;
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_INTEGRAL_CONSTANT_LIKE_H_
diff --git a/base/numerics/math_constants.h b/base/numerics/math_constants.h
index 2819606..9ab8fcd 100644
--- a/base/numerics/math_constants.h
+++ b/base/numerics/math_constants.h
@@ -6,22 +6,6 @@
 #define BASE_NUMERICS_MATH_CONSTANTS_H_
 
 namespace gurl_base {
-
-constexpr double kPiDouble = 3.14159265358979323846;
-constexpr float kPiFloat = 3.14159265358979323846f;
-
-// pi/180 and 180/pi. These are correctly rounded from the true
-// mathematical value, unlike what you'd get from e.g.
-// 180.0f / kPiFloat.
-constexpr double kDegToRadDouble = 0.017453292519943295769;
-constexpr float kDegToRadFloat = 0.017453292519943295769f;
-constexpr double kRadToDegDouble = 57.295779513082320876798;
-constexpr float kRadToDegFloat = 57.295779513082320876798f;
-
-// sqrt(1/2) = 1/sqrt(2).
-constexpr double kSqrtHalfDouble = 0.70710678118654752440;
-constexpr float kSqrtHalfFloat = 0.70710678118654752440f;
-
 // The mean acceleration due to gravity on Earth in m/s^2.
 constexpr double kMeanGravityDouble = 9.80665;
 constexpr float kMeanGravityFloat = 9.80665f;
diff --git a/base/numerics/ostream_operators.h b/base/numerics/ostream_operators.h
index 100c37f..a37a44e 100644
--- a/base/numerics/ostream_operators.h
+++ b/base/numerics/ostream_operators.h
@@ -6,13 +6,16 @@
 #define BASE_NUMERICS_OSTREAM_OPERATORS_H_
 
 #include <ostream>
+#include <type_traits>
 
 namespace gurl_base {
 namespace internal {
 
 template <typename T>
+  requires std::is_arithmetic_v<T>
 class ClampedNumeric;
 template <typename T>
+  requires std::is_arithmetic_v<T>
 class StrictNumeric;
 
 // Overload the ostream output operator to make logging work nicely.
diff --git a/base/numerics/safe_conversions.h b/base/numerics/safe_conversions.h
index d09237e..173c06a 100644
--- a/base/numerics/safe_conversions.h
+++ b/base/numerics/safe_conversions.h
@@ -8,13 +8,14 @@
 #include <stddef.h>
 
 #include <cmath>
+#include <concepts>
 #include <limits>
 #include <type_traits>
 
-#include "base/numerics/safe_conversions_impl.h"
+#include "base/numerics/safe_conversions_impl.h"  // IWYU pragma: export
 
-#if defined(__ARMEL__) && !defined(__native_client__)
-#include "base/numerics/safe_conversions_arm_impl.h"
+#if defined(__ARMEL__)
+#include "base/numerics/safe_conversions_arm_impl.h"  // IWYU pragma: export
 #define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
 #else
 #define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
@@ -37,10 +38,10 @@
 
 // The following special case a few specific integer conversions where we can
 // eke out better performance than range checking.
-template <typename Dst, typename Src, typename Enable = void>
+template <typename Dst, typename Src>
 struct IsValueInRangeFastOp {
   static constexpr bool is_supported = false;
-  static constexpr bool Do(Src value) {
+  static constexpr bool Do(Src) {
     // Force a compile failure if instantiated.
     return CheckOnFailure::template HandleFailure<bool>();
   }
@@ -48,12 +49,9 @@
 
 // Signed to signed range comparison.
 template <typename Dst, typename Src>
-struct IsValueInRangeFastOp<
-    Dst,
-    Src,
-    std::enable_if_t<std::is_integral_v<Dst> && std::is_integral_v<Src> &&
-                     std::is_signed_v<Dst> && std::is_signed_v<Src> &&
-                     !IsTypeInRangeForNumericType<Dst, Src>::value>> {
+  requires(std::signed_integral<Dst> && std::signed_integral<Src> &&
+           !kIsTypeInRangeForNumericType<Dst, Src>)
+struct IsValueInRangeFastOp<Dst, Src> {
   static constexpr bool is_supported = true;
 
   static constexpr bool Do(Src value) {
@@ -65,31 +63,30 @@
 
 // Signed to unsigned range comparison.
 template <typename Dst, typename Src>
-struct IsValueInRangeFastOp<
-    Dst,
-    Src,
-    std::enable_if_t<std::is_integral_v<Dst> && std::is_integral_v<Src> &&
-                     !std::is_signed_v<Dst> && std::is_signed_v<Src> &&
-                     !IsTypeInRangeForNumericType<Dst, Src>::value>> {
+  requires(std::unsigned_integral<Dst> && std::signed_integral<Src> &&
+           !kIsTypeInRangeForNumericType<Dst, Src>)
+struct IsValueInRangeFastOp<Dst, Src> {
   static constexpr bool is_supported = true;
 
   static constexpr bool Do(Src value) {
     // We cast a signed as unsigned to overflow negative values to the top,
     // then compare against whichever maximum is smaller, as our upper bound.
-    return as_unsigned(value) <= as_unsigned(CommonMax<Src, Dst>());
+    return as_unsigned(value) <= as_unsigned(kCommonMax<Src, Dst>);
   }
 };
 
 // Convenience function that returns true if the supplied value is in range
 // for the destination type.
 template <typename Dst, typename Src>
+  requires(IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
+           std::numeric_limits<Dst>::lowest() < std::numeric_limits<Dst>::max())
 constexpr bool IsValueInRangeForNumericType(Src value) {
-  using SrcType = typename internal::UnderlyingType<Src>::type;
+  using SrcType = UnderlyingType<Src>;
+  const auto underlying_value = static_cast<SrcType>(value);
   return internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
              ? internal::IsValueInRangeFastOp<Dst, SrcType>::Do(
-                   static_cast<SrcType>(value))
-             : internal::DstRangeRelationToSrcRange<Dst>(
-                   static_cast<SrcType>(value))
+                   underlying_value)
+             : internal::DstRangeRelationToSrcRange<Dst>(underlying_value)
                    .IsValid();
 }
 
@@ -99,13 +96,15 @@
 template <typename Dst,
           class CheckHandler = internal::CheckOnFailure,
           typename Src>
+  requires(IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
+           std::numeric_limits<Dst>::lowest() < std::numeric_limits<Dst>::max())
 constexpr Dst checked_cast(Src value) {
   // This throws a compile-time error on evaluating the constexpr if it can be
   // determined at compile-time as failing, otherwise it will GURL_CHECK at runtime.
-  using SrcType = typename internal::UnderlyingType<Src>::type;
-  return BASE_NUMERICS_LIKELY((IsValueInRangeForNumericType<Dst>(value)))
-             ? static_cast<Dst>(static_cast<SrcType>(value))
-             : CheckHandler::template HandleFailure<Dst>();
+  if (IsValueInRangeForNumericType<Dst>(value)) [[likely]] {
+    return static_cast<Dst>(static_cast<UnderlyingType<Src>>(value));
+  }
+  return CheckHandler::template HandleFailure<Dst>();
 }
 
 // Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
@@ -154,21 +153,19 @@
 // We can reduce the number of conditions and get slightly better performance
 // for normal signed and unsigned integer ranges. And in the specific case of
 // Arm, we can use the optimized saturation instructions.
-template <typename Dst, typename Src, typename Enable = void>
+template <typename Dst, typename Src>
 struct SaturateFastOp {
   static constexpr bool is_supported = false;
-  static constexpr Dst Do(Src value) {
+  static constexpr Dst Do(Src) {
     // Force a compile failure if instantiated.
     return CheckOnFailure::template HandleFailure<Dst>();
   }
 };
 
 template <typename Dst, typename Src>
-struct SaturateFastOp<
-    Dst,
-    Src,
-    std::enable_if_t<std::is_integral_v<Src> && std::is_integral_v<Dst> &&
-                     SaturateFastAsmOp<Dst, Src>::is_supported>> {
+  requires(std::integral<Src> && std::integral<Dst> &&
+           SaturateFastAsmOp<Dst, Src>::is_supported)
+struct SaturateFastOp<Dst, Src> {
   static constexpr bool is_supported = true;
   static constexpr Dst Do(Src value) {
     return SaturateFastAsmOp<Dst, Src>::Do(value);
@@ -176,22 +173,21 @@
 };
 
 template <typename Dst, typename Src>
-struct SaturateFastOp<
-    Dst,
-    Src,
-    std::enable_if_t<std::is_integral_v<Src> && std::is_integral_v<Dst> &&
-                     !SaturateFastAsmOp<Dst, Src>::is_supported>> {
+  requires(std::integral<Src> && std::integral<Dst> &&
+           !SaturateFastAsmOp<Dst, Src>::is_supported)
+struct SaturateFastOp<Dst, Src> {
   static constexpr bool is_supported = true;
   static constexpr Dst Do(Src value) {
     // The exact order of the following is structured to hit the correct
     // optimization heuristics across compilers. Do not change without
     // checking the emitted code.
     const Dst saturated = CommonMaxOrMin<Dst, Src>(
-        IsMaxInRangeForNumericType<Dst, Src>() ||
-        (!IsMinInRangeForNumericType<Dst, Src>() && IsValueNegative(value)));
-    return BASE_NUMERICS_LIKELY(IsValueInRangeForNumericType<Dst>(value))
-               ? static_cast<Dst>(value)
-               : saturated;
+        kIsMaxInRangeForNumericType<Dst, Src> ||
+        (!kIsMinInRangeForNumericType<Dst, Src> && IsValueNegative(value)));
+    if (IsValueInRangeForNumericType<Dst>(value)) [[likely]] {
+      return static_cast<Dst>(value);
+    }
+    return saturated;
   }
 };
 
@@ -203,56 +199,47 @@
           template <typename> class SaturationHandler = SaturationDefaultLimits,
           typename Src>
 constexpr Dst saturated_cast(Src value) {
-  using SrcType = typename UnderlyingType<Src>::type;
-  return !IsConstantEvaluated() && SaturateFastOp<Dst, SrcType>::is_supported &&
+  using SrcType = UnderlyingType<Src>;
+  const auto underlying_value = static_cast<SrcType>(value);
+  return !std::is_constant_evaluated() &&
+                 SaturateFastOp<Dst, SrcType>::is_supported &&
                  std::is_same_v<SaturationHandler<Dst>,
                                 SaturationDefaultLimits<Dst>>
-             ? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
+             ? SaturateFastOp<Dst, SrcType>::Do(underlying_value)
              : saturated_cast_impl<Dst, SaturationHandler, SrcType>(
-                   static_cast<SrcType>(value),
+                   underlying_value,
                    DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
-                       static_cast<SrcType>(value)));
+                       underlying_value));
 }
 
 // strict_cast<> is analogous to static_cast<> for numeric types, except that
 // it will cause a compile failure if the destination type is not large enough
 // to contain any value in the source type. It performs no runtime checking.
-template <typename Dst, typename Src>
+template <typename Dst, typename Src, typename SrcType = UnderlyingType<Src>>
+  requires(
+      IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
+      // If you got here from a compiler error, it's because you tried to assign
+      // from a source type to a destination type that has insufficient range.
+      // The solution may be to change the destination type you're assigning to,
+      // and use one large enough to represent the source.
+      // Alternatively, you may be better served with the checked_cast<> or
+      // saturated_cast<> template functions for your particular use case.
+      kStaticDstRangeRelationToSrcRange<Dst, SrcType> ==
+          NumericRangeRepresentation::kContained)
 constexpr Dst strict_cast(Src value) {
-  using SrcType = typename UnderlyingType<Src>::type;
-  static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
-  static_assert(std::is_arithmetic_v<Dst>, "Result must be numeric.");
-
-  // If you got here from a compiler error, it's because you tried to assign
-  // from a source type to a destination type that has insufficient range.
-  // The solution may be to change the destination type you're assigning to,
-  // and use one large enough to represent the source.
-  // Alternatively, you may be better served with the checked_cast<> or
-  // saturated_cast<> template functions for your particular use case.
-  static_assert(StaticDstRangeRelationToSrcRange<Dst, SrcType>::value ==
-                    NUMERIC_RANGE_CONTAINED,
-                "The source type is out of range for the destination type. "
-                "Please see strict_cast<> comments for more information.");
-
   return static_cast<Dst>(static_cast<SrcType>(value));
 }
 
 // Some wrappers to statically check that a type is in range.
-template <typename Dst, typename Src, class Enable = void>
-struct IsNumericRangeContained {
-  static constexpr bool value = false;
-};
+template <typename Dst, typename Src>
+inline constexpr bool kIsNumericRangeContained = false;
 
 template <typename Dst, typename Src>
-struct IsNumericRangeContained<
-    Dst,
-    Src,
-    std::enable_if_t<ArithmeticOrUnderlyingEnum<Dst>::value &&
-                     ArithmeticOrUnderlyingEnum<Src>::value>> {
-  static constexpr bool value =
-      StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
-      NUMERIC_RANGE_CONTAINED;
-};
+  requires(std::is_arithmetic_v<ArithmeticOrUnderlyingEnum<Dst>> &&
+           std::is_arithmetic_v<ArithmeticOrUnderlyingEnum<Src>>)
+inline constexpr bool kIsNumericRangeContained<Dst, Src> =
+    kStaticDstRangeRelationToSrcRange<Dst, Src> ==
+    NumericRangeRepresentation::kContained;
 
 // StrictNumeric implements compile time range checking between numeric types by
 // wrapping assignment operations in a strict_cast. This class is intended to be
@@ -265,6 +252,7 @@
 // runtime checking of any of the associated mathematical operations. Use
 // CheckedNumeric for runtime range checks of the actual value being assigned.
 template <typename T>
+  requires std::is_arithmetic_v<T>
 class StrictNumeric {
  public:
   using type = T;
@@ -276,12 +264,6 @@
   constexpr StrictNumeric(const StrictNumeric<Src>& rhs)
       : value_(strict_cast<T>(rhs.value_)) {}
 
-  // Strictly speaking, this is not necessary, but declaring this allows class
-  // template argument deduction to be used so that it is possible to simply
-  // write `StrictNumeric(777)` instead of `StrictNumeric<int>(777)`.
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  constexpr StrictNumeric(T value) : value_(value) {}
-
   // This is not an explicit constructor because we implicitly upgrade regular
   // numerics to StrictNumerics to make them easier to use.
   template <typename Src>
@@ -300,30 +282,38 @@
   // to explicitly cast the result to the destination type.
   // If none of that works, you may be better served with the checked_cast<> or
   // saturated_cast<> template functions for your particular use case.
-  template <typename Dst,
-            std::enable_if_t<IsNumericRangeContained<Dst, T>::value>* = nullptr>
-  constexpr operator Dst() const {
-    return static_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(value_);
+  template <typename Dst>
+    requires(kIsNumericRangeContained<Dst, T>)
+  constexpr operator Dst() const {  // NOLINT(google-explicit-constructor)
+    return static_cast<ArithmeticOrUnderlyingEnum<Dst>>(value_);
   }
 
+  // Unary negation does not require any conversions.
+  constexpr bool operator!() const { return !value_; }
+
  private:
-  const T value_;
+  template <typename U>
+    requires std::is_arithmetic_v<U>
+  friend class StrictNumeric;
+
+  T value_;
 };
 
+template <typename T>
+StrictNumeric(T) -> StrictNumeric<T>;
+
 // Convenience wrapper returns a StrictNumeric from the provided arithmetic
 // type.
 template <typename T>
-constexpr StrictNumeric<typename UnderlyingType<T>::type> MakeStrictNum(
-    const T value) {
+constexpr StrictNumeric<UnderlyingType<T>> MakeStrictNum(const T value) {
   return value;
 }
 
-#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP)                     \
-  template <typename L, typename R,                                            \
-            std::enable_if_t<internal::Is##CLASS##Op<L, R>::value>* = nullptr> \
-  constexpr bool operator OP(const L lhs, const R rhs) {                       \
-    return SafeCompare<NAME, typename UnderlyingType<L>::type,                 \
-                       typename UnderlyingType<R>::type>(lhs, rhs);            \
+#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP)                    \
+  template <typename L, typename R>                                           \
+    requires(internal::Is##CLASS##Op<L, R>)                                   \
+  constexpr bool operator OP(L lhs, R rhs) {                                  \
+    return SafeCompare<NAME, UnderlyingType<L>, UnderlyingType<R>>(lhs, rhs); \
   }
 
 BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <)
@@ -338,9 +328,9 @@
 using internal::as_signed;
 using internal::as_unsigned;
 using internal::checked_cast;
-using internal::IsTypeInRangeForNumericType;
 using internal::IsValueInRangeForNumericType;
 using internal::IsValueNegative;
+using internal::kIsTypeInRangeForNumericType;
 using internal::MakeStrictNum;
 using internal::SafeUnsignedAbs;
 using internal::saturated_cast;
@@ -363,19 +353,15 @@
 // they round in nonstandard directions and will generally be slower.
 
 // Rounds towards negative infinity (i.e., down).
-template <typename Dst = int,
-          typename Src,
-          typename = std::enable_if_t<std::is_integral_v<Dst> &&
-                                      std::is_floating_point_v<Src>>>
+template <typename Dst = int, typename Src>
+  requires(std::integral<Dst> && std::floating_point<Src>)
 Dst ClampFloor(Src value) {
   return saturated_cast<Dst>(std::floor(value));
 }
 
 // Rounds towards positive infinity (i.e., up).
-template <typename Dst = int,
-          typename Src,
-          typename = std::enable_if_t<std::is_integral_v<Dst> &&
-                                      std::is_floating_point_v<Src>>>
+template <typename Dst = int, typename Src>
+  requires(std::integral<Dst> && std::floating_point<Src>)
 Dst ClampCeil(Src value) {
   return saturated_cast<Dst>(std::ceil(value));
 }
@@ -389,10 +375,8 @@
 // existing code expects. Compare with saturated_cast<Dst>(std::nearbyint(x))
 // or std::lrint(x), which would round 0.5 and -0.5 to 0 but 1.5 to 2 and
 // -1.5 to -2.
-template <typename Dst = int,
-          typename Src,
-          typename = std::enable_if_t<std::is_integral_v<Dst> &&
-                                      std::is_floating_point_v<Src>>>
+template <typename Dst = int, typename Src>
+  requires(std::integral<Dst> && std::floating_point<Src>)
 Dst ClampRound(Src value) {
   const Src rounded = std::round(value);
   return saturated_cast<Dst>(rounded);
diff --git a/base/numerics/safe_conversions_arm_impl.h b/base/numerics/safe_conversions_arm_impl.h
index 3e7d848..91f9add 100644
--- a/base/numerics/safe_conversions_arm_impl.h
+++ b/base/numerics/safe_conversions_arm_impl.h
@@ -5,8 +5,12 @@
 #ifndef BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
 #define BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
 
-#include <cassert>
-#include <limits>
+// IWYU pragma: private, include "base/numerics/safe_conversions.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <concepts>
 #include <type_traits>
 
 #include "base/numerics/safe_conversions_impl.h"
@@ -18,30 +22,28 @@
 template <typename Dst, typename Src>
 struct SaturateFastAsmOp {
   static constexpr bool is_supported =
-      kEnableAsmCode && std::is_signed_v<Src> && std::is_integral_v<Dst> &&
-      std::is_integral_v<Src> &&
-      IntegerBitsPlusSign<Src>::value <= IntegerBitsPlusSign<int32_t>::value &&
-      IntegerBitsPlusSign<Dst>::value <= IntegerBitsPlusSign<int32_t>::value &&
-      !IsTypeInRangeForNumericType<Dst, Src>::value;
+      kEnableAsmCode && std::signed_integral<Src> && std::integral<Dst> &&
+      kIntegerBitsPlusSign<Src> <= kIntegerBitsPlusSign<int32_t> &&
+      kIntegerBitsPlusSign<Dst> <= kIntegerBitsPlusSign<int32_t> &&
+      !kIsTypeInRangeForNumericType<Dst, Src>;
 
   __attribute__((always_inline)) static Dst Do(Src value) {
     int32_t src = value;
-    typename std::conditional<std::is_signed_v<Dst>, int32_t, uint32_t>::type
-        result;
-    if (std::is_signed_v<Dst>) {
+    if constexpr (std::is_signed_v<Dst>) {
+      int32_t result;
       asm("ssat %[dst], %[shift], %[src]"
           : [dst] "=r"(result)
-          : [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value <= 32
-                                            ? IntegerBitsPlusSign<Dst>::value
-                                            : 32));
+          : [src] "r"(src), [shift] "n"(
+                                std::min(kIntegerBitsPlusSign<Dst>, 32)));
+      return static_cast<Dst>(result);
     } else {
+      uint32_t result;
       asm("usat %[dst], %[shift], %[src]"
           : [dst] "=r"(result)
-          : [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value < 32
-                                            ? IntegerBitsPlusSign<Dst>::value
-                                            : 31));
+          : [src] "r"(src), [shift] "n"(
+                                std::min(kIntegerBitsPlusSign<Dst>, 31)));
+      return static_cast<Dst>(result);
     }
-    return static_cast<Dst>(result);
   }
 };
 
diff --git a/base/numerics/safe_conversions_impl.h b/base/numerics/safe_conversions_impl.h
index 6d5450f..ecc2cf9 100644
--- a/base/numerics/safe_conversions_impl.h
+++ b/base/numerics/safe_conversions_impl.h
@@ -5,70 +5,54 @@
 #ifndef BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
 #define BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
 
+// IWYU pragma: private, include "base/numerics/safe_conversions.h"
+
+#include <stddef.h>
 #include <stdint.h>
 
+#include <concepts>
 #include <limits>
 #include <type_traits>
+#include <utility>
 
-#if defined(__GNUC__) || defined(__clang__)
-#define BASE_NUMERICS_LIKELY(x) __builtin_expect(!!(x), 1)
-#define BASE_NUMERICS_UNLIKELY(x) __builtin_expect(!!(x), 0)
-#else
-#define BASE_NUMERICS_LIKELY(x) (x)
-#define BASE_NUMERICS_UNLIKELY(x) (x)
-#endif
+#include "base/numerics/integral_constant_like.h"
 
-namespace gurl_base {
-namespace internal {
+namespace gurl_base::internal {
 
 // The std library doesn't provide a binary max_exponent for integers, however
 // we can compute an analog using std::numeric_limits<>::digits.
 template <typename NumericType>
-struct MaxExponent {
-  static const int value = std::is_floating_point_v<NumericType>
-                               ? std::numeric_limits<NumericType>::max_exponent
-                               : std::numeric_limits<NumericType>::digits + 1;
-};
+inline constexpr int kMaxExponent =
+    std::is_floating_point_v<NumericType>
+        ? std::numeric_limits<NumericType>::max_exponent
+        : std::numeric_limits<NumericType>::digits + 1;
 
 // The number of bits (including the sign) in an integer. Eliminates sizeof
 // hacks.
 template <typename NumericType>
-struct IntegerBitsPlusSign {
-  static const int value =
-      std::numeric_limits<NumericType>::digits + std::is_signed_v<NumericType>;
-};
-
-// Helper templates for integer manipulations.
-
-template <typename Integer>
-struct PositionOfSignBit {
-  static const size_t value = IntegerBitsPlusSign<Integer>::value - 1;
-};
+inline constexpr int kIntegerBitsPlusSign =
+    std::numeric_limits<NumericType>::digits + std::is_signed_v<NumericType>;
 
 // Determines if a numeric value is negative without throwing compiler
 // warnings on: unsigned(value) < 0.
-template <typename T, std::enable_if_t<std::is_signed_v<T>>* = nullptr>
+template <typename T>
+  requires(std::is_arithmetic_v<T>)
 constexpr bool IsValueNegative(T value) {
-  static_assert(std::is_arithmetic_v<T>, "Argument must be numeric.");
-  return value < 0;
-}
-
-template <typename T, std::enable_if_t<!std::is_signed_v<T>>* = nullptr>
-constexpr bool IsValueNegative(T) {
-  static_assert(std::is_arithmetic_v<T>, "Argument must be numeric.");
-  return false;
+  if constexpr (std::is_signed_v<T>) {
+    return value < 0;
+  } else {
+    return false;
+  }
 }
 
 // This performs a fast negation, returning a signed value. It works on unsigned
 // arguments, but probably doesn't do what you want for any unsigned value
 // larger than max / 2 + 1 (i.e. signed min cast to unsigned).
 template <typename T>
-constexpr typename std::make_signed<T>::type ConditionalNegate(
-    T x,
-    bool is_negative) {
-  static_assert(std::is_integral_v<T>, "Type must be integral");
-  using SignedT = typename std::make_signed<T>::type;
-  using UnsignedT = typename std::make_unsigned<T>::type;
+  requires std::is_integral_v<T>
+constexpr auto ConditionalNegate(T x, bool is_negative) {
+  using SignedT = std::make_signed_t<T>;
+  using UnsignedT = std::make_unsigned_t<T>;
   return static_cast<SignedT>((static_cast<UnsignedT>(x) ^
                                static_cast<UnsignedT>(-SignedT(is_negative))) +
                               is_negative);
@@ -76,28 +60,24 @@
 
 // This performs a safe, absolute value via unsigned overflow.
 template <typename T>
-constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
-  static_assert(std::is_integral_v<T>, "Type must be integral");
-  using UnsignedT = typename std::make_unsigned<T>::type;
+  requires std::is_integral_v<T>
+constexpr auto SafeUnsignedAbs(T value) {
+  using UnsignedT = std::make_unsigned_t<T>;
   return IsValueNegative(value)
              ? static_cast<UnsignedT>(0u - static_cast<UnsignedT>(value))
              : static_cast<UnsignedT>(value);
 }
 
-// TODO(jschuh): Switch to std::is_constant_evaluated() once C++20 is supported.
-// Alternately, the usage could be restructured for "consteval if" in C++23.
-#define IsConstantEvaluated() (__builtin_is_constant_evaluated())
-
 // TODO(jschuh): Debug builds don't reliably propagate constants, so we restrict
 // some accelerated runtime paths to release builds until this can be forced
 // with consteval support in C++20 or C++23.
 #if defined(NDEBUG)
-constexpr bool kEnableAsmCode = true;
+inline constexpr bool kEnableAsmCode = true;
 #else
-constexpr bool kEnableAsmCode = false;
+inline constexpr bool kEnableAsmCode = false;
 #endif
 
-// Forces a crash, like a GURL_CHECK(false). Used for numeric boundary errors.
+// Forces a crash, like a GURL_NOTREACHED(). Used for numeric boundary errors.
 // Also used in a constexpr template to trigger a compilation failure on
 // an error condition.
 struct CheckOnFailure {
@@ -114,92 +94,76 @@
   }
 };
 
-enum IntegerRepresentation {
-  INTEGER_REPRESENTATION_UNSIGNED,
-  INTEGER_REPRESENTATION_SIGNED
-};
+enum class IntegerRepresentation { kUnsigned, kSigned };
 
 // A range for a given nunmeric Src type is contained for a given numeric Dst
 // type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and
 // numeric_limits<Src>::lowest() >= numeric_limits<Dst>::lowest() are true.
 // We implement this as template specializations rather than simple static
 // comparisons to ensure type correctness in our comparisons.
-enum NumericRangeRepresentation {
-  NUMERIC_RANGE_NOT_CONTAINED,
-  NUMERIC_RANGE_CONTAINED
-};
+enum class NumericRangeRepresentation { kNotContained, kContained };
 
 // Helper templates to statically determine if our destination type can contain
 // maximum and minimum values represented by the source type.
 
+// Default case, used for same sign: Dst is guaranteed to contain Src only if
+// its range is equal or larger.
 template <typename Dst,
           typename Src,
-          IntegerRepresentation DstSign = std::is_signed_v<Dst>
-                                              ? INTEGER_REPRESENTATION_SIGNED
-                                              : INTEGER_REPRESENTATION_UNSIGNED,
-          IntegerRepresentation SrcSign = std::is_signed_v<Src>
-                                              ? INTEGER_REPRESENTATION_SIGNED
-                                              : INTEGER_REPRESENTATION_UNSIGNED>
-struct StaticDstRangeRelationToSrcRange;
-
-// Same sign: Dst is guaranteed to contain Src only if its range is equal or
-// larger.
-template <typename Dst, typename Src, IntegerRepresentation Sign>
-struct StaticDstRangeRelationToSrcRange<Dst, Src, Sign, Sign> {
-  static const NumericRangeRepresentation value =
-      MaxExponent<Dst>::value >= MaxExponent<Src>::value
-          ? NUMERIC_RANGE_CONTAINED
-          : NUMERIC_RANGE_NOT_CONTAINED;
-};
+          IntegerRepresentation DstSign =
+              std::is_signed_v<Dst> ? IntegerRepresentation::kSigned
+                                    : IntegerRepresentation::kUnsigned,
+          IntegerRepresentation SrcSign =
+              std::is_signed_v<Src> ? IntegerRepresentation::kSigned
+                                    : IntegerRepresentation::kUnsigned>
+inline constexpr auto kStaticDstRangeRelationToSrcRange =
+    kMaxExponent<Dst> >= kMaxExponent<Src>
+        ? NumericRangeRepresentation::kContained
+        : NumericRangeRepresentation::kNotContained;
 
 // Unsigned to signed: Dst is guaranteed to contain source only if its range is
 // larger.
 template <typename Dst, typename Src>
-struct StaticDstRangeRelationToSrcRange<Dst,
-                                        Src,
-                                        INTEGER_REPRESENTATION_SIGNED,
-                                        INTEGER_REPRESENTATION_UNSIGNED> {
-  static const NumericRangeRepresentation value =
-      MaxExponent<Dst>::value > MaxExponent<Src>::value
-          ? NUMERIC_RANGE_CONTAINED
-          : NUMERIC_RANGE_NOT_CONTAINED;
-};
+inline constexpr auto
+    kStaticDstRangeRelationToSrcRange<Dst,
+                                      Src,
+                                      IntegerRepresentation::kSigned,
+                                      IntegerRepresentation::kUnsigned> =
+        kMaxExponent<Dst> > kMaxExponent<Src>
+            ? NumericRangeRepresentation::kContained
+            : NumericRangeRepresentation::kNotContained;
 
 // Signed to unsigned: Dst cannot be statically determined to contain Src.
 template <typename Dst, typename Src>
-struct StaticDstRangeRelationToSrcRange<Dst,
-                                        Src,
-                                        INTEGER_REPRESENTATION_UNSIGNED,
-                                        INTEGER_REPRESENTATION_SIGNED> {
-  static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED;
-};
+inline constexpr auto
+    kStaticDstRangeRelationToSrcRange<Dst,
+                                      Src,
+                                      IntegerRepresentation::kUnsigned,
+                                      IntegerRepresentation::kSigned> =
+        NumericRangeRepresentation::kNotContained;
 
 // This class wraps the range constraints as separate booleans so the compiler
 // can identify constants and eliminate unused code paths.
 class RangeCheck {
  public:
+  constexpr RangeCheck() = default;
   constexpr RangeCheck(bool is_in_lower_bound, bool is_in_upper_bound)
       : is_underflow_(!is_in_lower_bound), is_overflow_(!is_in_upper_bound) {}
-  constexpr RangeCheck() : is_underflow_(false), is_overflow_(false) {}
+
+  constexpr bool operator==(const RangeCheck& rhs) const = default;
+
   constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; }
   constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; }
   constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; }
   constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; }
   constexpr bool IsOverflowFlagSet() const { return is_overflow_; }
   constexpr bool IsUnderflowFlagSet() const { return is_underflow_; }
-  constexpr bool operator==(const RangeCheck rhs) const {
-    return is_underflow_ == rhs.is_underflow_ &&
-           is_overflow_ == rhs.is_overflow_;
-  }
-  constexpr bool operator!=(const RangeCheck rhs) const {
-    return !(*this == rhs);
-  }
 
  private:
   // Do not change the order of these member variables. The integral conversion
   // optimization depends on this exact order.
-  const bool is_underflow_;
-  const bool is_overflow_;
+  const bool is_underflow_ = false;
+  const bool is_overflow_ = false;
 };
 
 // The following helper template addresses a corner case in range checks for
@@ -226,70 +190,55 @@
 template <typename Dst, typename Src, template <typename> class Bounds>
 struct NarrowingRange {
   using SrcLimits = std::numeric_limits<Src>;
-  using DstLimits = typename std::numeric_limits<Dst>;
+  using DstLimits = std::numeric_limits<Dst>;
 
   // Computes the mask required to make an accurate comparison between types.
-  static const int kShift =
-      (MaxExponent<Src>::value > MaxExponent<Dst>::value &&
-       SrcLimits::digits < DstLimits::digits)
-          ? (DstLimits::digits - SrcLimits::digits)
-          : 0;
-  template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
+  static constexpr int kShift = (kMaxExponent<Src> > kMaxExponent<Dst> &&
+                                 SrcLimits::digits < DstLimits::digits)
+                                    ? (DstLimits::digits - SrcLimits::digits)
+                                    : 0;
 
+  template <typename T>
+    requires(std::same_as<T, Dst> &&
+             ((std::integral<T> && kShift < DstLimits::digits) ||
+              (std::floating_point<T> && kShift == 0)))
   // Masks out the integer bits that are beyond the precision of the
   // intermediate type used for comparison.
   static constexpr T Adjust(T value) {
-    static_assert(std::is_same_v<T, Dst>, "");
-    static_assert(kShift < DstLimits::digits, "");
-    using UnsignedDst = typename std::make_unsigned_t<T>;
-    return static_cast<T>(ConditionalNegate(
-        SafeUnsignedAbs(value) & ~((UnsignedDst{1} << kShift) - UnsignedDst{1}),
-        IsValueNegative(value)));
-  }
-
-  template <typename T,
-            std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
-  static constexpr T Adjust(T value) {
-    static_assert(std::is_same_v<T, Dst>, "");
-    static_assert(kShift == 0, "");
-    return value;
+    if constexpr (std::integral<T>) {
+      using UnsignedDst = typename std::make_unsigned_t<T>;
+      return static_cast<T>(
+          ConditionalNegate(SafeUnsignedAbs(value) &
+                                ~((UnsignedDst{1} << kShift) - UnsignedDst{1}),
+                            IsValueNegative(value)));
+    } else {
+      return value;
+    }
   }
 
   static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); }
   static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); }
 };
 
-template <typename Dst,
-          typename Src,
-          template <typename>
-          class Bounds,
-          IntegerRepresentation DstSign = std::is_signed_v<Dst>
-                                              ? INTEGER_REPRESENTATION_SIGNED
-                                              : INTEGER_REPRESENTATION_UNSIGNED,
-          IntegerRepresentation SrcSign = std::is_signed_v<Src>
-                                              ? INTEGER_REPRESENTATION_SIGNED
-                                              : INTEGER_REPRESENTATION_UNSIGNED,
-          NumericRangeRepresentation DstRange =
-              StaticDstRangeRelationToSrcRange<Dst, Src>::value>
-struct DstRangeRelationToSrcRangeImpl;
-
 // The following templates are for ranges that must be verified at runtime. We
 // split it into checks based on signedness to avoid confusing casts and
 // compiler warnings on signed an unsigned comparisons.
 
-// Same sign narrowing: The range is contained for normal limits.
+// Default case, used for same sign narrowing: The range is contained for normal
+// limits.
 template <typename Dst,
           typename Src,
           template <typename>
           class Bounds,
-          IntegerRepresentation DstSign,
-          IntegerRepresentation SrcSign>
-struct DstRangeRelationToSrcRangeImpl<Dst,
-                                      Src,
-                                      Bounds,
-                                      DstSign,
-                                      SrcSign,
-                                      NUMERIC_RANGE_CONTAINED> {
+          IntegerRepresentation DstSign =
+              std::is_signed_v<Dst> ? IntegerRepresentation::kSigned
+                                    : IntegerRepresentation::kUnsigned,
+          IntegerRepresentation SrcSign =
+              std::is_signed_v<Src> ? IntegerRepresentation::kSigned
+                                    : IntegerRepresentation::kUnsigned,
+          NumericRangeRepresentation DstRange =
+              kStaticDstRangeRelationToSrcRange<Dst, Src>>
+struct DstRangeRelationToSrcRangeImpl {
   static constexpr RangeCheck Check(Src value) {
     using SrcLimits = std::numeric_limits<Src>;
     using DstLimits = NarrowingRange<Dst, Src, Bounds>;
@@ -304,12 +253,13 @@
 // Signed to signed narrowing: Both the upper and lower boundaries may be
 // exceeded for standard limits.
 template <typename Dst, typename Src, template <typename> class Bounds>
-struct DstRangeRelationToSrcRangeImpl<Dst,
-                                      Src,
-                                      Bounds,
-                                      INTEGER_REPRESENTATION_SIGNED,
-                                      INTEGER_REPRESENTATION_SIGNED,
-                                      NUMERIC_RANGE_NOT_CONTAINED> {
+struct DstRangeRelationToSrcRangeImpl<
+    Dst,
+    Src,
+    Bounds,
+    IntegerRepresentation::kSigned,
+    IntegerRepresentation::kSigned,
+    NumericRangeRepresentation::kNotContained> {
   static constexpr RangeCheck Check(Src value) {
     using DstLimits = NarrowingRange<Dst, Src, Bounds>;
     return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max());
@@ -319,32 +269,34 @@
 // Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
 // standard limits.
 template <typename Dst, typename Src, template <typename> class Bounds>
-struct DstRangeRelationToSrcRangeImpl<Dst,
-                                      Src,
-                                      Bounds,
-                                      INTEGER_REPRESENTATION_UNSIGNED,
-                                      INTEGER_REPRESENTATION_UNSIGNED,
-                                      NUMERIC_RANGE_NOT_CONTAINED> {
+struct DstRangeRelationToSrcRangeImpl<
+    Dst,
+    Src,
+    Bounds,
+    IntegerRepresentation::kUnsigned,
+    IntegerRepresentation::kUnsigned,
+    NumericRangeRepresentation::kNotContained> {
   static constexpr RangeCheck Check(Src value) {
     using DstLimits = NarrowingRange<Dst, Src, Bounds>;
     return RangeCheck(
-        DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest(),
+        DstLimits::lowest() == Dst{0} || value >= DstLimits::lowest(),
         value <= DstLimits::max());
   }
 };
 
 // Unsigned to signed: Only the upper bound can be exceeded for standard limits.
 template <typename Dst, typename Src, template <typename> class Bounds>
-struct DstRangeRelationToSrcRangeImpl<Dst,
-                                      Src,
-                                      Bounds,
-                                      INTEGER_REPRESENTATION_SIGNED,
-                                      INTEGER_REPRESENTATION_UNSIGNED,
-                                      NUMERIC_RANGE_NOT_CONTAINED> {
+struct DstRangeRelationToSrcRangeImpl<
+    Dst,
+    Src,
+    Bounds,
+    IntegerRepresentation::kSigned,
+    IntegerRepresentation::kUnsigned,
+    NumericRangeRepresentation::kNotContained> {
   static constexpr RangeCheck Check(Src value) {
     using DstLimits = NarrowingRange<Dst, Src, Bounds>;
     using Promotion = decltype(Src() + Dst());
-    return RangeCheck(DstLimits::lowest() <= Dst(0) ||
+    return RangeCheck(DstLimits::lowest() <= Dst{0} ||
                           static_cast<Promotion>(value) >=
                               static_cast<Promotion>(DstLimits::lowest()),
                       static_cast<Promotion>(value) <=
@@ -355,23 +307,24 @@
 // Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
 // and any negative value exceeds the lower boundary for standard limits.
 template <typename Dst, typename Src, template <typename> class Bounds>
-struct DstRangeRelationToSrcRangeImpl<Dst,
-                                      Src,
-                                      Bounds,
-                                      INTEGER_REPRESENTATION_UNSIGNED,
-                                      INTEGER_REPRESENTATION_SIGNED,
-                                      NUMERIC_RANGE_NOT_CONTAINED> {
+struct DstRangeRelationToSrcRangeImpl<
+    Dst,
+    Src,
+    Bounds,
+    IntegerRepresentation::kUnsigned,
+    IntegerRepresentation::kSigned,
+    NumericRangeRepresentation::kNotContained> {
   static constexpr RangeCheck Check(Src value) {
     using SrcLimits = std::numeric_limits<Src>;
     using DstLimits = NarrowingRange<Dst, Src, Bounds>;
     using Promotion = decltype(Src() + Dst());
-    bool ge_zero = false;
+    bool ge_zero;
     // Converting floating-point to integer will discard fractional part, so
     // values in (-1.0, -0.0) will truncate to 0 and fit in Dst.
-    if (std::is_floating_point_v<Src>) {
-      ge_zero = value > Src(-1);
+    if constexpr (std::is_floating_point_v<Src>) {
+      ge_zero = value > Src{-1};
     } else {
-      ge_zero = value >= Src(0);
+      ge_zero = value >= Src{0};
     }
     return RangeCheck(
         ge_zero && (DstLimits::lowest() == 0 ||
@@ -385,30 +338,28 @@
 
 // Simple wrapper for statically checking if a type's range is contained.
 template <typename Dst, typename Src>
-struct IsTypeInRangeForNumericType {
-  static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
-                            NUMERIC_RANGE_CONTAINED;
-};
+inline constexpr bool kIsTypeInRangeForNumericType =
+    kStaticDstRangeRelationToSrcRange<Dst, Src> ==
+    NumericRangeRepresentation::kContained;
 
 template <typename Dst,
           template <typename> class Bounds = std::numeric_limits,
           typename Src>
+  requires(std::is_arithmetic_v<Src> && std::is_arithmetic_v<Dst> &&
+           Bounds<Dst>::lowest() < Bounds<Dst>::max())
 constexpr RangeCheck DstRangeRelationToSrcRange(Src value) {
-  static_assert(std::is_arithmetic_v<Src>, "Argument must be numeric.");
-  static_assert(std::is_arithmetic_v<Dst>, "Result must be numeric.");
-  static_assert(Bounds<Dst>::lowest() < Bounds<Dst>::max(), "");
   return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value);
 }
 
 // Integer promotion templates used by the portable checked integer arithmetic.
 template <size_t Size, bool IsSigned>
-struct IntegerForDigitsAndSign;
+struct IntegerForDigitsAndSignImpl;
 
-#define INTEGER_FOR_DIGITS_AND_SIGN(I)                          \
-  template <>                                                   \
-  struct IntegerForDigitsAndSign<IntegerBitsPlusSign<I>::value, \
-                                 std::is_signed_v<I>> {         \
-    using type = I;                                             \
+#define INTEGER_FOR_DIGITS_AND_SIGN(I)                        \
+  template <>                                                 \
+  struct IntegerForDigitsAndSignImpl<kIntegerBitsPlusSign<I>, \
+                                     std::is_signed_v<I>> {   \
+    using type = I;                                           \
   }
 
 INTEGER_FOR_DIGITS_AND_SIGN(int8_t);
@@ -421,420 +372,353 @@
 INTEGER_FOR_DIGITS_AND_SIGN(uint64_t);
 #undef INTEGER_FOR_DIGITS_AND_SIGN
 
+template <size_t Size, bool IsSigned>
+using IntegerForDigitsAndSign =
+    IntegerForDigitsAndSignImpl<Size, IsSigned>::type;
+
 // WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to
 // support 128-bit math, then the ArithmeticPromotion template below will need
 // to be updated (or more likely replaced with a decltype expression).
-static_assert(IntegerBitsPlusSign<intmax_t>::value == 64,
+static_assert(kIntegerBitsPlusSign<intmax_t> == 64,
               "Max integer size not supported for this toolchain.");
 
 template <typename Integer, bool IsSigned = std::is_signed_v<Integer>>
-struct TwiceWiderInteger {
-  using type =
-      typename IntegerForDigitsAndSign<IntegerBitsPlusSign<Integer>::value * 2,
-                                       IsSigned>::type;
-};
-
-enum ArithmeticPromotionCategory {
-  LEFT_PROMOTION,  // Use the type of the left-hand argument.
-  RIGHT_PROMOTION  // Use the type of the right-hand argument.
-};
+using TwiceWiderInteger =
+    IntegerForDigitsAndSign<kIntegerBitsPlusSign<Integer> * 2, IsSigned>;
 
 // Determines the type that can represent the largest positive value.
-template <typename Lhs,
-          typename Rhs,
-          ArithmeticPromotionCategory Promotion =
-              (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value)
-                  ? LEFT_PROMOTION
-                  : RIGHT_PROMOTION>
-struct MaxExponentPromotion;
-
 template <typename Lhs, typename Rhs>
-struct MaxExponentPromotion<Lhs, Rhs, LEFT_PROMOTION> {
-  using type = Lhs;
-};
-
-template <typename Lhs, typename Rhs>
-struct MaxExponentPromotion<Lhs, Rhs, RIGHT_PROMOTION> {
-  using type = Rhs;
-};
+using MaxExponentPromotion =
+    std::conditional_t<(kMaxExponent<Lhs> > kMaxExponent<Rhs>), Lhs, Rhs>;
 
 // Determines the type that can represent the lowest arithmetic value.
-template <typename Lhs,
-          typename Rhs,
-          ArithmeticPromotionCategory Promotion =
-              std::is_signed_v<Lhs>
-                  ? (std::is_signed_v<Rhs>
-                         ? (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value
-                                ? LEFT_PROMOTION
-                                : RIGHT_PROMOTION)
-                         : LEFT_PROMOTION)
-                  : (std::is_signed_v<Rhs>
-                         ? RIGHT_PROMOTION
-                         : (MaxExponent<Lhs>::value < MaxExponent<Rhs>::value
-                                ? LEFT_PROMOTION
-                                : RIGHT_PROMOTION))>
-struct LowestValuePromotion;
-
 template <typename Lhs, typename Rhs>
-struct LowestValuePromotion<Lhs, Rhs, LEFT_PROMOTION> {
-  using type = Lhs;
-};
-
-template <typename Lhs, typename Rhs>
-struct LowestValuePromotion<Lhs, Rhs, RIGHT_PROMOTION> {
-  using type = Rhs;
-};
+using LowestValuePromotion = std::conditional_t<
+    std::is_signed_v<Lhs>
+        ? (!std::is_signed_v<Rhs> || kMaxExponent<Lhs> > kMaxExponent<Rhs>)
+        : (!std::is_signed_v<Rhs> && kMaxExponent<Lhs> < kMaxExponent<Rhs>),
+    Lhs,
+    Rhs>;
 
 // Determines the type that is best able to represent an arithmetic result.
-template <
-    typename Lhs,
-    typename Rhs = Lhs,
-    bool is_intmax_type =
-        std::is_integral_v<typename MaxExponentPromotion<Lhs, Rhs>::type> &&
-        IntegerBitsPlusSign<typename MaxExponentPromotion<Lhs, Rhs>::type>::
-                value == IntegerBitsPlusSign<intmax_t>::value,
-    bool is_max_exponent = StaticDstRangeRelationToSrcRange<
-                               typename MaxExponentPromotion<Lhs, Rhs>::type,
-                               Lhs>::value == NUMERIC_RANGE_CONTAINED &&
-                           StaticDstRangeRelationToSrcRange<
-                               typename MaxExponentPromotion<Lhs, Rhs>::type,
-                               Rhs>::value == NUMERIC_RANGE_CONTAINED>
-struct BigEnoughPromotion;
 
-// The side with the max exponent is big enough.
-template <typename Lhs, typename Rhs, bool is_intmax_type>
-struct BigEnoughPromotion<Lhs, Rhs, is_intmax_type, true> {
-  using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
-  static const bool is_contained = true;
+// Default case, used when the side with the max exponent is big enough.
+template <typename Lhs,
+          typename Rhs = Lhs,
+          bool is_intmax_type =
+              std::is_integral_v<MaxExponentPromotion<Lhs, Rhs>> &&
+              kIntegerBitsPlusSign<MaxExponentPromotion<Lhs, Rhs>> ==
+                  kIntegerBitsPlusSign<intmax_t>,
+          bool is_max_exponent =
+              kStaticDstRangeRelationToSrcRange<MaxExponentPromotion<Lhs, Rhs>,
+                                                Lhs> ==
+                  NumericRangeRepresentation::kContained &&
+              kStaticDstRangeRelationToSrcRange<MaxExponentPromotion<Lhs, Rhs>,
+                                                Rhs> ==
+                  NumericRangeRepresentation::kContained>
+struct BigEnoughPromotionImpl {
+  using type = MaxExponentPromotion<Lhs, Rhs>;
+  static constexpr bool kContained = true;
 };
 
 // We can use a twice wider type to fit.
 template <typename Lhs, typename Rhs>
-struct BigEnoughPromotion<Lhs, Rhs, false, false> {
+struct BigEnoughPromotionImpl<Lhs, Rhs, false, false> {
   using type =
-      typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
-                                 std::is_signed_v<Lhs> ||
-                                     std::is_signed_v<Rhs>>::type;
-  static const bool is_contained = true;
+      TwiceWiderInteger<MaxExponentPromotion<Lhs, Rhs>,
+                        std::is_signed_v<Lhs> || std::is_signed_v<Rhs>>;
+  static constexpr bool kContained = true;
 };
 
 // No type is large enough.
 template <typename Lhs, typename Rhs>
-struct BigEnoughPromotion<Lhs, Rhs, true, false> {
-  using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
-  static const bool is_contained = false;
+struct BigEnoughPromotionImpl<Lhs, Rhs, true, false> {
+  using type = MaxExponentPromotion<Lhs, Rhs>;
+  static constexpr bool kContained = false;
 };
 
+template <typename Lhs, typename Rhs>
+using BigEnoughPromotion = BigEnoughPromotionImpl<Lhs, Rhs>::type;
+
+template <typename Lhs, typename Rhs>
+inline constexpr bool kIsBigEnoughPromotionContained =
+    BigEnoughPromotionImpl<Lhs, Rhs>::kContained;
+
 // We can statically check if operations on the provided types can wrap, so we
 // can skip the checked operations if they're not needed. So, for an integer we
 // care if the destination type preserves the sign and is twice the width of
 // the source.
 template <typename T, typename Lhs, typename Rhs = Lhs>
-struct IsIntegerArithmeticSafe {
-  static const bool value =
-      !std::is_floating_point_v<T> && !std::is_floating_point_v<Lhs> &&
-      !std::is_floating_point_v<Rhs> &&
-      std::is_signed_v<T> >= std::is_signed_v<Lhs> &&
-      IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Lhs>::value) &&
-      std::is_signed_v<T> >= std::is_signed_v<Rhs> &&
-      IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Rhs>::value);
-};
+inline constexpr bool kIsIntegerArithmeticSafe =
+    !std::is_floating_point_v<T> && !std::is_floating_point_v<Lhs> &&
+    !std::is_floating_point_v<Rhs> &&
+    std::is_signed_v<T> >= std::is_signed_v<Lhs> &&
+    kIntegerBitsPlusSign<T> >=
+        (2 * kIntegerBitsPlusSign<Lhs>)&&std::is_signed_v<T> >=
+        std::is_signed_v<Rhs> &&
+    kIntegerBitsPlusSign<T> >= (2 * kIntegerBitsPlusSign<Rhs>);
 
 // Promotes to a type that can represent any possible result of a binary
 // arithmetic operation with the source types.
-template <typename Lhs,
-          typename Rhs,
-          bool is_promotion_possible = IsIntegerArithmeticSafe<
-              typename std::conditional<std::is_signed_v<Lhs> ||
-                                            std::is_signed_v<Rhs>,
-                                        intmax_t,
-                                        uintmax_t>::type,
-              typename MaxExponentPromotion<Lhs, Rhs>::type>::value>
-struct FastIntegerArithmeticPromotion;
-
 template <typename Lhs, typename Rhs>
-struct FastIntegerArithmeticPromotion<Lhs, Rhs, true> {
-  using type =
-      typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
-                                 std::is_signed_v<Lhs> ||
-                                     std::is_signed_v<Rhs>>::type;
-  static_assert(IsIntegerArithmeticSafe<type, Lhs, Rhs>::value, "");
-  static const bool is_contained = true;
+struct FastIntegerArithmeticPromotionImpl {
+  using type = BigEnoughPromotion<Lhs, Rhs>;
+  static constexpr bool kContained = false;
 };
 
 template <typename Lhs, typename Rhs>
-struct FastIntegerArithmeticPromotion<Lhs, Rhs, false> {
-  using type = typename BigEnoughPromotion<Lhs, Rhs>::type;
-  static const bool is_contained = false;
+  requires(kIsIntegerArithmeticSafe<
+           std::conditional_t<std::is_signed_v<Lhs> || std::is_signed_v<Rhs>,
+                              intmax_t,
+                              uintmax_t>,
+           MaxExponentPromotion<Lhs, Rhs>>)
+struct FastIntegerArithmeticPromotionImpl<Lhs, Rhs> {
+  using type =
+      TwiceWiderInteger<MaxExponentPromotion<Lhs, Rhs>,
+                        std::is_signed_v<Lhs> || std::is_signed_v<Rhs>>;
+  static_assert(kIsIntegerArithmeticSafe<type, Lhs, Rhs>);
+  static constexpr bool kContained = true;
+};
+
+template <typename Lhs, typename Rhs>
+using FastIntegerArithmeticPromotion =
+    FastIntegerArithmeticPromotionImpl<Lhs, Rhs>::type;
+
+template <typename Lhs, typename Rhs>
+inline constexpr bool kIsFastIntegerArithmeticPromotionContained =
+    FastIntegerArithmeticPromotionImpl<Lhs, Rhs>::kContained;
+
+template <typename T>
+struct ArithmeticOrIntegralConstant {
+  using type = T;
+};
+
+template <typename T>
+  requires IntegralConstantLike<T>
+struct ArithmeticOrIntegralConstant<T> {
+  using type = T::value_type;
 };
 
 // Extracts the underlying type from an enum.
-template <typename T, bool is_enum = std::is_enum_v<T>>
-struct ArithmeticOrUnderlyingEnum;
-
 template <typename T>
-struct ArithmeticOrUnderlyingEnum<T, true> {
-  using type = typename std::underlying_type<T>::type;
-  static const bool value = std::is_arithmetic_v<type>;
-};
-
-template <typename T>
-struct ArithmeticOrUnderlyingEnum<T, false> {
-  using type = T;
-  static const bool value = std::is_arithmetic_v<type>;
-};
+using ArithmeticOrUnderlyingEnum =
+    typename std::conditional_t<std::is_enum_v<T>,
+                                std::underlying_type<T>,
+                                ArithmeticOrIntegralConstant<T>>::type;
 
 // The following are helper templates used in the CheckedNumeric class.
 template <typename T>
+  requires std::is_arithmetic_v<T>
 class CheckedNumeric;
 
 template <typename T>
+  requires std::is_arithmetic_v<T>
 class ClampedNumeric;
 
 template <typename T>
+  requires std::is_arithmetic_v<T>
 class StrictNumeric;
 
 // Used to treat CheckedNumeric and arithmetic underlying types the same.
 template <typename T>
-struct UnderlyingType {
-  using type = typename ArithmeticOrUnderlyingEnum<T>::type;
-  static const bool is_numeric = std::is_arithmetic_v<type>;
-  static const bool is_checked = false;
-  static const bool is_clamped = false;
-  static const bool is_strict = false;
-};
+inline constexpr bool kIsCheckedNumeric = false;
+template <typename T>
+inline constexpr bool kIsCheckedNumeric<CheckedNumeric<T>> = true;
+template <typename T>
+concept IsCheckedNumeric = kIsCheckedNumeric<T>;
 
 template <typename T>
-struct UnderlyingType<CheckedNumeric<T>> {
-  using type = T;
-  static const bool is_numeric = true;
-  static const bool is_checked = true;
-  static const bool is_clamped = false;
-  static const bool is_strict = false;
-};
+inline constexpr bool kIsClampedNumeric = false;
+template <typename T>
+inline constexpr bool kIsClampedNumeric<ClampedNumeric<T>> = true;
+template <typename T>
+concept IsClampedNumeric = kIsClampedNumeric<T>;
 
 template <typename T>
-struct UnderlyingType<ClampedNumeric<T>> {
-  using type = T;
-  static const bool is_numeric = true;
-  static const bool is_checked = false;
-  static const bool is_clamped = true;
-  static const bool is_strict = false;
-};
+inline constexpr bool kIsStrictNumeric = false;
+template <typename T>
+inline constexpr bool kIsStrictNumeric<StrictNumeric<T>> = true;
+template <typename T>
+concept IsStrictNumeric = kIsStrictNumeric<T>;
 
 template <typename T>
-struct UnderlyingType<StrictNumeric<T>> {
+struct UnderlyingTypeImpl {
+  using type = ArithmeticOrUnderlyingEnum<T>;
+};
+template <typename T>
+struct UnderlyingTypeImpl<CheckedNumeric<T>> {
   using type = T;
-  static const bool is_numeric = true;
-  static const bool is_checked = false;
-  static const bool is_clamped = false;
-  static const bool is_strict = true;
 };
+template <typename T>
+struct UnderlyingTypeImpl<ClampedNumeric<T>> {
+  using type = T;
+};
+template <typename T>
+struct UnderlyingTypeImpl<StrictNumeric<T>> {
+  using type = T;
+};
+template <typename T>
+using UnderlyingType = UnderlyingTypeImpl<T>::type;
+
+template <typename T>
+inline constexpr bool kIsNumeric = std::is_arithmetic_v<UnderlyingType<T>>;
+template <typename T>
+  requires(IsCheckedNumeric<T> || IsClampedNumeric<T> || IsStrictNumeric<T>)
+inline constexpr bool kIsNumeric<T> = true;
+template <typename T>
+concept IsNumeric = kIsNumeric<T>;
 
 template <typename L, typename R>
-struct IsCheckedOp {
-  static const bool value =
-      UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
-      (UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
-};
+concept IsCheckedOp = (IsCheckedNumeric<L> && IsNumeric<R>) ||
+                      (IsCheckedNumeric<R> && IsNumeric<L>);
 
 template <typename L, typename R>
-struct IsClampedOp {
-  static const bool value =
-      UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
-      (UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped) &&
-      !(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
-};
+concept IsClampedOp =
+    !IsCheckedOp<L, R> && ((IsClampedNumeric<L> && IsNumeric<R>) ||
+                           (IsClampedNumeric<R> && IsNumeric<L>));
 
 template <typename L, typename R>
-struct IsStrictOp {
-  static const bool value =
-      UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
-      (UnderlyingType<L>::is_strict || UnderlyingType<R>::is_strict) &&
-      !(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked) &&
-      !(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped);
-};
+concept IsStrictOp = !IsCheckedOp<L, R> && !IsClampedOp<L, R> &&
+                     ((IsStrictNumeric<L> && IsNumeric<R>) ||
+                      (IsStrictNumeric<R> && IsNumeric<L>));
 
 // as_signed<> returns the supplied integral value (or integral castable
 // Numeric template) cast as a signed integral of equivalent precision.
 // I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t)
-template <typename Src>
-constexpr typename std::make_signed<
-    typename gurl_base::internal::UnderlyingType<Src>::type>::type
-as_signed(const Src value) {
-  static_assert(std::is_integral_v<decltype(as_signed(value))>,
-                "Argument must be a signed or unsigned integer type.");
-  return static_cast<decltype(as_signed(value))>(value);
+template <typename Src, typename Dst = std::make_signed_t<UnderlyingType<Src>>>
+  requires std::integral<Dst>
+constexpr auto as_signed(Src value) {
+  return static_cast<Dst>(value);
 }
 
 // as_unsigned<> returns the supplied integral value (or integral castable
 // Numeric template) cast as an unsigned integral of equivalent precision.
 // I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t)
-template <typename Src>
-constexpr typename std::make_unsigned<
-    typename gurl_base::internal::UnderlyingType<Src>::type>::type
-as_unsigned(const Src value) {
-  static_assert(std::is_integral_v<decltype(as_unsigned(value))>,
-                "Argument must be a signed or unsigned integer type.");
-  return static_cast<decltype(as_unsigned(value))>(value);
+template <typename Src,
+          typename Dst = std::make_unsigned_t<UnderlyingType<Src>>>
+  requires std::integral<Dst>
+constexpr auto as_unsigned(Src value) {
+  return static_cast<Dst>(value);
 }
 
 template <typename L, typename R>
-constexpr bool IsLessImpl(const L lhs,
-                          const R rhs,
-                          const RangeCheck l_range,
-                          const RangeCheck r_range) {
-  return l_range.IsUnderflow() || r_range.IsOverflow() ||
-         (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) <
-                                    static_cast<decltype(lhs + rhs)>(rhs));
-}
-
-template <typename L, typename R>
+  requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
 struct IsLess {
-  static_assert(std::is_arithmetic_v<L> && std::is_arithmetic_v<R>,
-                "Types must be numeric.");
-  static constexpr bool Test(const L lhs, const R rhs) {
-    return IsLessImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
-                      DstRangeRelationToSrcRange<L>(rhs));
+  using SumT = decltype(std::declval<L>() + std::declval<R>());
+  static constexpr bool Test(L lhs, R rhs) {
+    const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs);
+    const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs);
+    return l_range.IsUnderflow() || r_range.IsOverflow() ||
+           (l_range == r_range &&
+            static_cast<SumT>(lhs) < static_cast<SumT>(rhs));
   }
 };
 
 template <typename L, typename R>
-constexpr bool IsLessOrEqualImpl(const L lhs,
-                                 const R rhs,
-                                 const RangeCheck l_range,
-                                 const RangeCheck r_range) {
-  return l_range.IsUnderflow() || r_range.IsOverflow() ||
-         (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) <=
-                                    static_cast<decltype(lhs + rhs)>(rhs));
-}
-
-template <typename L, typename R>
+  requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
 struct IsLessOrEqual {
-  static_assert(std::is_arithmetic_v<L> && std::is_arithmetic_v<R>,
-                "Types must be numeric.");
-  static constexpr bool Test(const L lhs, const R rhs) {
-    return IsLessOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
-                             DstRangeRelationToSrcRange<L>(rhs));
+  using SumT = decltype(std::declval<L>() + std::declval<R>());
+  static constexpr bool Test(L lhs, R rhs) {
+    const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs);
+    const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs);
+    return l_range.IsUnderflow() || r_range.IsOverflow() ||
+           (l_range == r_range &&
+            static_cast<SumT>(lhs) <= static_cast<SumT>(rhs));
   }
 };
 
 template <typename L, typename R>
-constexpr bool IsGreaterImpl(const L lhs,
-                             const R rhs,
-                             const RangeCheck l_range,
-                             const RangeCheck r_range) {
-  return l_range.IsOverflow() || r_range.IsUnderflow() ||
-         (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) >
-                                    static_cast<decltype(lhs + rhs)>(rhs));
-}
-
-template <typename L, typename R>
+  requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
 struct IsGreater {
-  static_assert(std::is_arithmetic_v<L> && std::is_arithmetic_v<R>,
-                "Types must be numeric.");
-  static constexpr bool Test(const L lhs, const R rhs) {
-    return IsGreaterImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
-                         DstRangeRelationToSrcRange<L>(rhs));
+  using SumT = decltype(std::declval<L>() + std::declval<R>());
+  static constexpr bool Test(L lhs, R rhs) {
+    const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs);
+    const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs);
+    return l_range.IsOverflow() || r_range.IsUnderflow() ||
+           (l_range == r_range &&
+            static_cast<SumT>(lhs) > static_cast<SumT>(rhs));
   }
 };
 
 template <typename L, typename R>
-constexpr bool IsGreaterOrEqualImpl(const L lhs,
-                                    const R rhs,
-                                    const RangeCheck l_range,
-                                    const RangeCheck r_range) {
-  return l_range.IsOverflow() || r_range.IsUnderflow() ||
-         (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) >=
-                                    static_cast<decltype(lhs + rhs)>(rhs));
-}
-
-template <typename L, typename R>
+  requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
 struct IsGreaterOrEqual {
-  static_assert(std::is_arithmetic_v<L> && std::is_arithmetic_v<R>,
-                "Types must be numeric.");
-  static constexpr bool Test(const L lhs, const R rhs) {
-    return IsGreaterOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
-                                DstRangeRelationToSrcRange<L>(rhs));
+  using SumT = decltype(std::declval<L>() + std::declval<R>());
+  static constexpr bool Test(L lhs, R rhs) {
+    const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs);
+    const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs);
+    return l_range.IsOverflow() || r_range.IsUnderflow() ||
+           (l_range == r_range &&
+            static_cast<SumT>(lhs) >= static_cast<SumT>(rhs));
   }
 };
 
 template <typename L, typename R>
+  requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
 struct IsEqual {
-  static_assert(std::is_arithmetic_v<L> && std::is_arithmetic_v<R>,
-                "Types must be numeric.");
-  static constexpr bool Test(const L lhs, const R rhs) {
+  using SumT = decltype(std::declval<L>() + std::declval<R>());
+  static constexpr bool Test(L lhs, R rhs) {
     return DstRangeRelationToSrcRange<R>(lhs) ==
                DstRangeRelationToSrcRange<L>(rhs) &&
-           static_cast<decltype(lhs + rhs)>(lhs) ==
-               static_cast<decltype(lhs + rhs)>(rhs);
+           static_cast<SumT>(lhs) == static_cast<SumT>(rhs);
   }
 };
 
 template <typename L, typename R>
+  requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
 struct IsNotEqual {
-  static_assert(std::is_arithmetic_v<L> && std::is_arithmetic_v<R>,
-                "Types must be numeric.");
-  static constexpr bool Test(const L lhs, const R rhs) {
+  using SumT = decltype(std::declval<L>() + std::declval<R>());
+  static constexpr bool Test(L lhs, R rhs) {
     return DstRangeRelationToSrcRange<R>(lhs) !=
                DstRangeRelationToSrcRange<L>(rhs) ||
-           static_cast<decltype(lhs + rhs)>(lhs) !=
-               static_cast<decltype(lhs + rhs)>(rhs);
+           static_cast<SumT>(lhs) != static_cast<SumT>(rhs);
   }
 };
 
 // These perform the actual math operations on the CheckedNumerics.
 // Binary arithmetic operations.
-template <template <typename, typename> class C, typename L, typename R>
-constexpr bool SafeCompare(const L lhs, const R rhs) {
-  static_assert(std::is_arithmetic_v<L> && std::is_arithmetic_v<R>,
-                "Types must be numeric.");
-  using Promotion = BigEnoughPromotion<L, R>;
-  using BigType = typename Promotion::type;
-  return Promotion::is_contained
+template <template <typename, typename> typename C, typename L, typename R>
+  requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
+constexpr bool SafeCompare(L lhs, R rhs) {
+  using BigType = BigEnoughPromotion<L, R>;
+  return kIsBigEnoughPromotionContained<L, R>
              // Force to a larger type for speed if both are contained.
-             ? C<BigType, BigType>::Test(
-                   static_cast<BigType>(static_cast<L>(lhs)),
-                   static_cast<BigType>(static_cast<R>(rhs)))
+             ? C<BigType, BigType>::Test(static_cast<BigType>(lhs),
+                                         static_cast<BigType>(rhs))
              // Let the template functions figure it out for mixed types.
              : C<L, R>::Test(lhs, rhs);
 }
 
 template <typename Dst, typename Src>
-constexpr bool IsMaxInRangeForNumericType() {
-  return IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
-                                          std::numeric_limits<Src>::max());
-}
+inline constexpr bool kIsMaxInRangeForNumericType =
+    IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
+                                     std::numeric_limits<Src>::max());
 
 template <typename Dst, typename Src>
-constexpr bool IsMinInRangeForNumericType() {
-  return IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
-                                       std::numeric_limits<Src>::lowest());
-}
+inline constexpr bool kIsMinInRangeForNumericType =
+    IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
+                                  std::numeric_limits<Src>::lowest());
 
 template <typename Dst, typename Src>
-constexpr Dst CommonMax() {
-  return !IsMaxInRangeForNumericType<Dst, Src>()
-             ? Dst(std::numeric_limits<Dst>::max())
-             : Dst(std::numeric_limits<Src>::max());
-}
+inline constexpr Dst kCommonMax =
+    kIsMaxInRangeForNumericType<Dst, Src>
+        ? static_cast<Dst>(std::numeric_limits<Src>::max())
+        : std::numeric_limits<Dst>::max();
 
 template <typename Dst, typename Src>
-constexpr Dst CommonMin() {
-  return !IsMinInRangeForNumericType<Dst, Src>()
-             ? Dst(std::numeric_limits<Dst>::lowest())
-             : Dst(std::numeric_limits<Src>::lowest());
-}
+inline constexpr Dst kCommonMin =
+    kIsMinInRangeForNumericType<Dst, Src>
+        ? static_cast<Dst>(std::numeric_limits<Src>::lowest())
+        : std::numeric_limits<Dst>::lowest();
 
 // This is a wrapper to generate return the max or min for a supplied type.
 // If the argument is false, the returned value is the maximum. If true the
 // returned value is the minimum.
 template <typename Dst, typename Src = Dst>
 constexpr Dst CommonMaxOrMin(bool is_min) {
-  return is_min ? CommonMin<Dst, Src>() : CommonMax<Dst, Src>();
+  return is_min ? kCommonMin<Dst, Src> : kCommonMax<Dst, Src>;
 }
 
-}  // namespace internal
-}  // namespace base
+}  // namespace gurl_base::internal
 
 #endif  // BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
diff --git a/base/numerics/safe_math.h b/base/numerics/safe_math.h
index 25d10f6..abe5eb7 100644
--- a/base/numerics/safe_math.h
+++ b/base/numerics/safe_math.h
@@ -5,8 +5,8 @@
 #ifndef BASE_NUMERICS_SAFE_MATH_H_
 #define BASE_NUMERICS_SAFE_MATH_H_
 
-#include "base/numerics/checked_math.h"
-#include "base/numerics/clamped_math.h"
-#include "base/numerics/safe_conversions.h"
+#include "base/numerics/checked_math.h"      // IWYU pragma: export
+#include "base/numerics/clamped_math.h"      // IWYU pragma: export
+#include "base/numerics/safe_conversions.h"  // IWYU pragma: export
 
 #endif  // BASE_NUMERICS_SAFE_MATH_H_
diff --git a/base/numerics/safe_math_arm_impl.h b/base/numerics/safe_math_arm_impl.h
index f419773..3bec0e4 100644
--- a/base/numerics/safe_math_arm_impl.h
+++ b/base/numerics/safe_math_arm_impl.h
@@ -5,18 +5,20 @@
 #ifndef BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
 #define BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
 
+// IWYU pragma: private
+
+#include <stdint.h>
+
 #include <cassert>
-#include <type_traits>
 
 #include "base/numerics/safe_conversions.h"
 
-namespace gurl_base {
-namespace internal {
+namespace gurl_base::internal {
 
 template <typename T, typename U>
 struct CheckedMulFastAsmOp {
-  static const bool is_supported =
-      kEnableAsmCode && FastIntegerArithmeticPromotion<T, U>::is_contained;
+  static constexpr bool is_supported =
+      kEnableAsmCode && kIsFastIntegerArithmeticPromotionContained<T, U>;
 
   // The following is not an assembler routine and is thus constexpr safe, it
   // just emits much more efficient code than the Clang and GCC builtins for
@@ -32,12 +34,13 @@
   //    cmp     r2, r1, asr #15
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
-    using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+    using Promotion = FastIntegerArithmeticPromotion<T, U>;
     Promotion presult;
 
     presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
-    if (!IsValueInRangeForNumericType<V>(presult))
+    if (!IsValueInRangeForNumericType<V>(presult)) {
       return false;
+    }
     *result = static_cast<V>(presult);
     return true;
   }
@@ -45,81 +48,78 @@
 
 template <typename T, typename U>
 struct ClampedAddFastAsmOp {
-  static const bool is_supported =
-      kEnableAsmCode && BigEnoughPromotion<T, U>::is_contained &&
-      IsTypeInRangeForNumericType<
-          int32_t,
-          typename BigEnoughPromotion<T, U>::type>::value;
+  static constexpr bool is_supported =
+      kEnableAsmCode && kIsBigEnoughPromotionContained<T, U> &&
+      kIsTypeInRangeForNumericType<int32_t, BigEnoughPromotion<T, U>>;
 
   template <typename V>
   __attribute__((always_inline)) static V Do(T x, U y) {
     // This will get promoted to an int, so let the compiler do whatever is
     // clever and rely on the saturated cast to bounds check.
-    if (IsIntegerArithmeticSafe<int, T, U>::value)
+    if constexpr (kIsIntegerArithmeticSafe<int, T, U>) {
       return saturated_cast<V>(static_cast<int>(x) + static_cast<int>(y));
+    } else {
+      int32_t result;
+      int32_t x_i32 = checked_cast<int32_t>(x);
+      int32_t y_i32 = checked_cast<int32_t>(y);
 
-    int32_t result;
-    int32_t x_i32 = checked_cast<int32_t>(x);
-    int32_t y_i32 = checked_cast<int32_t>(y);
-
-    asm("qadd %[result], %[first], %[second]"
-        : [result] "=r"(result)
-        : [first] "r"(x_i32), [second] "r"(y_i32));
-    return saturated_cast<V>(result);
+      asm("qadd %[result], %[first], %[second]"
+          : [result] "=r"(result)
+          : [first] "r"(x_i32), [second] "r"(y_i32));
+      return saturated_cast<V>(result);
+    }
   }
 };
 
 template <typename T, typename U>
 struct ClampedSubFastAsmOp {
-  static const bool is_supported =
-      kEnableAsmCode && BigEnoughPromotion<T, U>::is_contained &&
-      IsTypeInRangeForNumericType<
-          int32_t,
-          typename BigEnoughPromotion<T, U>::type>::value;
+  static constexpr bool is_supported =
+      kEnableAsmCode && kIsBigEnoughPromotionContained<T, U> &&
+      kIsTypeInRangeForNumericType<int32_t, BigEnoughPromotion<T, U>>;
 
   template <typename V>
   __attribute__((always_inline)) static V Do(T x, U y) {
     // This will get promoted to an int, so let the compiler do whatever is
     // clever and rely on the saturated cast to bounds check.
-    if (IsIntegerArithmeticSafe<int, T, U>::value)
+    if constexpr (kIsIntegerArithmeticSafe<int, T, U>) {
       return saturated_cast<V>(static_cast<int>(x) - static_cast<int>(y));
+    } else {
+      int32_t result;
+      int32_t x_i32 = checked_cast<int32_t>(x);
+      int32_t y_i32 = checked_cast<int32_t>(y);
 
-    int32_t result;
-    int32_t x_i32 = checked_cast<int32_t>(x);
-    int32_t y_i32 = checked_cast<int32_t>(y);
-
-    asm("qsub %[result], %[first], %[second]"
-        : [result] "=r"(result)
-        : [first] "r"(x_i32), [second] "r"(y_i32));
-    return saturated_cast<V>(result);
+      asm("qsub %[result], %[first], %[second]"
+          : [result] "=r"(result)
+          : [first] "r"(x_i32), [second] "r"(y_i32));
+      return saturated_cast<V>(result);
+    }
   }
 };
 
 template <typename T, typename U>
 struct ClampedMulFastAsmOp {
-  static const bool is_supported =
+  static constexpr bool is_supported =
       kEnableAsmCode && CheckedMulFastAsmOp<T, U>::is_supported;
 
   template <typename V>
   __attribute__((always_inline)) static V Do(T x, U y) {
     // Use the CheckedMulFastAsmOp for full-width 32-bit values, because
     // it's fewer instructions than promoting and then saturating.
-    if (!IsIntegerArithmeticSafe<int32_t, T, U>::value &&
-        !IsIntegerArithmeticSafe<uint32_t, T, U>::value) {
+    if constexpr (!kIsIntegerArithmeticSafe<int32_t, T, U> &&
+                  !kIsIntegerArithmeticSafe<uint32_t, T, U>) {
       V result;
       return CheckedMulFastAsmOp<T, U>::Do(x, y, &result)
                  ? result
                  : CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
+    } else {
+      static_assert(kIsFastIntegerArithmeticPromotionContained<T, U>);
+      using Promotion = FastIntegerArithmeticPromotion<T, U>;
+      return saturated_cast<V>(static_cast<Promotion>(x) *
+                               static_cast<Promotion>(y));
     }
-
-    assert((FastIntegerArithmeticPromotion<T, U>::is_contained));
-    using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
-    return saturated_cast<V>(static_cast<Promotion>(x) *
-                             static_cast<Promotion>(y));
   }
 };
 
-}  // namespace internal
-}  // namespace base
+}  // namespace gurl_base::internal
 
 #endif  // BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
diff --git a/base/numerics/safe_math_clang_gcc_impl.h b/base/numerics/safe_math_clang_gcc_impl.h
index e27bdc4..de7621f 100644
--- a/base/numerics/safe_math_clang_gcc_impl.h
+++ b/base/numerics/safe_math_clang_gcc_impl.h
@@ -5,14 +5,17 @@
 #ifndef BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
 #define BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
 
-#include <cassert>
+// IWYU pragma: private
+
+#include <stdint.h>
+
 #include <limits>
 #include <type_traits>
 
 #include "base/numerics/safe_conversions.h"
 
-#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
-#include "base/numerics/safe_math_arm_impl.h"
+#if defined(__ARMEL__) || defined(__arch64__)
+#include "base/numerics/safe_math_arm_impl.h"  // IWYU pragma: export
 #define BASE_HAS_ASSEMBLER_SAFE_MATH (1)
 #else
 #define BASE_HAS_ASSEMBLER_SAFE_MATH (0)
@@ -92,10 +95,10 @@
   // https://crbug.com/613003
   // We can support intptr_t, uintptr_t, or a smaller common type.
   static const bool is_supported =
-      (IsTypeInRangeForNumericType<intptr_t, T>::value &&
-       IsTypeInRangeForNumericType<intptr_t, U>::value) ||
-      (IsTypeInRangeForNumericType<uintptr_t, T>::value &&
-       IsTypeInRangeForNumericType<uintptr_t, U>::value);
+      (kIsTypeInRangeForNumericType<intptr_t, T> &&
+       kIsTypeInRangeForNumericType<intptr_t, U>) ||
+      (kIsTypeInRangeForNumericType<uintptr_t, T> &&
+       kIsTypeInRangeForNumericType<uintptr_t, U>);
 #else
   static const bool is_supported = true;
 #endif
diff --git a/base/numerics/safe_math_shared_impl.h b/base/numerics/safe_math_shared_impl.h
index f6955a1..8bda882 100644
--- a/base/numerics/safe_math_shared_impl.h
+++ b/base/numerics/safe_math_shared_impl.h
@@ -5,32 +5,19 @@
 #ifndef BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
 #define BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
 
-#include <stddef.h>
-#include <stdint.h>
+// IWYU pragma: private
 
-#include <cassert>
-#include <climits>
-#include <cmath>
-#include <cstdlib>
-#include <limits>
+#include <concepts>
 #include <type_traits>
 
 #include "base/numerics/safe_conversions.h"
-#include "build/build_config.h"
 
-#if BUILDFLAG(IS_ASMJS)
+#if defined(__asmjs__) || defined(__wasm__)
 // Optimized safe math instructions are incompatible with asmjs.
 #define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
-// Where available use builtin math overflow support on Clang and GCC.
-#elif !defined(__native_client__) &&                       \
-    ((defined(__clang__) &&                                \
-      ((__clang_major__ > 3) ||                            \
-       (__clang_major__ == 3 && __clang_minor__ >= 4))) || \
-     (defined(__GNUC__) && __GNUC__ >= 5))
-#include "base/numerics/safe_math_clang_gcc_impl.h"
-#define BASE_HAS_OPTIMIZED_SAFE_MATH (1)
 #else
-#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
+#include "base/numerics/safe_math_clang_gcc_impl.h"  // IWYU pragma: export
+#define BASE_HAS_OPTIMIZED_SAFE_MATH (1)
 #endif
 
 namespace gurl_base {
@@ -114,18 +101,18 @@
 // template instantiations even though we don't actually support the operations.
 // However, there is no corresponding implementation of e.g. SafeUnsignedAbs,
 // so the float versions will not compile.
-template <typename Numeric,
-          bool IsInteger = std::is_integral_v<Numeric>,
-          bool IsFloat = std::is_floating_point_v<Numeric>>
+template <typename Numeric>
 struct UnsignedOrFloatForSize;
 
 template <typename Numeric>
-struct UnsignedOrFloatForSize<Numeric, true, false> {
+  requires(std::integral<Numeric>)
+struct UnsignedOrFloatForSize<Numeric> {
   using type = typename std::make_unsigned<Numeric>::type;
 };
 
 template <typename Numeric>
-struct UnsignedOrFloatForSize<Numeric, false, true> {
+  requires(std::floating_point<Numeric>)
+struct UnsignedOrFloatForSize<Numeric> {
   using type = Numeric;
 };
 
@@ -134,40 +121,45 @@
 // exhibit well-defined overflow semantics and rely on the caller to detect
 // if an overflow occurred.
 
-template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
+template <typename T>
+  requires(std::integral<T>)
 constexpr T NegateWrapper(T value) {
   using UnsignedT = typename std::make_unsigned<T>::type;
   // This will compile to a NEG on Intel, and is normal negation on ARM.
   return static_cast<T>(UnsignedT(0) - static_cast<UnsignedT>(value));
 }
 
-template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
+template <typename T>
+  requires(std::floating_point<T>)
 constexpr T NegateWrapper(T value) {
   return -value;
 }
 
-template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
+template <typename T>
+  requires(std::integral<T>)
 constexpr typename std::make_unsigned<T>::type InvertWrapper(T value) {
   return ~value;
 }
 
-template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
+template <typename T>
+  requires(std::integral<T>)
 constexpr T AbsWrapper(T value) {
   return static_cast<T>(SafeUnsignedAbs(value));
 }
 
-template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
+template <typename T>
+  requires(std::floating_point<T>)
 constexpr T AbsWrapper(T value) {
   return value < 0 ? -value : value;
 }
 
-template <template <typename, typename, typename> class M,
+template <template <typename, typename> class M,
           typename L,
-          typename R>
+          typename R,
+          typename Math = M<UnderlyingType<L>, UnderlyingType<R>>>
+  requires requires { typename Math::result_type; }
 struct MathWrapper {
-  using math = M<typename UnderlyingType<L>::type,
-                 typename UnderlyingType<R>::type,
-                 void>;
+  using math = Math;
   using type = typename math::result_type;
 };
 
@@ -176,27 +168,26 @@
 // solution, but it beats rewriting these over and over again.
 #define BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME)       \
   template <typename L, typename R, typename... Args>                   \
-  constexpr auto CL_ABBR##OP_NAME(const L lhs, const R rhs,             \
-                                  const Args... args) {                 \
+  constexpr auto CL_ABBR##OP_NAME(L lhs, R rhs, Args... args) {         \
     return CL_ABBR##MathOp<CLASS##OP_NAME##Op, L, R, Args...>(lhs, rhs, \
                                                               args...); \
   }
 
 #define BASE_NUMERIC_ARITHMETIC_OPERATORS(CLASS, CL_ABBR, OP_NAME, OP, CMP_OP) \
   /* Binary arithmetic operator for all CLASS##Numeric operations. */          \
-  template <typename L, typename R,                                            \
-            std::enable_if_t<Is##CLASS##Op<L, R>::value>* = nullptr>           \
+  template <typename L, typename R>                                            \
+    requires(Is##CLASS##Op<L, R>)                                              \
   constexpr CLASS##Numeric<                                                    \
       typename MathWrapper<CLASS##OP_NAME##Op, L, R>::type>                    \
-  operator OP(const L lhs, const R rhs) {                                      \
+  operator OP(L lhs, R rhs) {                                                  \
     return decltype(lhs OP rhs)::template MathOp<CLASS##OP_NAME##Op>(lhs,      \
                                                                      rhs);     \
   }                                                                            \
   /* Assignment arithmetic operator implementation from CLASS##Numeric. */     \
   template <typename L>                                                        \
+    requires std::is_arithmetic_v<L>                                           \
   template <typename R>                                                        \
-  constexpr CLASS##Numeric<L>& CLASS##Numeric<L>::operator CMP_OP(             \
-      const R rhs) {                                                           \
+  constexpr CLASS##Numeric<L>& CLASS##Numeric<L>::operator CMP_OP(R rhs) {     \
     return MathOp<CLASS##OP_NAME##Op>(rhs);                                    \
   }                                                                            \
   /* Variadic arithmetic functions that return CLASS##Numeric. */              \
diff --git a/base/ranges/algorithm.h b/base/ranges/algorithm.h
deleted file mode 100644
index fe09c74..0000000
--- a/base/ranges/algorithm.h
+++ /dev/null
@@ -1,5018 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_RANGES_ALGORITHM_H_
-#define BASE_RANGES_ALGORITHM_H_
-
-#include <algorithm>
-#include <initializer_list>
-#include <iterator>
-#include <type_traits>
-#include <utility>
-
-#include "polyfills/base/check.h"
-#include "base/compiler_specific.h"
-#include "base/cxx20_is_constant_evaluated.h"
-#include "base/functional/identity.h"
-#include "base/functional/invoke.h"
-#include "polyfills/base/memory/raw_ptr_exclusion.h"
-#include "base/ranges/functional.h"
-#include "base/ranges/ranges.h"
-
-namespace gurl_base {
-
-namespace internal {
-
-// Returns a transformed version of the unary predicate `pred` applying `proj`
-// to its argument before invoking `pred` on it.
-// Ensures that the return type of `invoke(pred, ...)` is convertible to bool.
-template <typename Pred, typename Proj>
-constexpr auto ProjectedUnaryPredicate(Pred& pred, Proj& proj) noexcept {
-  return [&pred, &proj](auto&& arg) -> bool {
-    return gurl_base::invoke(pred,
-                        gurl_base::invoke(proj, std::forward<decltype(arg)>(arg)));
-  };
-}
-
-// Returns a transformed version of the binary predicate `pred` applying `proj1`
-// and `proj2` to its arguments before invoking `pred` on them.
-//
-// Provides an opt-in to considers all four permutations of projections and
-// argument types. This is sometimes necessary to allow usage with legacy
-// non-ranges std:: algorithms that don't support projections.
-//
-// These permutations are assigned different priorities to break ambiguities in
-// case several permutations are possible, e.g. when Proj1 and Proj2 are the
-// same type.
-//
-// Note that even when opting in to using all permutations of projections,
-// calling code should still ensure that the canonical mapping of {Proj1, Proj2}
-// to {LHS, RHS} compiles for all members of the range. This can be done by
-// adding the following constraint:
-//
-//   typename = indirect_result_t<Pred&,
-//                                projected<iterator_t<Range1>, Proj1>,
-//                                projected<iterator_t<Range2>, Proj2>>
-//
-// Ensures that the return type of `invoke(pred, ...)` is convertible to bool.
-template <typename Pred, typename Proj1, typename Proj2, bool kPermute = false>
-class BinaryPredicateProjector {
- public:
-  constexpr BinaryPredicateProjector(Pred& pred, Proj1& proj1, Proj2& proj2)
-      : pred_(pred), proj1_(proj1), proj2_(proj2) {}
-
- private:
-  template <typename ProjT, typename ProjU, typename T, typename U>
-  using InvokeResult = std::invoke_result_t<Pred&,
-                                            std::invoke_result_t<ProjT&, T&&>,
-                                            std::invoke_result_t<ProjU&, U&&>>;
-
-  template <typename T, typename U, typename = InvokeResult<Proj1, Proj2, T, U>>
-  constexpr std::pair<Proj1&, Proj2&> GetProjs(priority_tag<3>) const {
-    return {proj1_, proj2_};
-  }
-
-  template <typename T,
-            typename U,
-            bool LazyPermute = kPermute,
-            typename = std::enable_if_t<LazyPermute>,
-            typename = InvokeResult<Proj2, Proj1, T, U>>
-  constexpr std::pair<Proj2&, Proj1&> GetProjs(priority_tag<2>) const {
-    return {proj2_, proj1_};
-  }
-
-  template <typename T,
-            typename U,
-            bool LazyPermute = kPermute,
-            typename = std::enable_if_t<LazyPermute>,
-            typename = InvokeResult<Proj1, Proj1, T, U>>
-  constexpr std::pair<Proj1&, Proj1&> GetProjs(priority_tag<1>) const {
-    return {proj1_, proj1_};
-  }
-
-  template <typename T,
-            typename U,
-            bool LazyPermute = kPermute,
-            typename = std::enable_if_t<LazyPermute>,
-            typename = InvokeResult<Proj2, Proj2, T, U>>
-  constexpr std::pair<Proj2&, Proj2&> GetProjs(priority_tag<0>) const {
-    return {proj2_, proj2_};
-  }
-
- public:
-  template <typename T, typename U>
-  constexpr bool operator()(T&& lhs, U&& rhs) const {
-    auto projs = GetProjs<T, U>(priority_tag<3>());
-    return gurl_base::invoke(pred_, gurl_base::invoke(projs.first, std::forward<T>(lhs)),
-                        gurl_base::invoke(projs.second, std::forward<U>(rhs)));
-  }
-
- private:
-  // This field is not a raw_ref<> because it was filtered by the rewriter for:
-  // #constexpr-ctor-field-initializer
-  RAW_PTR_EXCLUSION Pred& pred_;
-  // This field is not a raw_ref<> because it was filtered by the rewriter for:
-  // #constexpr-ctor-field-initializer
-  RAW_PTR_EXCLUSION Proj1& proj1_;
-  // This field is not a raw_ref<> because it was filtered by the rewriter for:
-  // #constexpr-ctor-field-initializer
-  RAW_PTR_EXCLUSION Proj2& proj2_;
-};
-
-// Small wrappers around BinaryPredicateProjector to make the calling side more
-// readable.
-template <typename Pred, typename Proj1, typename Proj2>
-constexpr auto ProjectedBinaryPredicate(Pred& pred,
-                                        Proj1& proj1,
-                                        Proj2& proj2) noexcept {
-  return BinaryPredicateProjector<Pred, Proj1, Proj2>(pred, proj1, proj2);
-}
-
-template <typename Pred, typename Proj1, typename Proj2>
-constexpr auto PermutedProjectedBinaryPredicate(Pred& pred,
-                                                Proj1& proj1,
-                                                Proj2& proj2) noexcept {
-  return BinaryPredicateProjector<Pred, Proj1, Proj2, true>(pred, proj1, proj2);
-}
-
-// This alias is used below to restrict iterator based APIs to types for which
-// `iterator_category` and the pre-increment and post-increment operators are
-// defined. This is required in situations where otherwise an undesired overload
-// would be chosen, e.g. copy_if. In spirit this is similar to C++20's
-// std::input_or_output_iterator, a concept that each iterator should satisfy.
-template <typename Iter,
-          typename = decltype(++std::declval<Iter&>()),
-          typename = decltype(std::declval<Iter&>()++)>
-using iterator_category_t =
-    typename std::iterator_traits<Iter>::iterator_category;
-
-// This alias is used below to restrict range based APIs to types for which
-// `iterator_category_t` is defined for the underlying iterator. This is
-// required in situations where otherwise an undesired overload would be chosen,
-// e.g. transform. In spirit this is similar to C++20's std::ranges::range, a
-// concept that each range should satisfy.
-template <typename Range>
-using range_category_t = iterator_category_t<ranges::iterator_t<Range>>;
-
-}  // namespace internal
-
-namespace ranges {
-
-// C++14 implementation of std::ranges::in_fun_result.
-//
-// Reference: https://wg21.link/algorithms.results#:~:text=in_fun_result
-template <typename I, typename F>
-struct in_fun_result {
-  NO_UNIQUE_ADDRESS I in;
-  NO_UNIQUE_ADDRESS F fun;
-
-  template <typename I2,
-            typename F2,
-            std::enable_if_t<std::is_convertible<const I&, I2>{} &&
-                             std::is_convertible<const F&, F2>{}>>
-  constexpr operator in_fun_result<I2, F2>() const& {
-    return {in, fun};
-  }
-
-  template <typename I2,
-            typename F2,
-            std::enable_if_t<std::is_convertible<I, I2>{} &&
-                             std::is_convertible<F, F2>{}>>
-  constexpr operator in_fun_result<I2, F2>() && {
-    return {std::move(in), std::move(fun)};
-  }
-};
-
-// TODO(crbug.com/1071094): Implement the other result types.
-
-// [alg.nonmodifying] Non-modifying sequence operations
-// Reference: https://wg21.link/alg.nonmodifying
-
-// [alg.all.of] All of
-// Reference: https://wg21.link/alg.all.of
-
-// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
-//
-// Returns: `false` if `E(i)` is `false` for some iterator `i` in the range
-// `[first, last)`, and `true` otherwise.
-//
-// Complexity: At most `last - first` applications of the predicate and any
-// projection.
-//
-// Reference: https://wg21.link/alg.all.of#:~:text=ranges::all_of(I
-template <typename InputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr bool all_of(InputIterator first,
-                      InputIterator last,
-                      Pred pred,
-                      Proj proj = {}) {
-  for (; first != last; ++first) {
-    if (!gurl_base::invoke(pred, gurl_base::invoke(proj, *first)))
-      return false;
-  }
-
-  return true;
-}
-
-// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
-//
-// Returns: `false` if `E(i)` is `false` for some iterator `i` in `range`, and
-// `true` otherwise.
-//
-// Complexity: At most `size(range)` applications of the predicate and any
-// projection.
-//
-// Reference: https://wg21.link/alg.all.of#:~:text=ranges::all_of(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr bool all_of(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::all_of(ranges::begin(range), ranges::end(range),
-                        std::move(pred), std::move(proj));
-}
-
-// [alg.any.of] Any of
-// Reference: https://wg21.link/alg.any.of
-
-// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
-//
-// Returns: `true` if `E(i)` is `true` for some iterator `i` in the range
-// `[first, last)`, and `false` otherwise.
-//
-// Complexity: At most `last - first` applications of the predicate and any
-// projection.
-//
-// Reference: https://wg21.link/alg.any.of#:~:text=ranges::any_of(I
-template <typename InputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr bool any_of(InputIterator first,
-                      InputIterator last,
-                      Pred pred,
-                      Proj proj = {}) {
-  for (; first != last; ++first) {
-    if (gurl_base::invoke(pred, gurl_base::invoke(proj, *first)))
-      return true;
-  }
-
-  return false;
-}
-
-// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
-//
-// Returns: `true` if `E(i)` is `true` for some iterator `i` in `range`, and
-// `false` otherwise.
-//
-// Complexity: At most `size(range)` applications of the predicate and any
-// projection.
-//
-// Reference: https://wg21.link/alg.any.of#:~:text=ranges::any_of(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr bool any_of(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::any_of(ranges::begin(range), ranges::end(range),
-                        std::move(pred), std::move(proj));
-}
-
-// [alg.none.of] None of
-// Reference: https://wg21.link/alg.none.of
-
-// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
-//
-// Returns: `false` if `E(i)` is `true` for some iterator `i` in the range
-// `[first, last)`, and `true` otherwise.
-//
-// Complexity: At most `last - first` applications of the predicate and any
-// projection.
-//
-// Reference: https://wg21.link/alg.none.of#:~:text=ranges::none_of(I
-template <typename InputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr bool none_of(InputIterator first,
-                       InputIterator last,
-                       Pred pred,
-                       Proj proj = {}) {
-  for (; first != last; ++first) {
-    if (gurl_base::invoke(pred, gurl_base::invoke(proj, *first)))
-      return false;
-  }
-
-  return true;
-}
-
-// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
-//
-// Returns: `false` if `E(i)` is `true` for some iterator `i` in `range`, and
-// `true` otherwise.
-//
-// Complexity: At most `size(range)` applications of the predicate and any
-// projection.
-//
-// Reference: https://wg21.link/alg.none.of#:~:text=ranges::none_of(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr bool none_of(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::none_of(ranges::begin(range), ranges::end(range),
-                         std::move(pred), std::move(proj));
-}
-
-// [alg.foreach] For each
-// Reference: https://wg21.link/alg.foreach
-
-// Reference: https://wg21.link/algorithm.syn#:~:text=for_each_result
-template <typename I, typename F>
-using for_each_result = in_fun_result<I, F>;
-
-// Effects: Calls `invoke(f, invoke(proj, *i))` for every iterator `i` in the
-// range `[first, last)`, starting from `first` and proceeding to `last - 1`.
-//
-// Returns: `{last, std::move(f)}`.
-//
-// Complexity: Applies `f` and `proj` exactly `last - first` times.
-//
-// Remarks: If `f` returns a result, the result is ignored.
-//
-// Reference: https://wg21.link/alg.foreach#:~:text=ranges::for_each(I
-template <typename InputIterator,
-          typename Fun,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr auto for_each(InputIterator first,
-                        InputIterator last,
-                        Fun f,
-                        Proj proj = {}) {
-  for (; first != last; ++first)
-    gurl_base::invoke(f, gurl_base::invoke(proj, *first));
-  return for_each_result<InputIterator, Fun>{first, std::move(f)};
-}
-
-// Effects: Calls `invoke(f, invoke(proj, *i))` for every iterator `i` in the
-// range `range`, starting from `begin(range)` and proceeding to `end(range) -
-// 1`.
-//
-// Returns: `{last, std::move(f)}`.
-//
-// Complexity: Applies `f` and `proj` exactly `size(range)` times.
-//
-// Remarks: If `f` returns a result, the result is ignored.
-//
-// Reference: https://wg21.link/alg.foreach#:~:text=ranges::for_each(R
-template <typename Range,
-          typename Fun,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto for_each(Range&& range, Fun f, Proj proj = {}) {
-  return ranges::for_each(ranges::begin(range), ranges::end(range),
-                          std::move(f), std::move(proj));
-}
-
-// Reference: https://wg21.link/algorithm.syn#:~:text=for_each_n_result
-template <typename I, typename F>
-using for_each_n_result = in_fun_result<I, F>;
-
-// Preconditions: `n >= 0` is `true`.
-//
-// Effects: Calls `invoke(f, invoke(proj, *i))` for every iterator `i` in the
-// range `[first, first + n)` in order.
-//
-// Returns: `{first + n, std::move(f)}`.
-//
-// Remarks: If `f` returns a result, the result is ignored.
-//
-// Reference: https://wg21.link/alg.foreach#:~:text=ranges::for_each_n
-template <typename InputIterator,
-          typename Size,
-          typename Fun,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr auto for_each_n(InputIterator first, Size n, Fun f, Proj proj = {}) {
-  while (n > 0) {
-    gurl_base::invoke(f, gurl_base::invoke(proj, *first));
-    ++first;
-    --n;
-  }
-
-  return for_each_n_result<InputIterator, Fun>{first, std::move(f)};
-}
-
-// [alg.find] Find
-// Reference: https://wg21.link/alg.find
-
-// Let `E(i)` be `bool(invoke(proj, *i) == value)`.
-//
-// Returns: The first iterator `i` in the range `[first, last)` for which `E(i)`
-// is `true`. Returns `last` if no such iterator is found.
-//
-// Complexity: At most `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.find#:~:text=ranges::find(I
-template <typename InputIterator,
-          typename T,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr auto find(InputIterator first,
-                    InputIterator last,
-                    const T& value,
-                    Proj proj = {}) {
-  for (; first != last; ++first) {
-    if (gurl_base::invoke(proj, *first) == value)
-      break;
-  }
-
-  return first;
-}
-
-// Let `E(i)` be `bool(invoke(proj, *i) == value)`.
-//
-// Returns: The first iterator `i` in `range` for which `E(i)` is `true`.
-// Returns `end(range)` if no such iterator is found.
-//
-// Complexity: At most `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.find#:~:text=ranges::find(R
-template <typename Range,
-          typename T,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto find(Range&& range, const T& value, Proj proj = {}) {
-  return ranges::find(ranges::begin(range), ranges::end(range), value,
-                      std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Returns: The first iterator `i` in the range `[first, last)` for which `E(i)`
-// is `true`. Returns `last` if no such iterator is found.
-//
-// Complexity: At most `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.find#:~:text=ranges::find_if(I
-template <typename InputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr auto find_if(InputIterator first,
-                       InputIterator last,
-                       Pred pred,
-                       Proj proj = {}) {
-  return std::find_if(first, last,
-                      internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Returns: The first iterator `i` in `range` for which `E(i)` is `true`.
-// Returns `end(range)` if no such iterator is found.
-//
-// Complexity: At most `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.find#:~:text=ranges::find_if(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto find_if(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::find_if(ranges::begin(range), ranges::end(range),
-                         std::move(pred), std::move(proj));
-}
-
-// Let `E(i)` be `bool(!invoke(pred, invoke(proj, *i)))`.
-//
-// Returns: The first iterator `i` in the range `[first, last)` for which `E(i)`
-// is `true`. Returns `last` if no such iterator is found.
-//
-// Complexity: At most `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.find#:~:text=ranges::find_if_not(I
-template <typename InputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr auto find_if_not(InputIterator first,
-                           InputIterator last,
-                           Pred pred,
-                           Proj proj = {}) {
-  return std::find_if_not(first, last,
-                          internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(i)` be `bool(!invoke(pred, invoke(proj, *i)))`.
-//
-// Returns: The first iterator `i` in `range` for which `E(i)` is `true`.
-// Returns `end(range)` if no such iterator is found.
-//
-// Complexity: At most `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.find#:~:text=ranges::find_if_not(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto find_if_not(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::find_if_not(ranges::begin(range), ranges::end(range),
-                             std::move(pred), std::move(proj));
-}
-
-// [alg.find.end] Find end
-// Reference: https://wg21.link/alg.find.end
-
-// Let:
-// - `E(i,n)` be `invoke(pred, invoke(proj1, *(i + n)),
-//                             invoke(proj2, *(first2 + n)))`
-//
-// - `i` be `last1` if `[first2, last2)` is empty, or if
-//   `(last2 - first2) > (last1 - first1)` is `true`, or if there is no iterator
-//   in the range `[first1, last1 - (last2 - first2))` such that for every
-//   non-negative integer `n < (last2 - first2)`, `E(i,n)` is `true`. Otherwise
-//   `i` is the last such iterator in `[first1, last1 - (last2 - first2))`.
-//
-// Returns: `i`
-// Note: std::ranges::find_end(I1 first1,...) returns a range, rather than an
-// iterator. For simplicitly we match std::find_end's return type instead.
-//
-// Complexity:
-// At most `(last2 - first2) * (last1 - first1 - (last2 - first2) + 1)`
-// applications of the corresponding predicate and any projections.
-//
-// Reference: https://wg21.link/alg.find.end#:~:text=ranges::find_end(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<ForwardIterator1, Proj1>,
-                                       projected<ForwardIterator2, Proj2>>>
-constexpr auto find_end(ForwardIterator1 first1,
-                        ForwardIterator1 last1,
-                        ForwardIterator2 first2,
-                        ForwardIterator2 last2,
-                        Pred pred = {},
-                        Proj1 proj1 = {},
-                        Proj2 proj2 = {}) {
-  return std::find_end(first1, last1, first2, last2,
-                       internal::ProjectedBinaryPredicate(pred, proj1, proj2));
-}
-
-// Let:
-// - `E(i,n)` be `invoke(pred, invoke(proj1, *(i + n)),
-//                             invoke(proj2, *(first2 + n)))`
-//
-// - `i` be `end(range1)` if `range2` is empty, or if
-//   `size(range2) > size(range1)` is `true`, or if there is no iterator in the
-//   range `[begin(range1), end(range1) - size(range2))` such that for every
-//   non-negative integer `n < size(range2)`, `E(i,n)` is `true`. Otherwise `i`
-//   is the last such iterator in `[begin(range1), end(range1) - size(range2))`.
-//
-// Returns: `i`
-// Note: std::ranges::find_end(R1&& r1,...) returns a range, rather than an
-// iterator. For simplicitly we match std::find_end's return type instead.
-//
-// Complexity: At most `size(range2) * (size(range1) - size(range2) + 1)`
-// applications of the corresponding predicate and any projections.
-//
-// Reference: https://wg21.link/alg.find.end#:~:text=ranges::find_end(R1
-template <typename Range1,
-          typename Range2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>>
-constexpr auto find_end(Range1&& range1,
-                        Range2&& range2,
-                        Pred pred = {},
-                        Proj1 proj1 = {},
-                        Proj2 proj2 = {}) {
-  return ranges::find_end(ranges::begin(range1), ranges::end(range1),
-                          ranges::begin(range2), ranges::end(range2),
-                          std::move(pred), std::move(proj1), std::move(proj2));
-}
-
-// [alg.find.first.of] Find first
-// Reference: https://wg21.link/alg.find.first.of
-
-// Let `E(i,j)` be `bool(invoke(pred, invoke(proj1, *i), invoke(proj2, *j)))`.
-//
-// Effects: Finds an element that matches one of a set of values.
-//
-// Returns: The first iterator `i` in the range `[first1, last1)` such that for
-// some iterator `j` in the range `[first2, last2)` `E(i,j)` holds. Returns
-// `last1` if `[first2, last2)` is empty or if no such iterator is found.
-//
-// Complexity: At most `(last1 - first1) * (last2 - first2)` applications of the
-// corresponding predicate and any projections.
-//
-// Reference:
-// https://wg21.link/alg.find.first.of#:~:text=ranges::find_first_of(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<ForwardIterator1, Proj1>,
-                                       projected<ForwardIterator2, Proj2>>>
-constexpr auto find_first_of(ForwardIterator1 first1,
-                             ForwardIterator1 last1,
-                             ForwardIterator2 first2,
-                             ForwardIterator2 last2,
-                             Pred pred = {},
-                             Proj1 proj1 = {},
-                             Proj2 proj2 = {}) {
-  return std::find_first_of(
-      first1, last1, first2, last2,
-      internal::ProjectedBinaryPredicate(pred, proj1, proj2));
-}
-
-// Let `E(i,j)` be `bool(invoke(pred, invoke(proj1, *i), invoke(proj2, *j)))`.
-//
-// Effects: Finds an element that matches one of a set of values.
-//
-// Returns: The first iterator `i` in `range1` such that for some iterator `j`
-// in `range2` `E(i,j)` holds. Returns `end(range1)` if `range2` is empty or if
-// no such iterator is found.
-//
-// Complexity: At most `size(range1) * size(range2)` applications of the
-// corresponding predicate and any projections.
-//
-// Reference:
-// https://wg21.link/alg.find.first.of#:~:text=ranges::find_first_of(R1
-template <typename Range1,
-          typename Range2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>>
-constexpr auto find_first_of(Range1&& range1,
-                             Range2&& range2,
-                             Pred pred = {},
-                             Proj1 proj1 = {},
-                             Proj2 proj2 = {}) {
-  return ranges::find_first_of(
-      ranges::begin(range1), ranges::end(range1), ranges::begin(range2),
-      ranges::end(range2), std::move(pred), std::move(proj1), std::move(proj2));
-}
-
-// [alg.adjacent.find] Adjacent find
-// Reference: https://wg21.link/alg.adjacent.find
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i), invoke(proj, *(i + 1))))`.
-//
-// Returns: The first iterator `i` such that both `i` and `i + 1` are in the
-// range `[first, last)` for which `E(i)` holds. Returns `last` if no such
-// iterator is found.
-//
-// Complexity: Exactly `min((i - first) + 1, (last - first) - 1)` applications
-// of the corresponding predicate, where `i` is `adjacent_find`'s return value.
-//
-// Reference:
-// https://wg21.link/alg.adjacent.find#:~:text=ranges::adjacent_find(I
-template <typename ForwardIterator,
-          typename Pred = ranges::equal_to,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto adjacent_find(ForwardIterator first,
-                             ForwardIterator last,
-                             Pred pred = {},
-                             Proj proj = {}) {
-  // Implementation inspired by cppreference.com:
-  // https://en.cppreference.com/w/cpp/algorithm/adjacent_find
-  //
-  // A reimplementation is required, because std::adjacent_find is not constexpr
-  // prior to C++20. Once we have C++20, we should switch to standard library
-  // implementation.
-  if (first == last)
-    return last;
-
-  for (ForwardIterator next = first; ++next != last; ++first) {
-    if (gurl_base::invoke(pred, gurl_base::invoke(proj, *first),
-                     gurl_base::invoke(proj, *next))) {
-      return first;
-    }
-  }
-
-  return last;
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i), invoke(proj, *(i + 1))))`.
-//
-// Returns: The first iterator `i` such that both `i` and `i + 1` are in the
-// range `range` for which `E(i)` holds. Returns `end(range)` if no such
-// iterator is found.
-//
-// Complexity: Exactly `min((i - begin(range)) + 1, size(range) - 1)`
-// applications of the corresponding predicate, where `i` is `adjacent_find`'s
-// return value.
-//
-// Reference:
-// https://wg21.link/alg.adjacent.find#:~:text=ranges::adjacent_find(R
-template <typename Range,
-          typename Pred = ranges::equal_to,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto adjacent_find(Range&& range, Pred pred = {}, Proj proj = {}) {
-  return ranges::adjacent_find(ranges::begin(range), ranges::end(range),
-                               std::move(pred), std::move(proj));
-}
-
-// [alg.count] Count
-// Reference: https://wg21.link/alg.count
-
-// Let `E(i)` be `invoke(proj, *i) == value`.
-//
-// Effects: Returns the number of iterators `i` in the range `[first, last)` for
-// which `E(i)` holds.
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.count#:~:text=ranges::count(I
-template <typename InputIterator,
-          typename T,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr auto count(InputIterator first,
-                     InputIterator last,
-                     const T& value,
-                     Proj proj = {}) {
-  // Note: In order to be able to apply `proj` to each element in [first, last)
-  // we are dispatching to std::count_if instead of std::count.
-  return std::count_if(first, last, [&proj, &value](auto&& lhs) {
-    return gurl_base::invoke(proj, std::forward<decltype(lhs)>(lhs)) == value;
-  });
-}
-
-// Let `E(i)` be `invoke(proj, *i) == value`.
-//
-// Effects: Returns the number of iterators `i` in `range` for which `E(i)`
-// holds.
-//
-// Complexity: Exactly `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.count#:~:text=ranges::count(R
-template <typename Range,
-          typename T,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto count(Range&& range, const T& value, Proj proj = {}) {
-  return ranges::count(ranges::begin(range), ranges::end(range), value,
-                       std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Effects: Returns the number of iterators `i` in the range `[first, last)` for
-// which `E(i)` holds.
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.count#:~:text=ranges::count_if(I
-template <typename InputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>>
-constexpr auto count_if(InputIterator first,
-                        InputIterator last,
-                        Pred pred,
-                        Proj proj = {}) {
-  return std::count_if(first, last,
-                       internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Effects: Returns the number of iterators `i` in `range` for which `E(i)`
-// holds.
-//
-// Complexity: Exactly `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.count#:~:text=ranges::count_if(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto count_if(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::count_if(ranges::begin(range), ranges::end(range),
-                          std::move(pred), std::move(proj));
-}
-
-// [mismatch] Mismatch
-// Reference: https://wg21.link/mismatch
-
-// Let `E(n)` be `!invoke(pred, invoke(proj1, *(first1 + n)),
-//                              invoke(proj2, *(first2 + n)))`.
-//
-// Let `N` be `min(last1 - first1, last2 - first2)`.
-//
-// Returns: `{ first1 + n, first2 + n }`, where `n` is the smallest integer in
-// `[0, N)` such that `E(n)` holds, or `N` if no such integer exists.
-//
-// Complexity: At most `N` applications of the corresponding predicate and any
-// projections.
-//
-// Reference: https://wg21.link/mismatch#:~:text=ranges::mismatch(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<ForwardIterator1, Proj1>,
-                                       projected<ForwardIterator2, Proj2>>>
-constexpr auto mismatch(ForwardIterator1 first1,
-                        ForwardIterator1 last1,
-                        ForwardIterator2 first2,
-                        ForwardIterator2 last2,
-                        Pred pred = {},
-                        Proj1 proj1 = {},
-                        Proj2 proj2 = {}) {
-  return std::mismatch(first1, last1, first2, last2,
-                       internal::ProjectedBinaryPredicate(pred, proj1, proj2));
-}
-
-// Let `E(n)` be `!invoke(pred, invoke(proj1, *(begin(range1) + n)),
-//                              invoke(proj2, *(begin(range2) + n)))`.
-//
-// Let `N` be `min(size(range1), size(range2))`.
-//
-// Returns: `{ begin(range1) + n, begin(range2) + n }`, where `n` is the
-// smallest integer in `[0, N)` such that `E(n)` holds, or `N` if no such
-// integer exists.
-//
-// Complexity: At most `N` applications of the corresponding predicate and any
-// projections.
-//
-// Reference: https://wg21.link/mismatch#:~:text=ranges::mismatch(R1
-template <typename Range1,
-          typename Range2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>>
-constexpr auto mismatch(Range1&& range1,
-                        Range2&& range2,
-                        Pred pred = {},
-                        Proj1 proj1 = {},
-                        Proj2 proj2 = {}) {
-  return ranges::mismatch(ranges::begin(range1), ranges::end(range1),
-                          ranges::begin(range2), ranges::end(range2),
-                          std::move(pred), std::move(proj1), std::move(proj2));
-}
-
-// [alg.equal] Equal
-// Reference: https://wg21.link/alg.equal
-
-// Let `E(i)` be
-//   `invoke(pred, invoke(proj1, *i), invoke(proj2, *(first2 + (i - first1))))`.
-//
-// Returns: If `last1 - first1 != last2 - first2`, return `false.` Otherwise
-// return `true` if `E(i)` holds for every iterator `i` in the range `[first1,
-// last1)`. Otherwise, returns `false`.
-//
-// Complexity: If the types of `first1`, `last1`, `first2`, and `last2` meet the
-// `RandomAccessIterator` requirements and `last1 - first1 != last2 - first2`,
-// then no applications of the corresponding predicate and each projection;
-// otherwise, at most `min(last1 - first1, last2 - first2)` applications of the
-// corresponding predicate and any projections.
-//
-// Reference: https://wg21.link/alg.equal#:~:text=ranges::equal(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<ForwardIterator1, Proj1>,
-                                       projected<ForwardIterator2, Proj2>>>
-constexpr bool equal(ForwardIterator1 first1,
-                     ForwardIterator1 last1,
-                     ForwardIterator2 first2,
-                     ForwardIterator2 last2,
-                     Pred pred = {},
-                     Proj1 proj1 = {},
-                     Proj2 proj2 = {}) {
-  if (gurl_base::is_constant_evaluated()) {
-    for (; first1 != last1 && first2 != last2; ++first1, ++first2) {
-      if (!gurl_base::invoke(pred, gurl_base::invoke(proj1, *first1),
-                        gurl_base::invoke(proj2, *first2))) {
-        return false;
-      }
-    }
-
-    return first1 == last1 && first2 == last2;
-  }
-
-  return std::equal(first1, last1, first2, last2,
-                    internal::ProjectedBinaryPredicate(pred, proj1, proj2));
-}
-
-// Let `E(i)` be
-//   `invoke(pred, invoke(proj1, *i),
-//                 invoke(proj2, *(begin(range2) + (i - begin(range1)))))`.
-//
-// Returns: If `size(range1) != size(range2)`, return `false.` Otherwise return
-// `true` if `E(i)` holds for every iterator `i` in `range1`. Otherwise, returns
-// `false`.
-//
-// Complexity: If the types of `begin(range1)`, `end(range1)`, `begin(range2)`,
-// and `end(range2)` meet the `RandomAccessIterator` requirements and
-// `size(range1) != size(range2)`, then no applications of the corresponding
-// predicate and each projection;
-// otherwise, at most `min(size(range1), size(range2))` applications of the
-// corresponding predicate and any projections.
-//
-// Reference: https://wg21.link/alg.equal#:~:text=ranges::equal(R1
-template <typename Range1,
-          typename Range2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>>
-constexpr bool equal(Range1&& range1,
-                     Range2&& range2,
-                     Pred pred = {},
-                     Proj1 proj1 = {},
-                     Proj2 proj2 = {}) {
-  return ranges::equal(ranges::begin(range1), ranges::end(range1),
-                       ranges::begin(range2), ranges::end(range2),
-                       std::move(pred), std::move(proj1), std::move(proj2));
-}
-
-// [alg.is.permutation] Is permutation
-// Reference: https://wg21.link/alg.is.permutation
-
-// Returns: If `last1 - first1 != last2 - first2`, return `false`. Otherwise
-// return `true` if there exists a permutation of the elements in the range
-// `[first2, last2)`, bounded by `[pfirst, plast)`, such that
-// `ranges::equal(first1, last1, pfirst, plast, pred, proj, proj)` returns
-// `true`; otherwise, returns `false`.
-//
-// Complexity: No applications of the corresponding predicate if
-// ForwardIterator1 and ForwardIterator2 meet the requirements of random access
-// iterators and `last1 - first1 != last2 - first2`. Otherwise, exactly
-// `last1 - first1` applications of the corresponding predicate and projections
-// if `ranges::equal(first1, last1, first2, last2, pred, proj, proj)` would
-// return true;
-// otherwise, at worst `O(N^2)`, where `N` has the value `last1 - first1`.
-//
-// Reference:
-// https://wg21.link/alg.is.permutation#:~:text=ranges::is_permutation(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<ForwardIterator1, Proj1>,
-                                       projected<ForwardIterator2, Proj2>>>
-constexpr bool is_permutation(ForwardIterator1 first1,
-                              ForwardIterator1 last1,
-                              ForwardIterator2 first2,
-                              ForwardIterator2 last2,
-                              Pred pred = {},
-                              Proj1 proj1 = {},
-                              Proj2 proj2 = {}) {
-  // Needs to opt-in to all permutations, since std::is_permutation expects
-  // pred(proj1(lhs), proj1(rhs)) to compile.
-  return std::is_permutation(
-      first1, last1, first2, last2,
-      internal::PermutedProjectedBinaryPredicate(pred, proj1, proj2));
-}
-
-// Returns: If `size(range1) != size(range2)`, return `false`. Otherwise return
-// `true` if there exists a permutation of the elements in `range2`, bounded by
-// `[pbegin, pend)`, such that
-// `ranges::equal(range1, [pbegin, pend), pred, proj, proj)` returns `true`;
-// otherwise, returns `false`.
-//
-// Complexity: No applications of the corresponding predicate if Range1 and
-// Range2 meet the requirements of random access ranges and
-// `size(range1) != size(range2)`. Otherwise, exactly `size(range1)`
-// applications of the corresponding predicate and projections if
-// `ranges::equal(range1, range2, pred, proj, proj)` would return true;
-// otherwise, at worst `O(N^2)`, where `N` has the value `size(range1)`.
-//
-// Reference:
-// https://wg21.link/alg.is.permutation#:~:text=ranges::is_permutation(R1
-template <typename Range1,
-          typename Range2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>>
-constexpr bool is_permutation(Range1&& range1,
-                              Range2&& range2,
-                              Pred pred = {},
-                              Proj1 proj1 = {},
-                              Proj2 proj2 = {}) {
-  return ranges::is_permutation(
-      ranges::begin(range1), ranges::end(range1), ranges::begin(range2),
-      ranges::end(range2), std::move(pred), std::move(proj1), std::move(proj2));
-}
-
-// [alg.search] Search
-// Reference: https://wg21.link/alg.search
-
-// Returns: `i`, where `i` is the first iterator in the range
-// `[first1, last1 - (last2 - first2))` such that for every non-negative integer
-// `n` less than `last2 - first2` the condition
-// `bool(invoke(pred, invoke(proj1, *(i + n)), invoke(proj2, *(first2 + n))))`
-// is `true`.
-// Returns `last1` if no such iterator exists.
-// Note: std::ranges::search(I1 first1,...) returns a range, rather than an
-// iterator. For simplicitly we match std::search's return type instead.
-//
-// Complexity: At most `(last1 - first1) * (last2 - first2)` applications of the
-// corresponding predicate and projections.
-//
-// Reference: https://wg21.link/alg.search#:~:text=ranges::search(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<ForwardIterator1, Proj1>,
-                                       projected<ForwardIterator2, Proj2>>>
-constexpr auto search(ForwardIterator1 first1,
-                      ForwardIterator1 last1,
-                      ForwardIterator2 first2,
-                      ForwardIterator2 last2,
-                      Pred pred = {},
-                      Proj1 proj1 = {},
-                      Proj2 proj2 = {}) {
-  return std::search(first1, last1, first2, last2,
-                     internal::ProjectedBinaryPredicate(pred, proj1, proj2));
-}
-
-// Returns: `i`, where `i` is the first iterator in the range
-// `[begin(range1), end(range1) - size(range2))` such that for every
-// non-negative integer `n` less than `size(range2)` the condition
-// `bool(invoke(pred, invoke(proj1, *(i + n)),
-//                    invoke(proj2, *(begin(range2) + n))))` is `true`.
-// Returns `end(range1)` if no such iterator exists.
-// Note: std::ranges::search(R1&& r1,...) returns a range, rather than an
-// iterator. For simplicitly we match std::search's return type instead.
-//
-// Complexity: At most `size(range1) * size(range2)` applications of the
-// corresponding predicate and projections.
-//
-// Reference: https://wg21.link/alg.search#:~:text=ranges::search(R1
-template <typename Range1,
-          typename Range2,
-          typename Pred = ranges::equal_to,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Pred&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>>
-constexpr auto search(Range1&& range1,
-                      Range2&& range2,
-                      Pred pred = {},
-                      Proj1 proj1 = {},
-                      Proj2 proj2 = {}) {
-  return ranges::search(ranges::begin(range1), ranges::end(range1),
-                        ranges::begin(range2), ranges::end(range2),
-                        std::move(pred), std::move(proj1), std::move(proj2));
-}
-
-// Mandates: The type `Size` is convertible to an integral type.
-//
-// Returns: `i` where `i` is the first iterator in the range
-// `[first, last - count)` such that for every non-negative integer `n` less
-// than `count`, the following condition holds:
-// `invoke(pred, invoke(proj, *(i + n)), value)`.
-// Returns `last` if no such iterator is found.
-// Note: std::ranges::search_n(I1 first1,...) returns a range, rather than an
-// iterator. For simplicitly we match std::search_n's return type instead.
-//
-// Complexity: At most `last - first` applications of the corresponding
-// predicate and projection.
-//
-// Reference: https://wg21.link/alg.search#:~:text=ranges::search_n(I
-template <typename ForwardIterator,
-          typename Size,
-          typename T,
-          typename Pred = ranges::equal_to,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto search_n(ForwardIterator first,
-                        ForwardIterator last,
-                        Size count,
-                        const T& value,
-                        Pred pred = {},
-                        Proj proj = {}) {
-  // The second arg is guaranteed to be `value`, so we'll simply apply the
-  // identity projection.
-  identity value_proj;
-  return std::search_n(
-      first, last, count, value,
-      internal::ProjectedBinaryPredicate(pred, proj, value_proj));
-}
-
-// Mandates: The type `Size` is convertible to an integral type.
-//
-// Returns: `i` where `i` is the first iterator in the range
-// `[begin(range), end(range) - count)` such that for every non-negative integer
-// `n` less than `count`, the following condition holds:
-// `invoke(pred, invoke(proj, *(i + n)), value)`.
-// Returns `end(arnge)` if no such iterator is found.
-// Note: std::ranges::search_n(R1&& r1,...) returns a range, rather than an
-// iterator. For simplicitly we match std::search_n's return type instead.
-//
-// Complexity: At most `size(range)` applications of the corresponding predicate
-// and projection.
-//
-// Reference: https://wg21.link/alg.search#:~:text=ranges::search_n(R
-template <typename Range,
-          typename Size,
-          typename T,
-          typename Pred = ranges::equal_to,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto search_n(Range&& range,
-                        Size count,
-                        const T& value,
-                        Pred pred = {},
-                        Proj proj = {}) {
-  return ranges::search_n(ranges::begin(range), ranges::end(range), count,
-                          value, std::move(pred), std::move(proj));
-}
-
-// [alg.modifying.operations] Mutating sequence operations
-// Reference: https://wg21.link/alg.modifying.operations
-
-// [alg.copy] Copy
-// Reference: https://wg21.link/alg.copy
-
-// Let N be `last - first`.
-//
-// Preconditions: `result` is not in the range `[first, last)`.
-//
-// Effects: Copies elements in the range `[first, last)` into the range
-// `[result, result + N)` starting from `first` and proceeding to `last`. For
-// each non-negative integer `n < N` , performs `*(result + n) = *(first + n)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.copy#:~:text=ranges::copy(I
-template <typename InputIterator,
-          typename OutputIterator,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto copy(InputIterator first,
-                    InputIterator last,
-                    OutputIterator result) {
-  return std::copy(first, last, result);
-}
-
-// Let N be `size(range)`.
-//
-// Preconditions: `result` is not in `range`.
-//
-// Effects: Copies elements in `range` into the range `[result, result + N)`
-// starting from `begin(range)` and proceeding to `end(range)`. For each
-// non-negative integer `n < N` , performs
-// *(result + n) = *(begin(range) + n)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.copy#:~:text=ranges::copy(R
-template <typename Range,
-          typename OutputIterator,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto copy(Range&& range, OutputIterator result) {
-  return ranges::copy(ranges::begin(range), ranges::end(range), result);
-}
-
-// Let `N` be `max(0, n)`.
-//
-// Mandates: The type `Size` is convertible to an integral type.
-//
-// Effects: For each non-negative integer `i < N`, performs
-// `*(result + i) = *(first + i)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.copy#:~:text=ranges::copy_n
-template <typename InputIterator,
-          typename Size,
-          typename OutputIterator,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto copy_n(InputIterator first, Size n, OutputIterator result) {
-  return std::copy_n(first, n, result);
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`, and `N` be the number
-// of iterators `i` in the range `[first, last)` for which the condition `E(i)`
-// holds.
-//
-// Preconditions: The ranges `[first, last)` and
-// `[result, result + (last - first))` do not overlap.
-//
-// Effects: Copies all of the elements referred to by the iterator `i` in the
-// range `[first, last)` for which `E(i)` is true.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.copy#:~:text=ranges::copy_if(I
-template <typename InputIterator,
-          typename OutputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto copy_if(InputIterator first,
-                       InputIterator last,
-                       OutputIterator result,
-                       Pred pred,
-                       Proj proj = {}) {
-  return std::copy_if(first, last, result,
-                      internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`, and `N` be the number
-// of iterators `i` in `range` for which the condition `E(i)` holds.
-//
-// Preconditions: `range`  and `[result, result + size(range))` do not overlap.
-//
-// Effects: Copies all of the elements referred to by the iterator `i` in
-// `range` for which `E(i)` is true.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.copy#:~:text=ranges::copy_if(R
-template <typename Range,
-          typename OutputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto copy_if(Range&& range,
-                       OutputIterator result,
-                       Pred pred,
-                       Proj proj = {}) {
-  return ranges::copy_if(ranges::begin(range), ranges::end(range), result,
-                         std::move(pred), std::move(proj));
-}
-
-// Let `N` be `last - first`.
-//
-// Preconditions: `result` is not in the range `(first, last]`.
-//
-// Effects: Copies elements in the range `[first, last)` into the range
-// `[result - N, result)` starting from `last - 1` and proceeding to `first`.
-// For each positive integer `n ≤ N`, performs `*(result - n) = *(last - n)`.
-//
-// Returns: `result - N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.copy#:~:text=ranges::copy_backward(I1
-template <typename BidirectionalIterator1,
-          typename BidirectionalIterator2,
-          typename = internal::iterator_category_t<BidirectionalIterator1>,
-          typename = internal::iterator_category_t<BidirectionalIterator2>>
-constexpr auto copy_backward(BidirectionalIterator1 first,
-                             BidirectionalIterator1 last,
-                             BidirectionalIterator2 result) {
-  return std::copy_backward(first, last, result);
-}
-
-// Let `N` be `size(range)`.
-//
-// Preconditions: `result` is not in the range `(begin(range), end(range)]`.
-//
-// Effects: Copies elements in `range` into the range `[result - N, result)`
-// starting from `end(range) - 1` and proceeding to `begin(range)`. For each
-// positive integer `n ≤ N`, performs `*(result - n) = *(end(range) - n)`.
-//
-// Returns: `result - N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.copy#:~:text=ranges::copy_backward(R
-template <typename Range,
-          typename BidirectionalIterator,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<BidirectionalIterator>>
-constexpr auto copy_backward(Range&& range, BidirectionalIterator result) {
-  return ranges::copy_backward(ranges::begin(range), ranges::end(range),
-                               result);
-}
-
-// [alg.move] Move
-// Reference: https://wg21.link/alg.move
-
-// Let `E(n)` be `std::move(*(first + n))`.
-//
-// Let `N` be `last - first`.
-//
-// Preconditions: `result` is not in the range `[first, last)`.
-//
-// Effects: Moves elements in the range `[first, last)` into the range `[result,
-// result + N)` starting from `first` and proceeding to `last`. For each
-// non-negative integer `n < N`, performs `*(result + n) = E(n)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.move#:~:text=ranges::move(I
-template <typename InputIterator,
-          typename OutputIterator,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto move(InputIterator first,
-                    InputIterator last,
-                    OutputIterator result) {
-  return std::move(first, last, result);
-}
-
-// Let `E(n)` be `std::move(*(begin(range) + n))`.
-//
-// Let `N` be `size(range)`.
-//
-// Preconditions: `result` is not in `range`.
-//
-// Effects: Moves elements in `range` into the range `[result, result + N)`
-// starting from `begin(range)` and proceeding to `end(range)`. For each
-// non-negative integer `n < N`, performs `*(result + n) = E(n)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.move#:~:text=ranges::move(R
-template <typename Range,
-          typename OutputIterator,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto move(Range&& range, OutputIterator result) {
-  return ranges::move(ranges::begin(range), ranges::end(range), result);
-}
-
-// Let `E(n)` be `std::move(*(last - n))`.
-//
-// Let `N` be `last - first`.
-//
-// Preconditions: `result` is not in the range `(first, last]`.
-//
-// Effects: Moves elements in the range `[first, last)` into the range
-// `[result - N, result)` starting from `last - 1` and proceeding to `first`.
-// For each positive integer `n ≤ N`, performs `*(result - n) = E(n)`.
-//
-// Returns: `result - N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.move#:~:text=ranges::move_backward(I1
-template <typename BidirectionalIterator1,
-          typename BidirectionalIterator2,
-          typename = internal::iterator_category_t<BidirectionalIterator1>,
-          typename = internal::iterator_category_t<BidirectionalIterator2>>
-constexpr auto move_backward(BidirectionalIterator1 first,
-                             BidirectionalIterator1 last,
-                             BidirectionalIterator2 result) {
-  return std::move_backward(first, last, result);
-}
-
-// Let `E(n)` be `std::move(*(end(range) - n))`.
-//
-// Let `N` be `size(range)`.
-//
-// Preconditions: `result` is not in the range `(begin(range), end(range)]`.
-//
-// Effects: Moves elements in `range` into the range `[result - N, result)`
-// starting from `end(range) - 1` and proceeding to `begin(range)`. For each
-// positive integer `n ≤ N`, performs `*(result - n) = E(n)`.
-//
-// Returns: `result - N`
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.move#:~:text=ranges::move_backward(R
-template <typename Range,
-          typename BidirectionalIterator,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<BidirectionalIterator>>
-constexpr auto move_backward(Range&& range, BidirectionalIterator result) {
-  return ranges::move_backward(ranges::begin(range), ranges::end(range),
-                               result);
-}
-
-// [alg.swap] Swap
-// Reference: https://wg21.link/alg.swap
-
-// Let `M` be `min(last1 - first1, last2 - first2)`.
-//
-// Preconditions: The two ranges `[first1, last1)` and `[first2, last2)` do not
-// overlap. `*(first1 + n)` is swappable with `*(first2 + n)`.
-//
-// Effects: For each non-negative integer `n < M` performs
-// `swap(*(first1 + n), *(first2 + n))`
-//
-// Returns: `first2 + M`
-//
-// Complexity: Exactly `M` swaps.
-//
-// Reference: https://wg21.link/alg.swap#:~:text=ranges::swap_ranges(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>>
-constexpr auto swap_ranges(ForwardIterator1 first1,
-                           ForwardIterator1 last1,
-                           ForwardIterator2 first2,
-                           ForwardIterator2 last2) {
-  // std::swap_ranges does not have a `last2` overload. Thus we need to
-  // adjust `last1` to ensure to not read past `last2`.
-  last1 = std::next(first1, std::min(std::distance(first1, last1),
-                                     std::distance(first2, last2)));
-  return std::swap_ranges(first1, last1, first2);
-}
-
-// Let `M` be `min(size(range1), size(range2))`.
-//
-// Preconditions: The two ranges `range1` and `range2` do not overlap.
-// `*(begin(range1) + n)` is swappable with `*(begin(range2) + n)`.
-//
-// Effects: For each non-negative integer `n < M` performs
-// `swap(*(begin(range1) + n), *(begin(range2) + n))`
-//
-// Returns: `begin(range2) + M`
-//
-// Complexity: Exactly `M` swaps.
-//
-// Reference: https://wg21.link/alg.swap#:~:text=ranges::swap_ranges(R1
-template <typename Range1,
-          typename Range2,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>>
-constexpr auto swap_ranges(Range1&& range1, Range2&& range2) {
-  return ranges::swap_ranges(ranges::begin(range1), ranges::end(range1),
-                             ranges::begin(range2), ranges::end(range2));
-}
-
-// [alg.transform] Transform
-// Reference: https://wg21.link/alg.transform
-
-// Let `N` be `last1 - first1`,
-// `E(i)` be `invoke(op, invoke(proj, *(first1 + (i - result))))`.
-//
-// Preconditions: `op` does not invalidate iterators or subranges, nor modify
-// elements in the ranges `[first1, first1 + N]`, and `[result, result + N]`.
-//
-// Effects: Assigns through every iterator `i` in the range
-// `[result, result + N)` a new corresponding value equal to `E(i)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` applications of `op` and any projections.
-//
-// Remarks: result may be equal to `first1`.
-//
-// Reference: https://wg21.link/alg.transform#:~:text=ranges::transform(I
-template <typename InputIterator,
-          typename OutputIterator,
-          typename UnaryOperation,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<UnaryOperation&,
-                                       projected<InputIterator, Proj>>>
-constexpr auto transform(InputIterator first1,
-                         InputIterator last1,
-                         OutputIterator result,
-                         UnaryOperation op,
-                         Proj proj = {}) {
-  return std::transform(first1, last1, result, [&op, &proj](auto&& arg) {
-    return gurl_base::invoke(op,
-                        gurl_base::invoke(proj, std::forward<decltype(arg)>(arg)));
-  });
-}
-
-// Let `N` be `size(range)`,
-// `E(i)` be `invoke(op, invoke(proj, *(begin(range) + (i - result))))`.
-//
-// Preconditions: `op` does not invalidate iterators or subranges, nor modify
-// elements in the ranges `[begin(range), end(range)]`, and
-// `[result, result + N]`.
-//
-// Effects: Assigns through every iterator `i` in the range
-// `[result, result + N)` a new corresponding value equal to `E(i)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` applications of `op` and any projections.
-//
-// Remarks: result may be equal to `begin(range)`.
-//
-// Reference: https://wg21.link/alg.transform#:~:text=ranges::transform(R
-template <typename Range,
-          typename OutputIterator,
-          typename UnaryOperation,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<UnaryOperation&,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto transform(Range&& range,
-                         OutputIterator result,
-                         UnaryOperation op,
-                         Proj proj = {}) {
-  return ranges::transform(ranges::begin(range), ranges::end(range), result,
-                           std::move(op), std::move(proj));
-}
-
-// Let:
-// `N` be `min(last1 - first1, last2 - first2)`,
-// `E(i)` be `invoke(binary_op, invoke(proj1, *(first1 + (i - result))),
-//                              invoke(proj2, *(first2 + (i - result))))`.
-//
-// Preconditions: `binary_op` does not invalidate iterators or subranges, nor
-// modify elements in the ranges `[first1, first1 + N]`, `[first2, first2 + N]`,
-// and `[result, result + N]`.
-//
-// Effects: Assigns through every iterator `i` in the range
-// `[result, result + N)` a new corresponding value equal to `E(i)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` applications of `binary_op`, and any projections.
-//
-// Remarks: `result` may be equal to `first1` or `first2`.
-//
-// Reference: https://wg21.link/alg.transform#:~:text=ranges::transform(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename OutputIterator,
-          typename BinaryOperation,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<BinaryOperation&,
-                                       projected<ForwardIterator1, Proj1>,
-                                       projected<ForwardIterator2, Proj2>>>
-constexpr auto transform(ForwardIterator1 first1,
-                         ForwardIterator1 last1,
-                         ForwardIterator2 first2,
-                         ForwardIterator2 last2,
-                         OutputIterator result,
-                         BinaryOperation binary_op,
-                         Proj1 proj1 = {},
-                         Proj2 proj2 = {}) {
-  // std::transform does not have a `last2` overload. Thus we need to adjust
-  // `last1` to ensure to not read past `last2`.
-  last1 = std::next(first1, std::min(std::distance(first1, last1),
-                                     std::distance(first2, last2)));
-  return std::transform(
-      first1, last1, first2, result,
-      [&binary_op, &proj1, &proj2](auto&& lhs, auto&& rhs) {
-        return gurl_base::invoke(
-            binary_op, gurl_base::invoke(proj1, std::forward<decltype(lhs)>(lhs)),
-            gurl_base::invoke(proj2, std::forward<decltype(rhs)>(rhs)));
-      });
-}
-
-// Let:
-// `N` be `min(size(range1), size(range2)`,
-// `E(i)` be `invoke(binary_op, invoke(proj1, *(begin(range1) + (i - result))),
-//                              invoke(proj2, *(begin(range2) + (i - result))))`
-//
-// Preconditions: `binary_op` does not invalidate iterators or subranges, nor
-// modify elements in the ranges `[begin(range1), end(range1)]`,
-// `[begin(range2), end(range2)]`, and `[result, result + N]`.
-//
-// Effects: Assigns through every iterator `i` in the range
-// `[result, result + N)` a new corresponding value equal to `E(i)`.
-//
-// Returns: `result + N`
-//
-// Complexity: Exactly `N` applications of `binary_op`, and any projections.
-//
-// Remarks: `result` may be equal to `begin(range1)` or `begin(range2)`.
-//
-// Reference: https://wg21.link/alg.transform#:~:text=ranges::transform(R1
-template <typename Range1,
-          typename Range2,
-          typename OutputIterator,
-          typename BinaryOperation,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<BinaryOperation&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>>
-constexpr auto transform(Range1&& range1,
-                         Range2&& range2,
-                         OutputIterator result,
-                         BinaryOperation binary_op,
-                         Proj1 proj1 = {},
-                         Proj2 proj2 = {}) {
-  return ranges::transform(ranges::begin(range1), ranges::end(range1),
-                           ranges::begin(range2), ranges::end(range2), result,
-                           std::move(binary_op), std::move(proj1),
-                           std::move(proj2));
-}
-
-// [alg.replace] Replace
-// Reference: https://wg21.link/alg.replace
-
-// Let `E(i)` be `bool(invoke(proj, *i) == old_value)`.
-//
-// Mandates: `new_value` is writable  to `first`.
-//
-// Effects: Substitutes elements referred by the iterator `i` in the range
-// `[first, last)` with `new_value`, when `E(i)` is true.
-//
-// Returns: `last`
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.replace#:~:text=ranges::replace(I
-template <typename ForwardIterator,
-          typename T,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto replace(ForwardIterator first,
-                       ForwardIterator last,
-                       const T& old_value,
-                       const T& new_value,
-                       Proj proj = {}) {
-  // Note: In order to be able to apply `proj` to each element in [first, last)
-  // we are dispatching to std::replace_if instead of std::replace.
-  std::replace_if(
-      first, last,
-      [&proj, &old_value](auto&& lhs) {
-        return gurl_base::invoke(proj, std::forward<decltype(lhs)>(lhs)) ==
-               old_value;
-      },
-      new_value);
-  return last;
-}
-
-// Let `E(i)` be `bool(invoke(proj, *i) == old_value)`.
-//
-// Mandates: `new_value` is writable  to `begin(range)`.
-//
-// Effects: Substitutes elements referred by the iterator `i` in `range` with
-// `new_value`, when `E(i)` is true.
-//
-// Returns: `end(range)`
-//
-// Complexity: Exactly `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.replace#:~:text=ranges::replace(R
-template <typename Range,
-          typename T,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto replace(Range&& range,
-                       const T& old_value,
-                       const T& new_value,
-                       Proj proj = {}) {
-  return ranges::replace(ranges::begin(range), ranges::end(range), old_value,
-                         new_value, std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Mandates: `new_value` is writable  to `first`.
-//
-// Effects: Substitutes elements referred by the iterator `i` in the range
-// `[first, last)` with `new_value`, when `E(i)` is true.
-//
-// Returns: `last`
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.replace#:~:text=ranges::replace_if(I
-template <typename ForwardIterator,
-          typename Predicate,
-          typename T,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto replace_if(ForwardIterator first,
-                          ForwardIterator last,
-                          Predicate pred,
-                          const T& new_value,
-                          Proj proj = {}) {
-  std::replace_if(first, last, internal::ProjectedUnaryPredicate(pred, proj),
-                  new_value);
-  return last;
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Mandates: `new_value` is writable  to `begin(range)`.
-//
-// Effects: Substitutes elements referred by the iterator `i` in `range` with
-// `new_value`, when `E(i)` is true.
-//
-// Returns: `end(range)`
-//
-// Complexity: Exactly `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.replace#:~:text=ranges::replace_if(R
-template <typename Range,
-          typename Predicate,
-          typename T,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto replace_if(Range&& range,
-                          Predicate pred,
-                          const T& new_value,
-                          Proj proj = {}) {
-  return ranges::replace_if(ranges::begin(range), ranges::end(range),
-                            std::move(pred), new_value, std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(proj, *(first + (i - result))) == old_value)`.
-//
-// Mandates: The results of the expressions `*first` and `new_value` are
-// writable  to `result`.
-//
-// Preconditions: The ranges `[first, last)` and `[result, result + (last -
-// first))` do not overlap.
-//
-// Effects: Assigns through every iterator `i` in the range `[result, result +
-// (last - first))` a new corresponding value, `new_value` if `E(i)` is true, or
-// `*(first + (i - result))` otherwise.
-//
-// Returns: `result + (last - first)`.
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.replace#:~:text=ranges::replace_copy(I
-template <typename InputIterator,
-          typename OutputIterator,
-          typename T,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto replace_copy(InputIterator first,
-                            InputIterator last,
-                            OutputIterator result,
-                            const T& old_value,
-                            const T& new_value,
-                            Proj proj = {}) {
-  // Note: In order to be able to apply `proj` to each element in [first, last)
-  // we are dispatching to std::replace_copy_if instead of std::replace_copy.
-  std::replace_copy_if(
-      first, last, result,
-      [&proj, &old_value](auto&& lhs) {
-        return gurl_base::invoke(proj, std::forward<decltype(lhs)>(lhs)) ==
-               old_value;
-      },
-      new_value);
-  return last;
-}
-
-// Let `E(i)` be
-// `bool(invoke(proj, *(begin(range) + (i - result))) == old_value)`.
-//
-// Mandates: The results of the expressions `*begin(range)` and `new_value` are
-// writable  to `result`.
-//
-// Preconditions: The ranges `range` and `[result, result + size(range))` do not
-// overlap.
-//
-// Effects: Assigns through every iterator `i` in the range `[result, result +
-// size(range))` a new corresponding value, `new_value` if `E(i)` is true, or
-// `*(begin(range) + (i - result))` otherwise.
-//
-// Returns: `result + size(range)`.
-//
-// Complexity: Exactly `size(range)` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.replace#:~:text=ranges::replace_copy(R
-template <typename Range,
-          typename OutputIterator,
-          typename T,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto replace_copy(Range&& range,
-                            OutputIterator result,
-                            const T& old_value,
-                            const T& new_value,
-                            Proj proj = {}) {
-  return ranges::replace_copy(ranges::begin(range), ranges::end(range), result,
-                              old_value, new_value, std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *(first + (i - result)))))`.
-//
-// Mandates: The results of the expressions `*first` and `new_value` are
-// writable  to `result`.
-//
-// Preconditions: The ranges `[first, last)` and `[result, result + (last -
-// first))` do not overlap.
-//
-// Effects: Assigns through every iterator `i` in the range `[result, result +
-// (last - first))` a new corresponding value, `new_value` if `E(i)` is true, or
-// `*(first + (i - result))` otherwise.
-//
-// Returns: `result + (last - first)`.
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.replace#:~:text=ranges::replace_copy_if(I
-template <typename InputIterator,
-          typename OutputIterator,
-          typename Predicate,
-          typename T,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto replace_copy_if(InputIterator first,
-                               InputIterator last,
-                               OutputIterator result,
-                               Predicate pred,
-                               const T& new_value,
-                               Proj proj = {}) {
-  return std::replace_copy_if(first, last, result,
-                              internal::ProjectedUnaryPredicate(pred, proj),
-                              new_value);
-}
-
-// Let `E(i)` be
-// `bool(invoke(pred, invoke(proj, *(begin(range) + (i - result)))))`.
-//
-// Mandates: The results of the expressions `*begin(range)` and `new_value` are
-// writable  to `result`.
-//
-// Preconditions: The ranges `range` and `[result, result + size(range))` do not
-// overlap.
-//
-// Effects: Assigns through every iterator `i` in the range `[result, result +
-// size(range))` a new corresponding value, `new_value` if `E(i)` is true, or
-// `*(begin(range) + (i - result))` otherwise.
-//
-// Returns: `result + size(range)`.
-//
-// Complexity: Exactly `size(range)` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.replace#:~:text=ranges::replace_copy_if(R
-template <typename Range,
-          typename OutputIterator,
-          typename Predicate,
-          typename T,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto replace_copy_if(Range&& range,
-                               OutputIterator result,
-                               Predicate pred,
-                               const T& new_value,
-                               Proj proj = {}) {
-  return ranges::replace_copy_if(ranges::begin(range), ranges::end(range),
-                                 result, pred, new_value, std::move(proj));
-}
-
-// [alg.fill] Fill
-// Reference: https://wg21.link/alg.fill
-
-// Let `N` be `last - first`.
-//
-// Mandates: The expression `value` is writable to the output iterator.
-//
-// Effects: Assigns `value` through all the iterators in the range
-// `[first, last)`.
-//
-// Returns: `last`.
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.fill#:~:text=ranges::fill(O
-template <typename OutputIterator,
-          typename T,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto fill(OutputIterator first, OutputIterator last, const T& value) {
-  std::fill(first, last, value);
-  return last;
-}
-
-// Let `N` be `size(range)`.
-//
-// Mandates: The expression `value` is writable to the output iterator.
-//
-// Effects: Assigns `value` through all the iterators in `range`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.fill#:~:text=ranges::fill(R
-template <typename Range,
-          typename T,
-          typename = internal::range_category_t<Range>>
-constexpr auto fill(Range&& range, const T& value) {
-  return ranges::fill(ranges::begin(range), ranges::end(range), value);
-}
-
-// Let `N` be `max(0, n)`.
-//
-// Mandates: The expression `value` is writable to the output iterator.
-// The type `Size` is convertible to an integral type.
-//
-// Effects: Assigns `value` through all the iterators in `[first, first + N)`.
-//
-// Returns: `first + N`.
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.fill#:~:text=ranges::fill_n(O
-template <typename OutputIterator,
-          typename Size,
-          typename T,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto fill_n(OutputIterator first, Size n, const T& value) {
-  return std::fill_n(first, n, value);
-}
-
-// [alg.generate] Generate
-// Reference: https://wg21.link/alg.generate
-
-// Let `N` be `last - first`.
-//
-// Effects: Assigns the result of successive evaluations of gen() through each
-// iterator in the range `[first, last)`.
-//
-// Returns: `last`.
-//
-// Complexity: Exactly `N` evaluations of `gen()` and assignments.
-//
-// Reference: https://wg21.link/alg.generate#:~:text=ranges::generate(O
-template <typename OutputIterator,
-          typename Generator,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto generate(OutputIterator first,
-                        OutputIterator last,
-                        Generator gen) {
-  std::generate(first, last, std::move(gen));
-  return last;
-}
-
-// Let `N` be `size(range)`.
-//
-// Effects: Assigns the result of successive evaluations of gen() through each
-// iterator in `range`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: Exactly `N` evaluations of `gen()` and assignments.
-//
-// Reference: https://wg21.link/alg.generate#:~:text=ranges::generate(R
-template <typename Range,
-          typename Generator,
-          typename = internal::range_category_t<Range>>
-constexpr auto generate(Range&& range, Generator gen) {
-  return ranges::generate(ranges::begin(range), ranges::end(range),
-                          std::move(gen));
-}
-
-// Let `N` be `max(0, n)`.
-//
-// Mandates: `Size` is convertible to an integral type.
-//
-// Effects: Assigns the result of successive evaluations of gen() through each
-// iterator in the range `[first, first + N)`.
-//
-// Returns: `first + N`.
-//
-// Complexity: Exactly `N` evaluations of `gen()` and assignments.
-//
-// Reference: https://wg21.link/alg.generate#:~:text=ranges::generate_n(O
-template <typename OutputIterator,
-          typename Size,
-          typename Generator,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto generate_n(OutputIterator first, Size n, Generator gen) {
-  return std::generate_n(first, n, std::move(gen));
-}
-
-// [alg.remove] Remove
-// Reference: https://wg21.link/alg.remove
-
-// Let `E(i)` be `bool(invoke(proj, *i) == value)`.
-//
-// Effects: Eliminates all the elements referred to by iterator `i` in the range
-// `[first, last)` for which `E(i)` holds.
-//
-// Returns: The end of the resulting range.
-//
-// Remarks: Stable.
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.remove#:~:text=ranges::remove(I
-template <typename ForwardIterator,
-          typename T,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto remove(ForwardIterator first,
-                      ForwardIterator last,
-                      const T& value,
-                      Proj proj = {}) {
-  // Note: In order to be able to apply `proj` to each element in [first, last)
-  // we are dispatching to std::remove_if instead of std::remove.
-  return std::remove_if(first, last, [&proj, &value](auto&& lhs) {
-    return gurl_base::invoke(proj, std::forward<decltype(lhs)>(lhs)) == value;
-  });
-}
-
-// Let `E(i)` be `bool(invoke(proj, *i) == value)`.
-//
-// Effects: Eliminates all the elements referred to by iterator `i` in `range`
-// for which `E(i)` holds.
-//
-// Returns: The end of the resulting range.
-//
-// Remarks: Stable.
-//
-// Complexity: Exactly `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.remove#:~:text=ranges::remove(R
-template <typename Range,
-          typename T,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto remove(Range&& range, const T& value, Proj proj = {}) {
-  return ranges::remove(ranges::begin(range), ranges::end(range), value,
-                        std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Effects: Eliminates all the elements referred to by iterator `i` in the range
-// `[first, last)` for which `E(i)` holds.
-//
-// Returns: The end of the resulting range.
-//
-// Remarks: Stable.
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Reference: https://wg21.link/alg.remove#:~:text=ranges::remove_if(I
-template <typename ForwardIterator,
-          typename Predicate,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto remove_if(ForwardIterator first,
-                         ForwardIterator last,
-                         Predicate pred,
-                         Proj proj = {}) {
-  return std::remove_if(first, last,
-                        internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Effects: Eliminates all the elements referred to by iterator `i` in `range`.
-//
-// Returns: The end of the resulting range.
-//
-// Remarks: Stable.
-//
-// Complexity: Exactly `size(range)` applications of the corresponding predicate
-// and any projection.
-//
-// Reference: https://wg21.link/alg.remove#:~:text=ranges::remove_if(R
-template <typename Range,
-          typename Predicate,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto remove_if(Range&& range, Predicate pred, Proj proj = {}) {
-  return ranges::remove_if(ranges::begin(range), ranges::end(range),
-                           std::move(pred), std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(proj, *i) == value)`.
-//
-// Let `N` be the number of elements in `[first, last)` for which `E(i)` is
-// false.
-//
-// Mandates: `*first` is writable to `result`.
-//
-// Preconditions: The ranges `[first, last)` and `[result, result + (last -
-// first))` do not overlap.
-//
-// Effects: Copies all the elements referred to by the iterator `i` in the range
-// `[first, last)` for which `E(i)` is false.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.remove#:~:text=ranges::remove_copy(I
-template <typename InputIterator,
-          typename OutputIterator,
-          typename T,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto remove_copy(InputIterator first,
-                           InputIterator last,
-                           OutputIterator result,
-                           const T& value,
-                           Proj proj = {}) {
-  // Note: In order to be able to apply `proj` to each element in [first, last)
-  // we are dispatching to std::remove_copy_if instead of std::remove_copy.
-  return std::remove_copy_if(first, last, result, [&proj, &value](auto&& lhs) {
-    return gurl_base::invoke(proj, std::forward<decltype(lhs)>(lhs)) == value;
-  });
-}
-
-// Let `E(i)` be `bool(invoke(proj, *i) == value)`.
-//
-// Let `N` be the number of elements in `range` for which `E(i)` is false.
-//
-// Mandates: `*begin(range)` is writable to `result`.
-//
-// Preconditions: The ranges `range` and `[result, result + size(range))` do not
-// overlap.
-//
-// Effects: Copies all the elements referred to by the iterator `i` in `range`
-//  for which `E(i)` is false.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `size(range)` applications of the corresponding
-// predicate and any projection.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.remove#:~:text=ranges::remove_copy(R
-template <typename Range,
-          typename OutputIterator,
-          typename T,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto remove_copy(Range&& range,
-                           OutputIterator result,
-                           const T& value,
-                           Proj proj = {}) {
-  return ranges::remove_copy(ranges::begin(range), ranges::end(range), result,
-                             value, std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Let `N` be the number of elements in `[first, last)` for which `E(i)` is
-// false.
-//
-// Mandates: `*first` is writable to `result`.
-//
-// Preconditions: The ranges `[first, last)` and `[result, result + (last -
-// first))` do not overlap.
-//
-// Effects: Copies all the elements referred to by the iterator `i` in the range
-// `[first, last)` for which `E(i)` is false.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `last - first` applications of the corresponding
-// predicate and any projection.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.remove#:~:text=ranges::remove_copy_if(I
-template <typename InputIterator,
-          typename OutputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto remove_copy_if(InputIterator first,
-                              InputIterator last,
-                              OutputIterator result,
-                              Pred pred,
-                              Proj proj = {}) {
-  return std::remove_copy_if(first, last, result,
-                             internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(i)` be `bool(invoke(pred, invoke(proj, *i)))`.
-//
-// Let `N` be the number of elements in `range` for which `E(i)` is false.
-//
-// Mandates: `*begin(range)` is writable to `result`.
-//
-// Preconditions: The ranges `range` and `[result, result + size(range))` do not
-// overlap.
-//
-// Effects: Copies all the elements referred to by the iterator `i` in `range`
-//  for which `E(i)` is false.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `size(range)` applications of the corresponding
-// predicate and any projection.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.remove#:~:text=ranges::remove_copy(R
-template <typename Range,
-          typename OutputIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto remove_copy_if(Range&& range,
-                              OutputIterator result,
-                              Pred pred,
-                              Proj proj = {}) {
-  return ranges::remove_copy_if(ranges::begin(range), ranges::end(range),
-                                result, std::move(pred), std::move(proj));
-}
-
-// [alg.unique] Unique
-// Reference: https://wg21.link/alg.unique
-
-// Let `E(i)` be `bool(invoke(comp, invoke(proj, *(i - 1)), invoke(proj, *i)))`.
-//
-// Effects: For a nonempty range, eliminates all but the first element from
-// every consecutive group of equivalent elements referred to by the iterator
-// `i` in the range `[first + 1, last)` for which `E(i)` is true.
-//
-// Returns: The end of the resulting range.
-//
-// Complexity: For nonempty ranges, exactly `(last - first) - 1` applications of
-// the corresponding predicate and no more than twice as many applications of
-// any projection.
-//
-// Reference: https://wg21.link/alg.unique#:~:text=ranges::unique(I
-template <typename ForwardIterator,
-          typename Comp = ranges::equal_to,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<ForwardIterator, Proj>,
-                                       projected<ForwardIterator, Proj>>>
-constexpr auto unique(ForwardIterator first,
-                      ForwardIterator last,
-                      Comp comp = {},
-                      Proj proj = {}) {
-  return std::unique(first, last,
-                     internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Let `E(i)` be `bool(invoke(comp, invoke(proj, *(i - 1)), invoke(proj, *i)))`.
-//
-// Effects: For a nonempty range, eliminates all but the first element from
-// every consecutive group of equivalent elements referred to by the iterator
-// `i` in the range `[begin(range) + 1, end(range))` for which `E(i)` is true.
-//
-// Returns: The end of the resulting range.
-//
-// Complexity: For nonempty ranges, exactly `size(range) - 1` applications of
-// the corresponding predicate and no more than twice as many applications of
-// any projection.
-//
-// Reference: https://wg21.link/alg.unique#:~:text=ranges::unique(R
-template <typename Range,
-          typename Comp = ranges::equal_to,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto unique(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::unique(ranges::begin(range), ranges::end(range),
-                        std::move(comp), std::move(proj));
-}
-
-// Let `E(i)` be `bool(invoke(comp, invoke(proj, *i), invoke(proj, *(i - 1))))`.
-//
-// Mandates: `*first` is writable to `result`.
-//
-// Preconditions: The ranges `[first, last)` and
-// `[result, result + (last - first))` do not overlap.
-//
-// Effects: Copies only the first element from every consecutive group of equal
-// elements referred to by the iterator `i` in the range `[first, last)` for
-// which `E(i)` holds.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `last - first - 1` applications of the corresponding
-// predicate and no more than twice as many applications of any projection.
-//
-// Reference: https://wg21.link/alg.unique#:~:text=ranges::unique_copy(I
-template <typename ForwardIterator,
-          typename OutputIterator,
-          typename Comp = ranges::equal_to,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto unique_copy(ForwardIterator first,
-                           ForwardIterator last,
-                           OutputIterator result,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  return std::unique_copy(first, last, result,
-                          internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Let `E(i)` be `bool(invoke(comp, invoke(proj, *i), invoke(proj, *(i - 1))))`.
-//
-// Mandates: `*begin(range)` is writable to `result`.
-//
-// Preconditions: The ranges `range` and `[result, result + size(range))` do not
-// overlap.
-//
-// Effects: Copies only the first element from every consecutive group of equal
-// elements referred to by the iterator `i` in `range` for which `E(i)` holds.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `size(range) - 1` applications of the corresponding
-// predicate and no more than twice as many applications of any projection.
-//
-// Reference: https://wg21.link/alg.unique#:~:text=ranges::unique_copy(R
-template <typename Range,
-          typename OutputIterator,
-          typename Comp = ranges::equal_to,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto unique_copy(Range&& range,
-                           OutputIterator result,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  return ranges::unique_copy(ranges::begin(range), ranges::end(range), result,
-                             std::move(comp), std::move(proj));
-}
-
-// [alg.reverse] Reverse
-// Reference: https://wg21.link/alg.reverse
-
-// Effects: For each non-negative integer `i < (last - first) / 2`, applies
-// `std::iter_swap` to all pairs of iterators `first + i, (last - i) - 1`.
-//
-// Returns: `last`.
-//
-// Complexity: Exactly `(last - first)/2` swaps.
-//
-// Reference: https://wg21.link/alg.reverse#:~:text=ranges::reverse(I
-template <typename BidirectionalIterator,
-          typename = internal::iterator_category_t<BidirectionalIterator>>
-constexpr auto reverse(BidirectionalIterator first,
-                       BidirectionalIterator last) {
-  std::reverse(first, last);
-  return last;
-}
-
-// Effects: For each non-negative integer `i < size(range) / 2`, applies
-// `std::iter_swap` to all pairs of iterators
-// `begin(range) + i, (end(range) - i) - 1`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: Exactly `size(range)/2` swaps.
-//
-// Reference: https://wg21.link/alg.reverse#:~:text=ranges::reverse(R
-template <typename Range, typename = internal::range_category_t<Range>>
-constexpr auto reverse(Range&& range) {
-  return ranges::reverse(ranges::begin(range), ranges::end(range));
-}
-
-// Let `N` be `last - first`.
-//
-// Preconditions: The ranges `[first, last)` and `[result, result + N)` do not
-// overlap.
-//
-// Effects: Copies the range `[first, last)` to the range `[result, result + N)`
-// such that for every non-negative integer `i < N` the following assignment
-// takes place: `*(result + N - 1 - i) = *(first + i)`.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.reverse#:~:text=ranges::reverse_copy(I
-template <typename BidirectionalIterator,
-          typename OutputIterator,
-          typename = internal::iterator_category_t<BidirectionalIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto reverse_copy(BidirectionalIterator first,
-                            BidirectionalIterator last,
-                            OutputIterator result) {
-  return std::reverse_copy(first, last, result);
-}
-
-// Let `N` be `size(range)`.
-//
-// Preconditions: The ranges `range` and `[result, result + N)` do not
-// overlap.
-//
-// Effects: Copies `range` to the range `[result, result + N)` such that for
-// every non-negative integer `i < N` the following assignment takes place:
-// `*(result + N - 1 - i) = *(begin(range) + i)`.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.reverse#:~:text=ranges::reverse_copy(R
-template <typename Range,
-          typename OutputIterator,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto reverse_copy(Range&& range, OutputIterator result) {
-  return ranges::reverse_copy(ranges::begin(range), ranges::end(range), result);
-}
-
-// [alg.rotate] Rotate
-// Reference: https://wg21.link/alg.rotate
-
-// Preconditions: `[first, middle)` and `[middle, last)` are valid ranges.
-//
-// Effects: For each non-negative integer `i < (last - first)`, places the
-// element from the position `first + i` into position
-// `first + (i + (last - middle)) % (last - first)`.
-//
-// Returns: `first + (last - middle)`.
-//
-// Complexity: At most `last - first` swaps.
-//
-// Reference: https://wg21.link/alg.rotate#:~:text=ranges::rotate(I
-template <typename ForwardIterator,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto rotate(ForwardIterator first,
-                      ForwardIterator middle,
-                      ForwardIterator last) {
-  return std::rotate(first, middle, last);
-}
-
-// Preconditions: `[begin(range), middle)` and `[middle, end(range))` are valid
-// ranges.
-//
-// Effects: For each non-negative integer `i < size(range)`, places the element
-// from the position `begin(range) + i` into position
-// `begin(range) + (i + (end(range) - middle)) % size(range)`.
-//
-// Returns: `begin(range) + (end(range) - middle)`.
-//
-// Complexity: At most `size(range)` swaps.
-//
-// Reference: https://wg21.link/alg.rotate#:~:text=ranges::rotate(R
-template <typename Range, typename = internal::range_category_t<Range>>
-constexpr auto rotate(Range&& range, iterator_t<Range> middle) {
-  return ranges::rotate(ranges::begin(range), middle, ranges::end(range));
-}
-
-// Let `N` be `last - first`.
-//
-// Preconditions: `[first, middle)` and `[middle, last)` are valid ranges. The
-// ranges `[first, last)` and `[result, result + N)` do not overlap.
-//
-// Effects: Copies the range `[first, last)` to the range `[result, result + N)`
-// such that for each non-negative integer `i < N` the following assignment
-// takes place: `*(result + i) = *(first + (i + (middle - first)) % N)`.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.rotate#:~:text=ranges::rotate_copy(I
-template <typename ForwardIterator,
-          typename OutputIterator,
-          typename = internal::iterator_category_t<ForwardIterator>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto rotate_copy(ForwardIterator first,
-                           ForwardIterator middle,
-                           ForwardIterator last,
-                           OutputIterator result) {
-  return std::rotate_copy(first, middle, last, result);
-}
-
-// Let `N` be `size(range)`.
-//
-// Preconditions: `[begin(range), middle)` and `[middle, end(range))` are valid
-// ranges. The ranges `range` and `[result, result + N)` do not overlap.
-//
-// Effects: Copies `range` to the range `[result, result + N)` such that for
-// each non-negative integer `i < N` the following assignment takes place:
-// `*(result + i) = *(begin(range) + (i + (middle - begin(range))) % N)`.
-//
-// Returns: `result + N`.
-//
-// Complexity: Exactly `N` assignments.
-//
-// Reference: https://wg21.link/alg.rotate#:~:text=ranges::rotate_copy(R
-template <typename Range,
-          typename OutputIterator,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator>>
-constexpr auto rotate_copy(Range&& range,
-                           iterator_t<Range> middle,
-                           OutputIterator result) {
-  return ranges::rotate_copy(ranges::begin(range), middle, ranges::end(range),
-                             result);
-}
-
-// [alg.random.sample] Sample
-// Reference: https://wg21.link/alg.random.sample
-
-// Currently not implemented due to lack of std::sample in C++14.
-// TODO(crbug.com/1071094): Consider implementing a hand-rolled version.
-
-// [alg.random.shuffle] Shuffle
-// Reference: https://wg21.link/alg.random.shuffle
-
-// Preconditions: The type `std::remove_reference_t<UniformRandomBitGenerator>`
-// meets the uniform random bit generator requirements.
-//
-// Effects: Permutes the elements in the range `[first, last)` such that each
-// possible permutation of those elements has equal probability of appearance.
-//
-// Returns: `last`.
-//
-// Complexity: Exactly `(last - first) - 1` swaps.
-//
-// Remarks: To the extent that the implementation of this function makes use of
-// random numbers, the object referenced by g shall serve as the
-// implementation's source of randomness.
-//
-// Reference: https://wg21.link/alg.random.shuffle#:~:text=ranges::shuffle(I
-template <typename RandomAccessIterator,
-          typename UniformRandomBitGenerator,
-          typename = internal::iterator_category_t<RandomAccessIterator>>
-constexpr auto shuffle(RandomAccessIterator first,
-                       RandomAccessIterator last,
-                       UniformRandomBitGenerator&& g) {
-  std::shuffle(first, last, std::forward<UniformRandomBitGenerator>(g));
-  return last;
-}
-
-// Preconditions: The type `std::remove_reference_t<UniformRandomBitGenerator>`
-// meets the uniform random bit generator requirements.
-//
-// Effects: Permutes the elements in `range` such that each possible permutation
-// of those elements has equal probability of appearance.
-//
-// Returns: `end(range)`.
-//
-// Complexity: Exactly `size(range) - 1` swaps.
-//
-// Remarks: To the extent that the implementation of this function makes use of
-// random numbers, the object referenced by g shall serve as the
-// implementation's source of randomness.
-//
-// Reference: https://wg21.link/alg.random.shuffle#:~:text=ranges::shuffle(R
-template <typename Range,
-          typename UniformRandomBitGenerator,
-          typename = internal::range_category_t<Range>>
-constexpr auto shuffle(Range&& range, UniformRandomBitGenerator&& g) {
-  return ranges::shuffle(ranges::begin(range), ranges::end(range),
-                         std::forward<UniformRandomBitGenerator>(g));
-}
-
-// [alg.nonmodifying] Sorting and related operations
-// Reference: https://wg21.link/alg.sorting
-
-// [alg.sort] Sorting
-// Reference: https://wg21.link/alg.sort
-
-// [sort] sort
-// Reference: https://wg21.link/sort
-
-// Effects: Sorts the elements in the range `[first, last)` with respect to
-// `comp` and `proj`.
-//
-// Returns: `last`.
-//
-// Complexity: Let `N` be `last - first`. `O(N log N)` comparisons and
-// projections.
-//
-// Reference: https://wg21.link/sort#:~:text=ranges::sort(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto sort(RandomAccessIterator first,
-                    RandomAccessIterator last,
-                    Comp comp = {},
-                    Proj proj = {}) {
-  std::sort(first, last, internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Effects: Sorts the elements in `range` with respect to `comp` and `proj`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: Let `N` be `size(range)`. `O(N log N)` comparisons and
-// projections.
-//
-// Reference: https://wg21.link/sort#:~:text=ranges::sort(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto sort(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::sort(ranges::begin(range), ranges::end(range), std::move(comp),
-                      std::move(proj));
-}
-
-// [stable.sort] stable_sort
-// Reference: https://wg21.link/stable.sort
-
-// Effects: Sorts the elements in the range `[first, last)` with respect to
-// `comp` and `proj`.
-//
-// Returns: `last`.
-//
-// Complexity: Let `N` be `last - first`. If enough extra memory is available,
-// `N log (N)` comparisons. Otherwise, at most `N log^2 (N)` comparisons. In
-// either case, twice as many projections as the number of comparisons.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/stable.sort#:~:text=ranges::stable_sort(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto stable_sort(RandomAccessIterator first,
-                           RandomAccessIterator last,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  std::stable_sort(first, last,
-                   internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Effects: Sorts the elements in `range` with respect to `comp` and `proj`.
-//
-// Returns: `end(rang)`.
-//
-// Complexity: Let `N` be `size(range)`. If enough extra memory is available,
-// `N log (N)` comparisons. Otherwise, at most `N log^2 (N)` comparisons. In
-// either case, twice as many projections as the number of comparisons.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/stable.sort#:~:text=ranges::stable_sort(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto stable_sort(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::stable_sort(ranges::begin(range), ranges::end(range),
-                             std::move(comp), std::move(proj));
-}
-
-// [partial.sort] partial_sort
-// Reference: https://wg21.link/partial.sort
-
-// Preconditions: `[first, middle)` and `[middle, last)` are valid ranges.
-//
-// Effects: Places the first `middle - first` elements from the range
-// `[first, last)` as sorted with respect to `comp` and `proj` into the range
-// `[first, middle)`. The rest of the elements in the range `[middle, last)` are
-// placed in an unspecified order.
-//
-// Returns: `last`.
-//
-// Complexity: Approximately `(last - first) * log(middle - first)` comparisons,
-// and twice as many projections.
-//
-// Reference: https://wg21.link/partial.sort#:~:text=ranges::partial_sort(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto partial_sort(RandomAccessIterator first,
-                            RandomAccessIterator middle,
-                            RandomAccessIterator last,
-                            Comp comp = {},
-                            Proj proj = {}) {
-  std::partial_sort(first, middle, last,
-                    internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Preconditions: `[begin(range), middle)` and `[middle, end(range))` are valid
-// ranges.
-//
-// Effects: Places the first `middle - begin(range)` elements from `range` as
-// sorted with respect to `comp` and `proj` into the range
-// `[begin(range), middle)`. The rest of the elements in the range
-// `[middle, end(range))` are placed in an unspecified order.
-//
-// Returns: `end(range)`.
-//
-// Complexity: Approximately `size(range) * log(middle - begin(range))`
-// comparisons, and twice as many projections.
-//
-// Reference: https://wg21.link/partial.sort#:~:text=ranges::partial_sort(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto partial_sort(Range&& range,
-                            iterator_t<Range> middle,
-                            Comp comp = {},
-                            Proj proj = {}) {
-  return ranges::partial_sort(ranges::begin(range), middle, ranges::end(range),
-                              std::move(comp), std::move(proj));
-}
-
-// [partial.sort.copy] partial_sort_copy
-// Reference: https://wg21.link/partial.sort.copy
-
-// Let `N` be `min(last - first, result_last - result_first)`.
-//
-// Preconditions: For iterators `a1` and `b1` in `[first, last)`, and iterators
-// `x2` and `y2` in `[result_first, result_last)`, after evaluating the
-// assignment `*y2 = *b1`, let `E` be the value of `bool(invoke(comp,
-// invoke(proj1, *a1), invoke(proj2, *y2)))`. Then, after evaluating the
-// assignment `*x2 = *a1`, `E` is equal to `bool(invoke(comp, invoke(proj2,
-// *x2), invoke(proj2, *y2)))`.
-//
-// Effects: Places the first `N` elements as sorted with respect to `comp` and
-// `proj2` into the range `[result_first, result_first + N)`.
-//
-// Returns: `result_first + N`.
-//
-// Complexity: Approximately `(last - first) * log N` comparisons, and twice as
-// many projections.
-//
-// Reference:
-// https://wg21.link/partial.sort.copy#:~:text=ranges::partial_sort_copy(I1
-template <typename InputIterator,
-          typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator, Proj1>,
-                                       projected<RandomAccessIterator, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj2>,
-                                       projected<InputIterator, Proj1>>>
-constexpr auto partial_sort_copy(InputIterator first,
-                                 InputIterator last,
-                                 RandomAccessIterator result_first,
-                                 RandomAccessIterator result_last,
-                                 Comp comp = {},
-                                 Proj1 proj1 = {},
-                                 Proj2 proj2 = {}) {
-  // Needs to opt-in to all permutations, since std::partial_sort_copy expects
-  // comp(proj2(lhs), proj1(rhs)) to compile.
-  return std::partial_sort_copy(
-      first, last, result_first, result_last,
-      internal::PermutedProjectedBinaryPredicate(comp, proj1, proj2));
-}
-
-// Let `N` be `min(size(range), size(result_range))`.
-//
-// Preconditions: For iterators `a1` and `b1` in `range`, and iterators
-// `x2` and `y2` in `result_range`, after evaluating the assignment
-// `*y2 = *b1`, let `E` be the value of
-// `bool(invoke(comp, invoke(proj1, *a1), invoke(proj2, *y2)))`. Then, after
-// evaluating the assignment `*x2 = *a1`, `E` is equal to
-// `bool(invoke(comp, invoke(proj2, *x2), invoke(proj2, *y2)))`.
-//
-// Effects: Places the first `N` elements as sorted with respect to `comp` and
-// `proj2` into the range `[begin(result_range), begin(result_range) + N)`.
-//
-// Returns: `begin(result_range) + N`.
-//
-// Complexity: Approximately `size(range) * log N` comparisons, and twice as
-// many projections.
-//
-// Reference:
-// https://wg21.link/partial.sort.copy#:~:text=ranges::partial_sort_copy(R1
-template <typename Range1,
-          typename Range2,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range2>, Proj2>,
-                                       projected<iterator_t<Range1>, Proj1>>>
-constexpr auto partial_sort_copy(Range1&& range,
-                                 Range2&& result_range,
-                                 Comp comp = {},
-                                 Proj1 proj1 = {},
-                                 Proj2 proj2 = {}) {
-  return ranges::partial_sort_copy(ranges::begin(range), ranges::end(range),
-                                   ranges::begin(result_range),
-                                   ranges::end(result_range), std::move(comp),
-                                   std::move(proj1), std::move(proj2));
-}
-
-// [is.sorted] is_sorted
-// Reference: https://wg21.link/is.sorted
-
-// Returns: The last iterator `i` in `[first, last]` for which the range
-// `[first, i)` is sorted with respect to `comp` and `proj`.
-//
-// Complexity: Linear.
-//
-// Reference: https://wg21.link/is.sorted#:~:text=ranges::is_sorted_until(I
-template <typename ForwardIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<ForwardIterator, Proj>,
-                                       projected<ForwardIterator, Proj>>>
-constexpr auto is_sorted_until(ForwardIterator first,
-                               ForwardIterator last,
-                               Comp comp = {},
-                               Proj proj = {}) {
-  // Implementation inspired by cppreference.com:
-  // https://en.cppreference.com/w/cpp/algorithm/is_sorted_until
-  //
-  // A reimplementation is required, because std::is_sorted_until is not
-  // constexpr prior to C++20. Once we have C++20, we should switch to standard
-  // library implementation.
-  if (first == last)
-    return last;
-
-  for (ForwardIterator next = first; ++next != last; ++first) {
-    if (gurl_base::invoke(comp, gurl_base::invoke(proj, *next),
-                     gurl_base::invoke(proj, *first))) {
-      return next;
-    }
-  }
-
-  return last;
-}
-
-// Returns: The last iterator `i` in `[begin(range), end(range)]` for which the
-// range `[begin(range), i)` is sorted with respect to `comp` and `proj`.
-//
-// Complexity: Linear.
-//
-// Reference: https://wg21.link/is.sorted#:~:text=ranges::is_sorted_until(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto is_sorted_until(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::is_sorted_until(ranges::begin(range), ranges::end(range),
-                                 std::move(comp), std::move(proj));
-}
-
-// Returns: Whether the range `[first, last)` is sorted with respect to `comp`
-// and `proj`.
-//
-// Complexity: Linear.
-//
-// Reference: https://wg21.link/is.sorted#:~:text=ranges::is_sorted(I
-template <typename ForwardIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<ForwardIterator, Proj>,
-                                       projected<ForwardIterator, Proj>>>
-constexpr auto is_sorted(ForwardIterator first,
-                         ForwardIterator last,
-                         Comp comp = {},
-                         Proj proj = {}) {
-  return ranges::is_sorted_until(first, last, std::move(comp),
-                                 std::move(proj)) == last;
-}
-
-// Returns: Whether `range` is sorted with respect to `comp` and `proj`.
-//
-// Complexity: Linear.
-//
-// Reference: https://wg21.link/is.sorted#:~:text=ranges::is_sorted(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto is_sorted(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::is_sorted(ranges::begin(range), ranges::end(range),
-                           std::move(comp), std::move(proj));
-}
-
-// [alg.nth.element] Nth element
-// Reference: https://wg21.link/alg.nth.element
-
-// Preconditions: `[first, nth)` and `[nth, last)` are valid ranges.
-//
-// Effects: After `nth_element` the element in the position pointed to by `nth`
-// is the element that would be in that position if the whole range were sorted
-// with respect to `comp` and `proj`, unless `nth == last`. Also for every
-// iterator `i` in the range `[first, nth)` and every iterator `j` in the range
-// `[nth, last)` it holds that:
-// `bool(invoke(comp, invoke(proj, *j), invoke(proj, *i)))` is false.
-//
-// Returns: `last`.
-//
-// Complexity: Linear on average.
-//
-// Reference: https://wg21.link/alg.nth.element#:~:text=ranges::nth_element(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto nth_element(RandomAccessIterator first,
-                           RandomAccessIterator nth,
-                           RandomAccessIterator last,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  std::nth_element(first, nth, last,
-                   internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Preconditions: `[begin(range), nth)` and `[nth, end(range))` are valid
-// ranges.
-//
-// Effects: After `nth_element` the element in the position pointed to by `nth`
-// is the element that would be in that position if the whole range were sorted
-// with respect to `comp` and `proj`, unless `nth == end(range)`. Also for every
-// iterator `i` in the range `[begin(range), nth)` and every iterator `j` in the
-// range `[nth, end(range))` it holds that:
-// `bool(invoke(comp, invoke(proj, *j), invoke(proj, *i)))` is false.
-//
-// Returns: `end(range)`.
-//
-// Complexity: Linear on average.
-//
-// Reference: https://wg21.link/alg.nth.element#:~:text=ranges::nth_element(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto nth_element(Range&& range,
-                           iterator_t<Range> nth,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  return ranges::nth_element(ranges::begin(range), nth, ranges::end(range),
-                             std::move(comp), std::move(proj));
-}
-
-// [alg.binary.search] Binary search
-// Reference: https://wg21.link/alg.binary.search
-
-// [lower.bound] lower_bound
-// Reference: https://wg21.link/lower.bound
-
-// Preconditions: The elements `e` of `[first, last)` are partitioned with
-// respect to the expression `bool(invoke(comp, invoke(proj, e), value))`.
-//
-// Returns: The furthermost iterator `i` in the range `[first, last]` such that
-// for every iterator `j` in the range `[first, i)`,
-// `bool(invoke(comp, invoke(proj, *j), value))` is true.
-//
-// Complexity: At most `log_2(last - first) + O(1)` comparisons and projections.
-//
-// Reference: https://wg21.link/lower.bound#:~:text=ranges::lower_bound(I
-template <typename ForwardIterator,
-          typename T,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto lower_bound(ForwardIterator first,
-                           ForwardIterator last,
-                           const T& value,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  // The second arg is guaranteed to be `value`, so we'll simply apply the
-  // identity projection.
-  identity value_proj;
-  return std::lower_bound(
-      first, last, value,
-      internal::ProjectedBinaryPredicate(comp, proj, value_proj));
-}
-
-// Preconditions: The elements `e` of `range` are partitioned with respect to
-// the expression `bool(invoke(comp, invoke(proj, e), value))`.
-//
-// Returns: The furthermost iterator `i` in the range
-// `[begin(range), end(range)]` such that for every iterator `j` in the range
-// `[begin(range), i)`, `bool(invoke(comp, invoke(proj, *j), value))` is true.
-//
-// Complexity: At most `log_2(size(range)) + O(1)` comparisons and projections.
-//
-// Reference: https://wg21.link/lower.bound#:~:text=ranges::lower_bound(R
-template <typename Range,
-          typename T,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto lower_bound(Range&& range,
-                           const T& value,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  return ranges::lower_bound(ranges::begin(range), ranges::end(range), value,
-                             std::move(comp), std::move(proj));
-}
-
-// [upper.bound] upper_bound
-// Reference: https://wg21.link/upper.bound
-
-// Preconditions: The elements `e` of `[first, last)` are partitioned with
-// respect to the expression `!bool(invoke(comp, value, invoke(proj, e)))`.
-//
-// Returns: The furthermost iterator `i` in the range `[first, last]` such that
-// for every iterator `j` in the range `[first, i)`,
-// `!bool(invoke(comp, value, invoke(proj, *j)))` is true.
-//
-// Complexity: At most `log_2(last - first) + O(1)` comparisons and projections.
-//
-// Reference: https://wg21.link/upper.bound#:~:text=ranges::upper_bound(I
-template <typename ForwardIterator,
-          typename T,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto upper_bound(ForwardIterator first,
-                           ForwardIterator last,
-                           const T& value,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  // The first arg is guaranteed to be `value`, so we'll simply apply the
-  // identity projection.
-  identity value_proj;
-  return std::upper_bound(
-      first, last, value,
-      internal::ProjectedBinaryPredicate(comp, value_proj, proj));
-}
-
-// Preconditions: The elements `e` of `range` are partitioned with
-// respect to the expression `!bool(invoke(comp, value, invoke(proj, e)))`.
-//
-// Returns: The furthermost iterator `i` in the range
-// `[begin(range), end(range)]` such that for every iterator `j` in the range
-// `[begin(range), i)`, `!bool(invoke(comp, value, invoke(proj, *j)))` is true.
-//
-// Complexity: At most `log_2(size(range)) + O(1)` comparisons and projections.
-//
-// Reference: https://wg21.link/upper.bound#:~:text=ranges::upper_bound(R
-template <typename Range,
-          typename T,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto upper_bound(Range&& range,
-                           const T& value,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  return ranges::upper_bound(ranges::begin(range), ranges::end(range), value,
-                             std::move(comp), std::move(proj));
-}
-
-// [equal.range] equal_range
-// Reference: https://wg21.link/equal.range
-
-// Preconditions: The elements `e` of `[first, last)` are partitioned with
-// respect to the expressions `bool(invoke(comp, invoke(proj, e), value))` and
-// `!bool(invoke(comp, value, invoke(proj, e)))`.
-//
-// Returns: `{ranges::lower_bound(first, last, value, comp, proj),
-//            ranges::upper_bound(first, last, value, comp, proj)}`.
-//
-// Complexity: At most 2 ∗ log_2(last - first) + O(1) comparisons and
-// projections.
-//
-// Reference: https://wg21.link/equal.range#:~:text=ranges::equal_range(I
-template <typename ForwardIterator,
-          typename T,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto equal_range(ForwardIterator first,
-                           ForwardIterator last,
-                           const T& value,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  // Note: This does not dispatch to std::equal_range, as otherwise it would not
-  // be possible to prevent applying `proj` to `value`, which can result in
-  // unintended behavior.
-  return std::make_pair(ranges::lower_bound(first, last, value, comp, proj),
-                        ranges::upper_bound(first, last, value, comp, proj));
-}
-
-// Preconditions: The elements `e` of `range` are partitioned with
-// respect to the expressions `bool(invoke(comp, invoke(proj, e), value))` and
-// `!bool(invoke(comp, value, invoke(proj, e)))`.
-//
-// Returns: `{ranges::lower_bound(range, value, comp, proj),
-//            ranges::upper_bound(range, value, comp, proj)}`.
-//
-// Complexity: At most 2 ∗ log_2(size(range)) + O(1) comparisons and
-// projections.
-//
-// Reference: https://wg21.link/equal.range#:~:text=ranges::equal_range(R
-template <typename Range,
-          typename T,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto equal_range(Range&& range,
-                           const T& value,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  return ranges::equal_range(ranges::begin(range), ranges::end(range), value,
-                             std::move(comp), std::move(proj));
-}
-
-// [binary.search] binary_search
-// Reference: https://wg21.link/binary.search
-
-// Preconditions: The elements `e` of `[first, last)` are partitioned with
-// respect to the expressions `bool(invoke(comp, invoke(proj, e), value))` and
-// `!bool(invoke(comp, value, invoke(proj, e)))`.
-//
-// Returns: `true` if and only if for some iterator `i` in the range
-// `[first, last)`, `!bool(invoke(comp, invoke(proj, *i), value)) &&
-//                   !bool(invoke(comp, value, invoke(proj, *i)))` is true.
-//
-// Complexity: At most `log_2(last - first) + O(1)` comparisons and projections.
-//
-// Reference: https://wg21.link/binary.search#:~:text=ranges::binary_search(I
-template <typename ForwardIterator,
-          typename T,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto binary_search(ForwardIterator first,
-                             ForwardIterator last,
-                             const T& value,
-                             Comp comp = {},
-                             Proj proj = {}) {
-  first = ranges::lower_bound(first, last, value, comp, proj);
-  return first != last &&
-         !gurl_base::invoke(comp, value, gurl_base::invoke(proj, *first));
-}
-
-// Preconditions: The elements `e` of `range` are partitioned with
-// respect to the expressions `bool(invoke(comp, invoke(proj, e), value))` and
-// `!bool(invoke(comp, value, invoke(proj, e)))`.
-//
-// Returns: `true` if and only if for some iterator `i` in `range`
-// `!bool(invoke(comp, invoke(proj, *i), value)) &&
-//  !bool(invoke(comp, value, invoke(proj, *i)))` is true.
-//
-// Complexity: At most `log_2(size(range)) + O(1)` comparisons and projections.
-//
-// Reference: https://wg21.link/binary.search#:~:text=ranges::binary_search(R
-template <typename Range,
-          typename T,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto binary_search(Range&& range,
-                             const T& value,
-                             Comp comp = {},
-                             Proj proj = {}) {
-  return ranges::binary_search(ranges::begin(range), ranges::end(range), value,
-                               std::move(comp), std::move(proj));
-}
-
-// [alg.partitions] Partitions
-// Reference: https://wg21.link/alg.partitions
-
-// Returns: `true` if and only if the elements `e` of `[first, last)` are
-// partitioned with respect to the expression
-// `bool(invoke(pred, invoke(proj, e)))`.
-//
-// Complexity: Linear. At most `last - first` applications of `pred` and `proj`.
-//
-// Reference: https://wg21.link/alg.partitions#:~:text=ranges::is_partitioned(I
-template <typename ForwardIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto is_partitioned(ForwardIterator first,
-                              ForwardIterator last,
-                              Pred pred,
-                              Proj proj = {}) {
-  return std::is_partitioned(first, last,
-                             internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Returns: `true` if and only if the elements `e` of `range` are partitioned
-// with respect to the expression `bool(invoke(pred, invoke(proj, e)))`.
-//
-// Complexity: Linear. At most `size(range)` applications of `pred` and `proj`.
-//
-// Reference: https://wg21.link/alg.partitions#:~:text=ranges::is_partitioned(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto is_partitioned(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::is_partitioned(ranges::begin(range), ranges::end(range),
-                                std::move(pred), std::move(proj));
-}
-
-// Let `E(x)` be `bool(invoke(pred, invoke(proj, x)))`.
-//
-// Effects: Places all the elements `e` in `[first, last)` that satisfy `E(e)`
-// before all the elements that do not.
-//
-// Returns: Let `i` be an iterator such that `E(*j)` is `true` for every
-// iterator `j` in `[first, i)` and `false` for every iterator `j` in
-// `[i, last)`. Returns: i.
-//
-// Complexity: Let `N = last - first`:
-// Exactly `N` applications of the predicate and projection. At most `N / 2`
-// swaps if the type of `first` models `bidirectional_iterator`, and at most `N`
-// swaps otherwise.
-//
-// Reference: https://wg21.link/alg.partitions#:~:text=ranges::partition(I
-template <typename ForwardIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto partition(ForwardIterator first,
-                         ForwardIterator last,
-                         Pred pred,
-                         Proj proj = {}) {
-  return std::partition(first, last,
-                        internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(x)` be `bool(invoke(pred, invoke(proj, x)))`.
-//
-// Effects: Places all the elements `e` in `range` that satisfy `E(e)` before
-// all the elements that do not.
-//
-// Returns: Let `i` be an iterator such that `E(*j)` is `true` for every
-// iterator `j` in `[begin(range), i)` and `false` for every iterator `j` in
-// `[i, last)`. Returns: i.
-//
-// Complexity: Let `N = size(range)`:
-// Exactly `N` applications of the predicate and projection. At most `N / 2`
-// swaps if the type of `first` models `bidirectional_iterator`, and at most `N`
-// swaps otherwise.
-//
-// Reference: https://wg21.link/alg.partitions#:~:text=ranges::partition(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto partition(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::partition(ranges::begin(range), ranges::end(range),
-                           std::move(pred), std::move(proj));
-}
-
-// Let `E(x)` be `bool(invoke(pred, invoke(proj, x)))`.
-//
-// Effects: Places all the elements `e` in `[first, last)` that satisfy `E(e)`
-// before all the elements that do not. The relative order of the elements in
-// both groups is preserved.
-//
-// Returns: Let `i` be an iterator such that for every iterator `j` in
-// `[first, i)`, `E(*j)` is `true`, and for every iterator `j` in the range
-// `[i, last)`, `E(*j)` is `false`. Returns: `i`.
-//
-// Complexity: Let `N = last - first`:
-// At most `N log N` swaps, but only `O(N)` swaps if there is enough extra
-// memory. Exactly `N` applications of the predicate and projection.
-//
-// Reference:
-// https://wg21.link/alg.partitions#:~:text=ranges::stable_partition(I
-template <typename BidirectionalIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<BidirectionalIterator>>
-constexpr auto stable_partition(BidirectionalIterator first,
-                                BidirectionalIterator last,
-                                Pred pred,
-                                Proj proj = {}) {
-  return std::stable_partition(first, last,
-                               internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(x)` be `bool(invoke(pred, invoke(proj, x)))`.
-//
-// Effects: Places all the elements `e` in `range` that satisfy `E(e)` before
-// all the elements that do not. The relative order of the elements in both
-// groups is preserved.
-//
-// Returns: Let `i` be an iterator such that for every iterator `j` in
-// `[begin(range), i)`, `E(*j)` is `true`, and for every iterator `j` in the
-// range `[i, end(range))`, `E(*j)` is `false`. Returns: `i`.
-//
-// Complexity: Let `N = size(range)`:
-// At most `N log N` swaps, but only `O(N)` swaps if there is enough extra
-// memory. Exactly `N` applications of the predicate and projection.
-//
-// Reference:
-// https://wg21.link/alg.partitions#:~:text=ranges::stable_partition(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto stable_partition(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::stable_partition(ranges::begin(range), ranges::end(range),
-                                  std::move(pred), std::move(proj));
-}
-
-// Let `E(x)` be `bool(invoke(pred, invoke(proj, x)))`.
-//
-// Mandates: The expression `*first` is writable to `out_true` and `out_false`.
-//
-// Preconditions: The input range and output ranges do not overlap.
-//
-// Effects: For each iterator `i` in `[first, last)`, copies `*i` to the output
-// range beginning with `out_true` if `E(*i)` is `true`, or to the output range
-// beginning with `out_false` otherwise.
-//
-// Returns: Let `o1` be the end of the output range beginning at `out_true`, and
-// `o2` the end of the output range beginning at `out_false`.
-// Returns `{o1, o2}`.
-//
-// Complexity: Exactly `last - first` applications of `pred` and `proj`.
-//
-// Reference: https://wg21.link/alg.partitions#:~:text=ranges::partition_copy(I
-template <typename InputIterator,
-          typename OutputIterator1,
-          typename OutputIterator2,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<InputIterator>,
-          typename = internal::iterator_category_t<OutputIterator1>,
-          typename = internal::iterator_category_t<OutputIterator2>>
-constexpr auto partition_copy(InputIterator first,
-                              InputIterator last,
-                              OutputIterator1 out_true,
-                              OutputIterator2 out_false,
-                              Pred pred,
-                              Proj proj = {}) {
-  return std::partition_copy(first, last, out_true, out_false,
-                             internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// Let `E(x)` be `bool(invoke(pred, invoke(proj, x)))`.
-//
-// Mandates: The expression `*begin(range)` is writable to `out_true` and
-// `out_false`.
-//
-// Preconditions: The input range and output ranges do not overlap.
-//
-// Effects: For each iterator `i` in `range`, copies `*i` to the output range
-// beginning with `out_true` if `E(*i)` is `true`, or to the output range
-// beginning with `out_false` otherwise.
-//
-// Returns: Let `o1` be the end of the output range beginning at `out_true`, and
-// `o2` the end of the output range beginning at `out_false`.
-// Returns `{o1, o2}`.
-//
-// Complexity: Exactly `size(range)` applications of `pred` and `proj`.
-//
-// Reference: https://wg21.link/alg.partitions#:~:text=ranges::partition_copy(R
-template <typename Range,
-          typename OutputIterator1,
-          typename OutputIterator2,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = internal::iterator_category_t<OutputIterator1>,
-          typename = internal::iterator_category_t<OutputIterator2>>
-constexpr auto partition_copy(Range&& range,
-                              OutputIterator1 out_true,
-                              OutputIterator2 out_false,
-                              Pred pred,
-                              Proj proj = {}) {
-  return ranges::partition_copy(ranges::begin(range), ranges::end(range),
-                                out_true, out_false, std::move(pred),
-                                std::move(proj));
-}
-
-// let `E(x)` be `bool(invoke(pred, invoke(proj, x)))`.
-//
-// Preconditions: The elements `e` of `[first, last)` are partitioned with
-// respect to `E(e)`.
-//
-// Returns: An iterator `mid` such that `E(*i)` is `true` for all iterators `i`
-// in `[first, mid)`, and `false` for all iterators `i` in `[mid, last)`.
-//
-// Complexity: `O(log(last - first))` applications of `pred` and `proj`.
-//
-// Reference: https://wg21.link/alg.partitions#:~:text=ranges::partition_point(I
-template <typename ForwardIterator,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>>
-constexpr auto partition_point(ForwardIterator first,
-                               ForwardIterator last,
-                               Pred pred,
-                               Proj proj = {}) {
-  return std::partition_point(first, last,
-                              internal::ProjectedUnaryPredicate(pred, proj));
-}
-
-// let `E(x)` be `bool(invoke(pred, invoke(proj, x)))`.
-//
-// Preconditions: The elements `e` of `range` are partitioned with respect to
-// `E(e)`.
-//
-// Returns: An iterator `mid` such that `E(*i)` is `true` for all iterators `i`
-// in `[begin(range), mid)`, and `false` for all iterators `i` in
-// `[mid, end(range))`.
-//
-// Complexity: `O(log(size(range)))` applications of `pred` and `proj`.
-//
-// Reference: https://wg21.link/alg.partitions#:~:text=ranges::partition_point(R
-template <typename Range,
-          typename Pred,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto partition_point(Range&& range, Pred pred, Proj proj = {}) {
-  return ranges::partition_point(ranges::begin(range), ranges::end(range),
-                                 std::move(pred), std::move(proj));
-}
-
-// [alg.merge] Merge
-// Reference: https://wg21.link/alg.merge
-
-// Let `N` be `(last1 - first1) + (last2 - first2)`.
-//
-// Preconditions: The ranges `[first1, last1)` and `[first2, last2)` are sorted
-// with respect to `comp` and `proj1` or `proj2`, respectively. The resulting
-// range does not overlap with either of the original ranges.
-//
-// Effects: Copies all the elements of the two ranges `[first1, last1)` and
-// `[first2, last2)` into the range `[result, result_last)`, where `result_last`
-// is `result + N`. If an element `a` precedes `b` in an input range, `a` is
-// copied into the output range before `b`. If `e1` is an element of
-// `[first1, last1)` and `e2` of `[first2, last2)`, `e2` is copied into the
-// output range before `e1` if and only if
-// `bool(invoke(comp, invoke(proj2, e2), invoke(proj1, e1)))` is `true`.
-//
-// Returns: `result_last`.
-//
-// Complexity: At most `N - 1` comparisons and applications of each projection.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.merge#:~:text=ranges::merge(I1
-template <typename InputIterator1,
-          typename InputIterator2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<InputIterator1>,
-          typename = internal::iterator_category_t<InputIterator2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator1, Proj1>,
-                                       projected<InputIterator2, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator2, Proj2>,
-                                       projected<InputIterator1, Proj1>>>
-constexpr auto merge(InputIterator1 first1,
-                     InputIterator1 last1,
-                     InputIterator2 first2,
-                     InputIterator2 last2,
-                     OutputIterator result,
-                     Comp comp = {},
-                     Proj1 proj1 = {},
-                     Proj2 proj2 = {}) {
-  // Needs to opt-in to all permutations, since std::merge expects
-  // comp(proj2(lhs), proj1(rhs)) to compile.
-  return std::merge(
-      first1, last1, first2, last2, result,
-      internal::PermutedProjectedBinaryPredicate(comp, proj1, proj2));
-}
-
-// Let `N` be `size(range1) + size(range2)`.
-//
-// Preconditions: The ranges `range1` and `range2` are sorted with respect to
-// `comp` and `proj1` or `proj2`, respectively. The resulting range does not
-// overlap with either of the original ranges.
-//
-// Effects: Copies all the elements of the two ranges `range1` and `range2` into
-// the range `[result, result_last)`, where `result_last` is `result + N`. If an
-// element `a` precedes `b` in an input range, `a` is copied into the output
-// range before `b`. If `e1` is an element of `range1` and `e2` of `range2`,
-// `e2` is copied into the output range before `e1` if and only if
-// `bool(invoke(comp, invoke(proj2, e2), invoke(proj1, e1)))` is `true`.
-//
-// Returns: `result_last`.
-//
-// Complexity: At most `N - 1` comparisons and applications of each projection.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.merge#:~:text=ranges::merge(R1
-template <typename Range1,
-          typename Range2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range2>, Proj2>,
-                                       projected<iterator_t<Range1>, Proj1>>>
-constexpr auto merge(Range1&& range1,
-                     Range2&& range2,
-                     OutputIterator result,
-                     Comp comp = {},
-                     Proj1 proj1 = {},
-                     Proj2 proj2 = {}) {
-  return ranges::merge(ranges::begin(range1), ranges::end(range1),
-                       ranges::begin(range2), ranges::end(range2), result,
-                       std::move(comp), std::move(proj1), std::move(proj2));
-}
-
-// Preconditions: `[first, middle)` and `[middle, last)` are valid ranges sorted
-// with respect to `comp` and `proj`.
-//
-// Effects: Merges two sorted consecutive ranges `[first, middle)` and
-// `[middle, last)`, putting the result of the merge into the range
-// `[first, last)`. The resulting range is sorted with respect to `comp` and
-// `proj`.
-//
-// Returns: `last`.
-//
-// Complexity: Let `N = last - first`: If enough additional memory is available,
-// exactly `N - 1` comparisons. Otherwise, `O(N log N)` comparisons. In either
-// case, twice as many projections as comparisons.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.merge#:~:text=ranges::inplace_merge(I
-template <typename BidirectionalIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<BidirectionalIterator>>
-constexpr auto inplace_merge(BidirectionalIterator first,
-                             BidirectionalIterator middle,
-                             BidirectionalIterator last,
-                             Comp comp = {},
-                             Proj proj = {}) {
-  std::inplace_merge(first, middle, last,
-                     internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Preconditions: `[begin(range), middle)` and `[middle, end(range))` are valid
-// ranges sorted with respect to `comp` and `proj`.
-//
-// Effects: Merges two sorted consecutive ranges `[begin(range), middle)` and
-// `[middle, end(range))`, putting the result of the merge into `range`. The
-// resulting range is sorted with respect to `comp` and `proj`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: Let `N = size(range)`: If enough additional memory is available,
-// exactly `N - 1` comparisons. Otherwise, `O(N log N)` comparisons. In either
-// case, twice as many projections as comparisons.
-//
-// Remarks: Stable.
-//
-// Reference: https://wg21.link/alg.merge#:~:text=ranges::inplace_merge(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto inplace_merge(Range&& range,
-                             iterator_t<Range> middle,
-                             Comp comp = {},
-                             Proj proj = {}) {
-  return ranges::inplace_merge(ranges::begin(range), middle, ranges::end(range),
-                               std::move(comp), std::move(proj));
-}
-
-// [alg.set.operations] Set operations on sorted structures
-// Reference: https://wg21.link/alg.set.operations
-
-// [includes] includes
-// Reference: https://wg21.link/includes
-
-// Preconditions: The ranges `[first1, last1)` and `[first2, last2)` are sorted
-// with respect to `comp` and `proj1` or `proj2`, respectively.
-//
-// Returns: `true` if and only if `[first2, last2)` is a subsequence of
-// `[first1, last1)`.
-//
-// Complexity: At most `2 * ((last1 - first1) + (last2 - first2)) - 1`
-// comparisons and applications of each projection.
-//
-// Reference: https://wg21.link/includes#:~:text=ranges::includes(I1
-template <typename InputIterator1,
-          typename InputIterator2,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<InputIterator1>,
-          typename = internal::iterator_category_t<InputIterator2>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator1, Proj1>,
-                                       projected<InputIterator2, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator2, Proj2>,
-                                       projected<InputIterator1, Proj1>>>
-constexpr auto includes(InputIterator1 first1,
-                        InputIterator1 last1,
-                        InputIterator2 first2,
-                        InputIterator2 last2,
-                        Comp comp = {},
-                        Proj1 proj1 = {},
-                        Proj2 proj2 = {}) {
-  GURL_DCHECK(ranges::is_sorted(first1, last1, comp, proj1));
-  GURL_DCHECK(ranges::is_sorted(first2, last2, comp, proj2));
-  // Needs to opt-in to all permutations, since std::includes expects
-  // comp(proj1(lhs), proj2(rhs)) and comp(proj2(lhs), proj1(rhs)) to compile.
-  return std::includes(
-      first1, last1, first2, last2,
-      internal::PermutedProjectedBinaryPredicate(comp, proj1, proj2));
-}
-
-// Preconditions: The ranges `range1` and `range2` are sorted with respect to
-// `comp` and `proj1` or `proj2`, respectively.
-//
-// Returns: `true` if and only if `range2` is a subsequence of `range1`.
-//
-// Complexity: At most `2 * (size(range1) + size(range2)) - 1` comparisons and
-// applications of each projection.
-//
-// Reference: https://wg21.link/includes#:~:text=ranges::includes(R1
-template <typename Range1,
-          typename Range2,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range2>, Proj2>,
-                                       projected<iterator_t<Range1>, Proj1>>>
-constexpr auto includes(Range1&& range1,
-                        Range2&& range2,
-                        Comp comp = {},
-                        Proj1 proj1 = {},
-                        Proj2 proj2 = {}) {
-  return ranges::includes(ranges::begin(range1), ranges::end(range1),
-                          ranges::begin(range2), ranges::end(range2),
-                          std::move(comp), std::move(proj1), std::move(proj2));
-}
-
-// [set.union] set_union
-// Reference: https://wg21.link/set.union
-
-// Preconditions: The ranges `[first1, last1)` and `[first2, last2)` are sorted
-// with respect to `comp` and `proj1` or `proj2`, respectively. The resulting
-// range does not overlap with either of the original ranges.
-//
-// Effects: Constructs a sorted union of the elements from the two ranges; that
-// is, the set of elements that are present in one or both of the ranges.
-//
-// Returns: The end of the constructed range.
-//
-// Complexity: At most `2 * ((last1 - first1) + (last2 - first2)) - 1`
-// comparisons and applications of each projection.
-//
-// Remarks: Stable. If `[first1, last1)` contains `m` elements that are
-// equivalent to each other and `[first2, last2)` contains `n` elements that are
-// equivalent to them, then all `m` elements from the first range are copied to
-// the output range, in order, and then the final `max(n - m , 0)` elements from
-// the second range are copied to the output range, in order.
-//
-// Reference: https://wg21.link/set.union#:~:text=ranges::set_union(I1
-template <typename InputIterator1,
-          typename InputIterator2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<InputIterator1>,
-          typename = internal::iterator_category_t<InputIterator2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator1, Proj1>,
-                                       projected<InputIterator2, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator2, Proj2>,
-                                       projected<InputIterator1, Proj1>>>
-constexpr auto set_union(InputIterator1 first1,
-                         InputIterator1 last1,
-                         InputIterator2 first2,
-                         InputIterator2 last2,
-                         OutputIterator result,
-                         Comp comp = {},
-                         Proj1 proj1 = {},
-                         Proj2 proj2 = {}) {
-  // Needs to opt-in to all permutations, since std::set_union expects
-  // comp(proj1(lhs), proj2(rhs)) and comp(proj2(lhs), proj1(rhs)) to compile.
-  return std::set_union(
-      first1, last1, first2, last2, result,
-      internal::PermutedProjectedBinaryPredicate(comp, proj1, proj2));
-}
-
-// Preconditions: The ranges `range1` and `range2` are sorted with respect to
-// `comp` and `proj1` or `proj2`, respectively. The resulting range does not
-// overlap with either of the original ranges.
-//
-// Effects: Constructs a sorted union of the elements from the two ranges; that
-// is, the set of elements that are present in one or both of the ranges.
-//
-// Returns: The end of the constructed range.
-//
-// Complexity: At most `2 * (size(range1) + size(range2)) - 1` comparisons and
-// applications of each projection.
-//
-// Remarks: Stable. If `range1` contains `m` elements that are equivalent to
-// each other and `range2` contains `n` elements that are equivalent to them,
-// then all `m` elements from the first range are copied to the output range, in
-// order, and then the final `max(n - m , 0)` elements from the second range are
-// copied to the output range, in order.
-//
-// Reference: https://wg21.link/set.union#:~:text=ranges::set_union(R1
-template <typename Range1,
-          typename Range2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range2>, Proj2>,
-                                       projected<iterator_t<Range1>, Proj1>>>
-constexpr auto set_union(Range1&& range1,
-                         Range2&& range2,
-                         OutputIterator result,
-                         Comp comp = {},
-                         Proj1 proj1 = {},
-                         Proj2 proj2 = {}) {
-  return ranges::set_union(ranges::begin(range1), ranges::end(range1),
-                           ranges::begin(range2), ranges::end(range2), result,
-                           std::move(comp), std::move(proj1), std::move(proj2));
-}
-
-// [set.intersection] set_intersection
-// Reference: https://wg21.link/set.intersection
-
-// Preconditions: The ranges `[first1, last1)` and `[first2, last2)` are sorted
-// with respect to `comp` and `proj1` or `proj2`, respectively. The resulting
-// range does not overlap with either of the original ranges.
-//
-// Effects: Constructs a sorted intersection of the elements from the two
-// ranges; that is, the set of elements that are present in both of the ranges.
-//
-// Returns: The end of the constructed range.
-//
-// Complexity: At most `2 * ((last1 - first1) + (last2 - first2)) - 1`
-// comparisons and applications of each projection.
-//
-// Remarks: Stable. If `[first1, last1)` contains `m` elements that are
-// equivalent to each other and `[first2, last2)` contains `n` elements that are
-// equivalent to them, the first `min(m, n)` elements are copied from the first
-// range to the output range, in order.
-//
-// Reference:
-// https://wg21.link/set.intersection#:~:text=ranges::set_intersection(I1
-template <typename InputIterator1,
-          typename InputIterator2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<InputIterator1>,
-          typename = internal::iterator_category_t<InputIterator2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator1, Proj1>,
-                                       projected<InputIterator2, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator2, Proj2>,
-                                       projected<InputIterator1, Proj1>>>
-constexpr auto set_intersection(InputIterator1 first1,
-                                InputIterator1 last1,
-                                InputIterator2 first2,
-                                InputIterator2 last2,
-                                OutputIterator result,
-                                Comp comp = {},
-                                Proj1 proj1 = {},
-                                Proj2 proj2 = {}) {
-  // Needs to opt-in to all permutations, since std::set_intersection expects
-  // comp(proj1(lhs), proj2(rhs)) and comp(proj2(lhs), proj1(rhs)) to compile.
-  return std::set_intersection(
-      first1, last1, first2, last2, result,
-      internal::PermutedProjectedBinaryPredicate(comp, proj1, proj2));
-}
-
-// Preconditions: The ranges `range1` and `range2` are sorted with respect to
-// `comp` and `proj1` or `proj2`, respectively. The resulting range does not
-// overlap with either of the original ranges.
-//
-// Effects: Constructs a sorted intersection of the elements from the two
-// ranges; that is, the set of elements that are present in both of the ranges.
-//
-// Returns: The end of the constructed range.
-//
-// Complexity: At most `2 * (size(range1) + size(range2)) - 1` comparisons and
-// applications of each projection.
-//
-// Remarks: Stable. If `range1` contains `m` elements that are equivalent to
-// each other and `range2` contains `n` elements that are equivalent to them,
-// the first `min(m, n)` elements are copied from the first range to the output
-// range, in order.
-//
-// Reference:
-// https://wg21.link/set.intersection#:~:text=ranges::set_intersection(R1
-template <typename Range1,
-          typename Range2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range2>, Proj2>,
-                                       projected<iterator_t<Range1>, Proj1>>>
-constexpr auto set_intersection(Range1&& range1,
-                                Range2&& range2,
-                                OutputIterator result,
-                                Comp comp = {},
-                                Proj1 proj1 = {},
-                                Proj2 proj2 = {}) {
-  return ranges::set_intersection(ranges::begin(range1), ranges::end(range1),
-                                  ranges::begin(range2), ranges::end(range2),
-                                  result, std::move(comp), std::move(proj1),
-                                  std::move(proj2));
-}
-
-// [set.difference] set_difference
-// Reference: https://wg21.link/set.difference
-
-// Preconditions: The ranges `[first1, last1)` and `[first2, last2)` are sorted
-// with respect to `comp` and `proj1` or `proj2`, respectively. The resulting
-// range does not overlap with either of the original ranges.
-//
-// Effects: Copies the elements of the range `[first1, last1)` which are not
-// present in the range `[first2, last2)` to the range beginning at `result`.
-// The elements in the constructed range are sorted.
-//
-// Returns: The end of the constructed range.
-//
-// Complexity: At most `2 * ((last1 - first1) + (last2 - first2)) - 1`
-// comparisons and applications of each projection.
-//
-// Remarks: If `[first1, last1)` contains `m` elements that are equivalent to
-// each other and `[first2, last2)` contains `n` elements that are equivalent to
-// them, the last `max(m - n, 0)` elements from `[first1, last1)` are copied to
-// the output range, in order.
-//
-// Reference:
-// https://wg21.link/set.difference#:~:text=ranges::set_difference(I1
-template <typename InputIterator1,
-          typename InputIterator2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<InputIterator1>,
-          typename = internal::iterator_category_t<InputIterator2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator1, Proj1>,
-                                       projected<InputIterator2, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator2, Proj2>,
-                                       projected<InputIterator1, Proj1>>>
-constexpr auto set_difference(InputIterator1 first1,
-                              InputIterator1 last1,
-                              InputIterator2 first2,
-                              InputIterator2 last2,
-                              OutputIterator result,
-                              Comp comp = {},
-                              Proj1 proj1 = {},
-                              Proj2 proj2 = {}) {
-  // Needs to opt-in to all permutations, since std::set_difference expects
-  // comp(proj1(lhs), proj2(rhs)) and comp(proj2(lhs), proj1(rhs)) to compile.
-  return std::set_difference(
-      first1, last1, first2, last2, result,
-      internal::PermutedProjectedBinaryPredicate(comp, proj1, proj2));
-}
-
-// Preconditions: The ranges `range1` and `range2` are sorted with respect to
-// `comp` and `proj1` or `proj2`, respectively. The resulting range does not
-// overlap with either of the original ranges.
-//
-// Effects: Copies the elements of `range1` which are not present in `range2`
-// to the range beginning at `result`. The elements in the constructed range are
-// sorted.
-//
-// Returns: The end of the constructed range.
-//
-// Complexity: At most `2 * (size(range1) + size(range2)) - 1` comparisons and
-// applications of each projection.
-//
-// Remarks: Stable. If `range1` contains `m` elements that are equivalent to
-// each other and `range2` contains `n` elements that are equivalent to them,
-// the last `max(m - n, 0)` elements from `range1` are copied to the output
-// range, in order.
-//
-// Reference:
-// https://wg21.link/set.difference#:~:text=ranges::set_difference(R1
-template <typename Range1,
-          typename Range2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range2>, Proj2>,
-                                       projected<iterator_t<Range1>, Proj1>>>
-constexpr auto set_difference(Range1&& range1,
-                              Range2&& range2,
-                              OutputIterator result,
-                              Comp comp = {},
-                              Proj1 proj1 = {},
-                              Proj2 proj2 = {}) {
-  return ranges::set_difference(ranges::begin(range1), ranges::end(range1),
-                                ranges::begin(range2), ranges::end(range2),
-                                result, std::move(comp), std::move(proj1),
-                                std::move(proj2));
-}
-
-// [set.symmetric.difference] set_symmetric_difference
-// Reference: https://wg21.link/set.symmetric.difference
-
-// Preconditions: The ranges `[first1, last1)` and `[first2, last2)` are sorted
-// with respect to `comp` and `proj1` or `proj2`, respectively. The resulting
-// range does not overlap with either of the original ranges.
-//
-// Effects: Copies the elements of the range `[first1, last1)` that are not
-// present in the range `[first2, last2)`, and the elements of the range
-// `[first2, last2)` that are not present in the range `[first1, last1)` to the
-// range beginning at `result`. The elements in the constructed range are
-// sorted.
-//
-// Returns: The end of the constructed range.
-//
-// Complexity: At most `2 * ((last1 - first1) + (last2 - first2)) - 1`
-// comparisons and applications of each projection.
-//
-// Remarks: Stable. If `[first1, last1)` contains `m` elements that are
-// equivalent to each other and `[first2, last2)` contains `n` elements that are
-// equivalent to them, then `|m - n|` of those elements shall be copied to the
-// output range: the last `m - n` of these elements from `[first1, last1)` if
-// `m > n`, and the last `n - m` of these elements from `[first2, last2)` if
-// `m < n`. In either case, the elements are copied in order.
-//
-// Reference:
-// https://wg21.link/set.symmetric.difference#:~:text=set_symmetric_difference(I1
-template <typename InputIterator1,
-          typename InputIterator2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<InputIterator1>,
-          typename = internal::iterator_category_t<InputIterator2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator1, Proj1>,
-                                       projected<InputIterator2, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<InputIterator2, Proj2>,
-                                       projected<InputIterator1, Proj1>>>
-constexpr auto set_symmetric_difference(InputIterator1 first1,
-                                        InputIterator1 last1,
-                                        InputIterator2 first2,
-                                        InputIterator2 last2,
-                                        OutputIterator result,
-                                        Comp comp = {},
-                                        Proj1 proj1 = {},
-                                        Proj2 proj2 = {}) {
-  // Needs to opt-in to all permutations, since std::set_symmetric_difference
-  // expects comp(proj1(lhs), proj2(rhs)) and comp(proj2(lhs), proj1(rhs)) to
-  // compile.
-  return std::set_symmetric_difference(
-      first1, last1, first2, last2, result,
-      internal::PermutedProjectedBinaryPredicate(comp, proj1, proj2));
-}
-
-// Preconditions: The ranges `range1` and `range2` are sorted with respect to
-// `comp` and `proj1` or `proj2`, respectively. The resulting range does not
-// overlap with either of the original ranges.
-//
-// Effects: Copies the elements of `range1` that are not present in `range2`,
-// and the elements of `range2` that are not present in `range1` to the range
-// beginning at `result`. The elements in the constructed range are sorted.
-//
-// Returns: The end of the constructed range.
-//
-// Complexity: At most `2 * (size(range1) + size(range2)) - 1` comparisons and
-// applications of each projection.
-//
-// Remarks: Stable. If `range1` contains `m` elements that are equivalent to
-// each other and `range2` contains `n` elements that are equivalent to them,
-// then `|m - n|` of those elements shall be copied to the output range: the
-// last `m - n` of these elements from `range1` if `m > n`, and the last `n - m`
-// of these elements from `range2` if `m < n`. In either case, the elements are
-// copied in order.
-//
-// Reference:
-// https://wg21.link/set.symmetric.difference#:~:text=set_symmetric_difference(R1
-template <typename Range1,
-          typename Range2,
-          typename OutputIterator,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = internal::iterator_category_t<OutputIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range2>, Proj2>,
-                                       projected<iterator_t<Range1>, Proj1>>>
-constexpr auto set_symmetric_difference(Range1&& range1,
-                                        Range2&& range2,
-                                        OutputIterator result,
-                                        Comp comp = {},
-                                        Proj1 proj1 = {},
-                                        Proj2 proj2 = {}) {
-  return ranges::set_symmetric_difference(
-      ranges::begin(range1), ranges::end(range1), ranges::begin(range2),
-      ranges::end(range2), result, std::move(comp), std::move(proj1),
-      std::move(proj2));
-}
-
-// [alg.heap.operations] Heap operations
-// Reference: https://wg21.link/alg.heap.operations
-
-// [push.heap] push_heap
-// Reference: https://wg21.link/push.heap
-
-// Preconditions: The range `[first, last - 1)` is a valid heap with respect to
-// `comp` and `proj`.
-//
-// Effects: Places the value in the location `last - 1` into the resulting heap
-// `[first, last)`.
-//
-// Returns: `last`.
-//
-// Complexity: At most `log(last - first)` comparisons and twice as many
-// projections.
-//
-// Reference: https://wg21.link/push.heap#:~:text=ranges::push_heap(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto push_heap(RandomAccessIterator first,
-                         RandomAccessIterator last,
-                         Comp comp = {},
-                         Proj proj = {}) {
-  std::push_heap(first, last,
-                 internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Preconditions: The range `[begin(range), end(range) - 1)` is a valid heap
-// with respect to `comp` and `proj`.
-//
-// Effects: Places the value in the location `end(range) - 1` into the resulting
-// heap `range`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: At most `log(size(range))` comparisons and twice as many
-// projections.
-//
-// Reference: https://wg21.link/push.heap#:~:text=ranges::push_heap(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto push_heap(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::push_heap(ranges::begin(range), ranges::end(range),
-                           std::move(comp), std::move(proj));
-}
-
-// [pop.heap] pop_heap
-// Reference: https://wg21.link/pop.heap
-
-// Preconditions: The range `[first, last)` is a valid non-empty heap with
-// respect to `comp` and `proj`.
-//
-// Effects: Swaps the value in the location `first` with the value in the
-// location `last - 1` and makes `[first, last - 1)` into a heap with respect to
-// `comp` and `proj`.
-//
-// Returns: `last`.
-//
-// Complexity: At most `2 log(last - first)` comparisons and twice as many
-// projections.
-//
-// Reference: https://wg21.link/pop.heap#:~:text=ranges::pop_heap(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto pop_heap(RandomAccessIterator first,
-                        RandomAccessIterator last,
-                        Comp comp = {},
-                        Proj proj = {}) {
-  std::pop_heap(first, last,
-                internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Preconditions: `range` is a valid non-empty heap with respect to `comp` and
-// `proj`.
-//
-// Effects: Swaps the value in the location `begin(range)` with the value in the
-// location `end(range) - 1` and makes `[begin(range), end(range) - 1)` into a
-// heap with respect to `comp` and `proj`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: At most `2 log(size(range))` comparisons and twice as many
-// projections.
-//
-// Reference: https://wg21.link/pop.heap#:~:text=ranges::pop_heap(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto pop_heap(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::pop_heap(ranges::begin(range), ranges::end(range),
-                          std::move(comp), std::move(proj));
-}
-
-// [make.heap] make_heap
-// Reference: https://wg21.link/make.heap
-
-// Effects: Constructs a heap with respect to `comp` and `proj` out of the range
-// `[first, last)`.
-//
-// Returns: `last`.
-//
-// Complexity: At most `3 * (last - first)` comparisons and twice as many
-// projections.
-//
-// Reference: https://wg21.link/make.heap#:~:text=ranges::make_heap(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto make_heap(RandomAccessIterator first,
-                         RandomAccessIterator last,
-                         Comp comp = {},
-                         Proj proj = {}) {
-  std::make_heap(first, last,
-                 internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Effects: Constructs a heap with respect to `comp` and `proj` out of `range`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: At most `3 * size(range)` comparisons and twice as many
-// projections.
-//
-// Reference: https://wg21.link/make.heap#:~:text=ranges::make_heap(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto make_heap(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::make_heap(ranges::begin(range), ranges::end(range),
-                           std::move(comp), std::move(proj));
-}
-
-// [sort.heap] sort_heap
-// Reference: https://wg21.link/sort.heap
-
-// Preconditions: The range `[first, last)` is a valid heap with respect to
-// `comp` and `proj`.
-//
-// Effects: Sorts elements in the heap `[first, last)` with respect to `comp`
-// and `proj`.
-//
-// Returns: `last`.
-//
-// Complexity: At most `2 N log N` comparisons, where `N = last - first`, and
-// twice as many projections.
-//
-// Reference: https://wg21.link/sort.heap#:~:text=ranges::sort_heap(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto sort_heap(RandomAccessIterator first,
-                         RandomAccessIterator last,
-                         Comp comp = {},
-                         Proj proj = {}) {
-  std::sort_heap(first, last,
-                 internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return last;
-}
-
-// Preconditions: `range` is a valid heap with respect to `comp` and `proj`.
-//
-// Effects: Sorts elements in the heap `range` with respect to `comp` and
-// `proj`.
-//
-// Returns: `end(range)`.
-//
-// Complexity: At most `2 N log N` comparisons, where `N = size(range)`, and
-// twice as many projections.
-//
-// Reference: https://wg21.link/sort.heap#:~:text=ranges::sort_heap(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto sort_heap(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::sort_heap(ranges::begin(range), ranges::end(range),
-                           std::move(comp), std::move(proj));
-}
-
-// [is.heap] is_heap
-// Reference: https://wg21.link/is.heap
-
-// Returns: Whether the range `[first, last)` is a heap with respect to `comp`
-// and `proj`.
-//
-// Complexity: Linear.
-//
-// Reference: https://wg21.link/is.heap#:~:text=ranges::is_heap(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto is_heap(RandomAccessIterator first,
-                       RandomAccessIterator last,
-                       Comp comp = {},
-                       Proj proj = {}) {
-  return std::is_heap(first, last,
-                      internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Returns: Whether `range` is a heap with respect to `comp` and `proj`.
-//
-// Complexity: Linear.
-//
-// Reference: https://wg21.link/is.heap#:~:text=ranges::is_heap(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto is_heap(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::is_heap(ranges::begin(range), ranges::end(range),
-                         std::move(comp), std::move(proj));
-}
-
-// Returns: The last iterator `i` in `[first, last]` for which the range
-// `[first, i)` is a heap with respect to `comp` and `proj`.
-//
-// Complexity: Linear.
-//
-// Reference: https://wg21.link/is.heap#:~:text=ranges::is_heap_until(I
-template <typename RandomAccessIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<RandomAccessIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<RandomAccessIterator, Proj>,
-                                       projected<RandomAccessIterator, Proj>>>
-constexpr auto is_heap_until(RandomAccessIterator first,
-                             RandomAccessIterator last,
-                             Comp comp = {},
-                             Proj proj = {}) {
-  return std::is_heap_until(
-      first, last, internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Returns: The last iterator `i` in `[begin(range), end(range)]` for which the
-// range `[begin(range), i)` is a heap with respect to `comp` and `proj`.
-//
-// Complexity: Linear.
-//
-// Reference: https://wg21.link/is.heap#:~:text=ranges::is_heap_until(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto is_heap_until(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::is_heap_until(ranges::begin(range), ranges::end(range),
-                               std::move(comp), std::move(proj));
-}
-
-// [alg.min.max] Minimum and maximum
-// Reference: https://wg21.link/alg.min.max
-
-// Returns: The smaller value. Returns the first argument when the arguments are
-// equivalent.
-//
-// Complexity: Exactly one comparison and two applications of the projection, if
-// any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::min
-template <typename T, typename Comp = ranges::less, typename Proj = identity>
-constexpr const T& min(const T& a, const T& b, Comp comp = {}, Proj proj = {}) {
-  return gurl_base::invoke(comp, gurl_base::invoke(proj, b), gurl_base::invoke(proj, a)) ? b
-                                                                          : a;
-}
-
-// Preconditions: `!empty(ilist)`.
-//
-// Returns: The smallest value in the input range. Returns a copy of the
-// leftmost element when several elements are equivalent to the smallest.
-//
-// Complexity: Exactly `size(ilist) - 1` comparisons and twice as many
-// applications of the projection, if any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::min(initializer_list
-template <typename T, typename Comp = ranges::less, typename Proj = identity>
-constexpr T min(std::initializer_list<T> ilist,
-                Comp comp = {},
-                Proj proj = {}) {
-  return *std::min_element(
-      ilist.begin(), ilist.end(),
-      internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Preconditions: `!empty(range)`.
-//
-// Returns: The smallest value in the input range. Returns a copy of the
-// leftmost element when several elements are equivalent to the smallest.
-//
-// Complexity: Exactly `size(range) - 1` comparisons and twice as many
-// applications of the projection, if any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::min(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto min(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return *std::min_element(
-      ranges::begin(range), ranges::end(range),
-      internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Returns: The larger value. Returns the first argument when the arguments are
-// equivalent.
-//
-// Complexity: Exactly one comparison and two applications of the projection, if
-// any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::max
-template <typename T, typename Comp = ranges::less, typename Proj = identity>
-constexpr const T& max(const T& a, const T& b, Comp comp = {}, Proj proj = {}) {
-  return gurl_base::invoke(comp, gurl_base::invoke(proj, a), gurl_base::invoke(proj, b)) ? b
-                                                                          : a;
-}
-
-// Preconditions: `!empty(ilist)`.
-//
-// Returns: The largest value in the input range. Returns a copy of the leftmost
-// element when several elements are equivalent to the largest.
-//
-// Complexity: Exactly `size(ilist) - 1` comparisons and twice as many
-// applications of the projection, if any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::max(initializer_list
-template <typename T, typename Comp = ranges::less, typename Proj = identity>
-constexpr T max(std::initializer_list<T> ilist,
-                Comp comp = {},
-                Proj proj = {}) {
-  return *std::max_element(
-      ilist.begin(), ilist.end(),
-      internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Preconditions: `!empty(range)`.
-//
-// Returns: The largest value in the input range. Returns a copy of the leftmost
-// element when several elements are equivalent to the smallest.
-//
-// Complexity: Exactly `size(range) - 1` comparisons and twice as many
-// applications of the projection, if any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::max(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto max(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return *std::max_element(
-      ranges::begin(range), ranges::end(range),
-      internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Returns: `{b, a}` if `b` is smaller than `a`, and `{a, b}` otherwise.
-//
-// Complexity: Exactly one comparison and two applications of the projection, if
-// any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::minmax
-template <typename T, typename Comp = ranges::less, typename Proj = identity>
-constexpr auto minmax(const T& a, const T& b, Comp comp = {}, Proj proj = {}) {
-  return std::minmax(a, b,
-                     internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Preconditions: `!empty(ilist)`.
-//
-// Returns: Let `X` be the return type. Returns `X{x, y}`, where `x` is a copy
-// of the leftmost element with the smallest value and `y` a copy of the
-// rightmost element with the largest value in the input range.
-//
-// Complexity: At most `(3/2) size(ilist)` applications of the corresponding
-// predicate and twice as many applications of the projection, if any.
-//
-// Reference:
-// https://wg21.link/alg.min.max#:~:text=ranges::minmax(initializer_list
-template <typename T, typename Comp = ranges::less, typename Proj = identity>
-constexpr auto minmax(std::initializer_list<T> ilist,
-                      Comp comp = {},
-                      Proj proj = {}) {
-  auto it =
-      std::minmax_element(ranges::begin(ilist), ranges::end(ilist),
-                          internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return std::pair<T, T>{*it.first, *it.second};
-}
-
-// Preconditions: `!empty(range)`.
-//
-// Returns: Let `X` be the return type. Returns `X{x, y}`, where `x` is a copy
-// of the leftmost element with the smallest value and `y` a copy of the
-// rightmost element with the largest value in the input range.
-//
-// Complexity: At most `(3/2) size(range)` applications of the corresponding
-// predicate and twice as many applications of the projection, if any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::minmax(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>>
-constexpr auto minmax(Range&& range, Comp comp = {}, Proj proj = {}) {
-  using T = range_value_t<Range>;
-  auto it =
-      std::minmax_element(ranges::begin(range), ranges::end(range),
-                          internal::ProjectedBinaryPredicate(comp, proj, proj));
-  return std::pair<T, T>{*it.first, *it.second};
-}
-
-// Returns: The first iterator i in the range `[first, last)` such that for
-// every iterator `j` in the range `[first, last)`,
-// `bool(invoke(comp, invoke(proj, *j), invoke(proj, *i)))` is `false`. Returns
-// `last` if `first == last`.
-//
-// Complexity: Exactly `max(last - first - 1, 0)` comparisons and twice as
-// many projections.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::min_element(I
-template <typename ForwardIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<ForwardIterator, Proj>,
-                                       projected<ForwardIterator, Proj>>>
-constexpr auto min_element(ForwardIterator first,
-                           ForwardIterator last,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  return std::min_element(first, last,
-                          internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Returns: The first iterator i in `range` such that for every iterator `j` in
-// `range`, `bool(invoke(comp, invoke(proj, *j), invoke(proj, *i)))` is `false`.
-// Returns `end(range)` if `empty(range)`.
-//
-// Complexity: Exactly `max(size(range) - 1, 0)` comparisons and twice as many
-// projections.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::min_element(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto min_element(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::min_element(ranges::begin(range), ranges::end(range),
-                             std::move(comp), std::move(proj));
-}
-
-// Returns: The first iterator i in the range `[first, last)` such that for
-// every iterator `j` in the range `[first, last)`,
-// `bool(invoke(comp, invoke(proj, *i), invoke(proj, *j)))` is `false`.
-// Returns `last` if `first == last`.
-//
-// Complexity: Exactly `max(last - first - 1, 0)` comparisons and twice as
-// many projections.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::max_element(I
-template <typename ForwardIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<ForwardIterator, Proj>,
-                                       projected<ForwardIterator, Proj>>>
-constexpr auto max_element(ForwardIterator first,
-                           ForwardIterator last,
-                           Comp comp = {},
-                           Proj proj = {}) {
-  return std::max_element(first, last,
-                          internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Returns: The first iterator i in `range` such that for every iterator `j`
-// in `range`, `bool(invoke(comp, invoke(proj, *j), invoke(proj, *j)))` is
-// `false`. Returns `end(range)` if `empty(range)`.
-//
-// Complexity: Exactly `max(size(range) - 1, 0)` comparisons and twice as many
-// projections.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::max_element(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto max_element(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::max_element(ranges::begin(range), ranges::end(range),
-                             std::move(comp), std::move(proj));
-}
-
-// Returns: `{first, first}` if `[first, last)` is empty, otherwise `{m, M}`,
-// where `m` is the first iterator in `[first, last)` such that no iterator in
-// the range refers to a smaller element, and where `M` is the last iterator
-// in
-// `[first, last)` such that no iterator in the range refers to a larger
-// element.
-//
-// Complexity: Let `N` be `last - first`. At most `max(3/2 (N − 1), 0)`
-// comparisons and twice as many applications of the projection, if any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::minmax_element(I
-template <typename ForwardIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<ForwardIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<ForwardIterator, Proj>,
-                                       projected<ForwardIterator, Proj>>>
-constexpr auto minmax_element(ForwardIterator first,
-                              ForwardIterator last,
-                              Comp comp = {},
-                              Proj proj = {}) {
-  return std::minmax_element(
-      first, last, internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Returns: `{begin(range), begin(range)}` if `range` is empty, otherwise
-// `{m, M}`, where `m` is the first iterator in `range` such that no iterator
-// in the range refers to a smaller element, and where `M` is the last
-// iterator in `range` such that no iterator in the range refers to a larger
-// element.
-//
-// Complexity: Let `N` be `size(range)`. At most `max(3/2 (N − 1), 0)`
-// comparisons and twice as many applications of the projection, if any.
-//
-// Reference: https://wg21.link/alg.min.max#:~:text=ranges::minmax_element(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto minmax_element(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::minmax_element(ranges::begin(range), ranges::end(range),
-                                std::move(comp), std::move(proj));
-}
-
-// [alg.clamp] Bounded value
-// Reference: https://wg21.link/alg.clamp
-
-// Preconditions: `bool(invoke(comp, invoke(proj, hi), invoke(proj, lo)))` is
-// `false`.
-//
-// Returns: `lo` if `bool(invoke(comp, invoke(proj, v), invoke(proj, lo)))` is
-// `true`, `hi` if `bool(invoke(comp, invoke(proj, hi), invoke(proj, v)))` is
-// `true`, otherwise `v`.
-//
-// Complexity: At most two comparisons and three applications of the
-// projection.
-//
-// Reference: https://wg21.link/alg.clamp#:~:text=ranges::clamp
-template <typename T, typename Comp = ranges::less, typename Proj = identity>
-constexpr const T& clamp(const T& v,
-                         const T& lo,
-                         const T& hi,
-                         Comp comp = {},
-                         Proj proj = {}) {
-  auto&& projected_v = gurl_base::invoke(proj, v);
-  if (gurl_base::invoke(comp, projected_v, gurl_base::invoke(proj, lo)))
-    return lo;
-
-  return gurl_base::invoke(comp, gurl_base::invoke(proj, hi), projected_v) ? hi : v;
-}
-
-// [alg.lex.comparison] Lexicographical comparison
-// Reference: https://wg21.link/alg.lex.comparison
-
-// Returns: `true` if and only if the sequence of elements defined by the range
-// `[first1, last1)` is lexicographically less than the sequence of elements
-// defined by the range `[first2, last2)`.
-//
-// Complexity: At most `2 min(last1 - first1, last2 - first2)` applications of
-// the corresponding comparison and each projection, if any.
-//
-// Remarks: If two sequences have the same number of elements and their
-// corresponding elements (if any) are equivalent, then neither sequence is
-// lexicographically less than the other. If one sequence is a proper prefix of
-// the other, then the shorter sequence is lexicographically less than the
-// longer sequence. Otherwise, the lexicographical comparison of the sequences
-// yields the same result as the comparison of the first corresponding pair of
-// elements that are not equivalent.
-//
-// Reference:
-// https://wg21.link/alg.lex.comparison#:~:text=lexicographical_compare(I1
-template <typename ForwardIterator1,
-          typename ForwardIterator2,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::iterator_category_t<ForwardIterator1>,
-          typename = internal::iterator_category_t<ForwardIterator2>,
-          typename = indirect_result_t<Comp&,
-                                       projected<ForwardIterator1, Proj1>,
-                                       projected<ForwardIterator2, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<ForwardIterator2, Proj2>,
-                                       projected<ForwardIterator1, Proj1>>>
-constexpr bool lexicographical_compare(ForwardIterator1 first1,
-                                       ForwardIterator1 last1,
-                                       ForwardIterator2 first2,
-                                       ForwardIterator2 last2,
-                                       Comp comp = {},
-                                       Proj1 proj1 = {},
-                                       Proj2 proj2 = {}) {
-  for (; first1 != last1 && first2 != last2; ++first1, ++first2) {
-    auto&& projected_first1 = gurl_base::invoke(proj1, *first1);
-    auto&& projected_first2 = gurl_base::invoke(proj2, *first2);
-    if (gurl_base::invoke(comp, projected_first1, projected_first2))
-      return true;
-    if (gurl_base::invoke(comp, projected_first2, projected_first1))
-      return false;
-  }
-
-  // `first2 != last2` is equivalent to `first1 == last1 && first2 != last2`
-  // here, since we broke out of the loop above.
-  return first2 != last2;
-}
-
-// Returns: `true` if and only if the sequence of elements defined by `range1`
-//  is lexicographically less than the sequence of elements defined by `range2`.
-//
-// Complexity: At most `2 min(size(range1), size(range2))` applications of the
-// corresponding comparison and each projection, if any.
-//
-// Remarks: If two sequences have the same number of elements and their
-// corresponding elements (if any) are equivalent, then neither sequence is
-// lexicographically less than the other. If one sequence is a proper prefix of
-// the other, then the shorter sequence is lexicographically less than the
-// longer sequence. Otherwise, the lexicographical comparison of the sequences
-// yields the same result as the comparison of the first corresponding pair of
-// elements that are not equivalent.
-//
-// Reference:
-// https://wg21.link/alg.lex.comparison#:~:text=lexicographical_compare(R1
-template <typename Range1,
-          typename Range2,
-          typename Comp = ranges::less,
-          typename Proj1 = identity,
-          typename Proj2 = identity,
-          typename = internal::range_category_t<Range1>,
-          typename = internal::range_category_t<Range2>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range1>, Proj1>,
-                                       projected<iterator_t<Range2>, Proj2>>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range2>, Proj2>,
-                                       projected<iterator_t<Range1>, Proj1>>>
-constexpr bool lexicographical_compare(Range1&& range1,
-                                       Range2&& range2,
-                                       Comp comp = {},
-                                       Proj1 proj1 = {},
-                                       Proj2 proj2 = {}) {
-  return ranges::lexicographical_compare(
-      ranges::begin(range1), ranges::end(range1), ranges::begin(range2),
-      ranges::end(range2), std::move(comp), std::move(proj1), std::move(proj2));
-}
-
-// [alg.permutation.generators] Permutation generators
-// Reference: https://wg21.link/alg.permutation.generators
-
-// Effects: Takes a sequence defined by the range `[first, last)` and transforms
-// it into the next permutation. The next permutation is found by assuming that
-// the set of all permutations is lexicographically sorted with respect to
-// `comp` and `proj`. If no such permutation exists, transforms the sequence
-// into the first permutation; that is, the ascendingly-sorted one.
-//
-// Returns: `true` if a next permutation was found and otherwise `false`.
-//
-// Complexity: At most `(last - first) / 2` swaps.
-//
-// Reference:
-// https://wg21.link/alg.permutation.generators#:~:text=next_permutation(I
-template <typename BidirectionalIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<BidirectionalIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<BidirectionalIterator, Proj>,
-                                       projected<BidirectionalIterator, Proj>>>
-constexpr auto next_permutation(BidirectionalIterator first,
-                                BidirectionalIterator last,
-                                Comp comp = {},
-                                Proj proj = {}) {
-  return std::next_permutation(
-      first, last, internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Effects: Takes a sequence defined by `range` and transforms it into the next
-// permutation. The next permutation is found by assuming that the set of all
-// permutations is lexicographically sorted with respect to `comp` and `proj`.
-// If no such permutation exists, transforms the sequence into the first
-// permutation; that is, the ascendingly-sorted one.
-//
-// Returns: `true` if a next permutation was found and otherwise `false`.
-//
-// Complexity: At most `size(range) / 2` swaps.
-//
-// Reference:
-// https://wg21.link/alg.permutation.generators#:~:text=next_permutation(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto next_permutation(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::next_permutation(ranges::begin(range), ranges::end(range),
-                                  std::move(comp), std::move(proj));
-}
-
-// Effects: Takes a sequence defined by the range `[first, last)` and transforms
-// it into the previous permutation. The previous permutation is found by
-// assuming that the set of all permutations is lexicographically sorted with
-// respect to `comp` and `proj`. If no such permutation exists, transforms the
-// sequence into the last permutation; that is, the decreasingly-sorted one.
-//
-// Returns: `true` if a next permutation was found and otherwise `false`.
-//
-// Complexity: At most `(last - first) / 2` swaps.
-//
-// Reference:
-// https://wg21.link/alg.permutation.generators#:~:text=prev_permutation(I
-template <typename BidirectionalIterator,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::iterator_category_t<BidirectionalIterator>,
-          typename = indirect_result_t<Comp&,
-                                       projected<BidirectionalIterator, Proj>,
-                                       projected<BidirectionalIterator, Proj>>>
-constexpr auto prev_permutation(BidirectionalIterator first,
-                                BidirectionalIterator last,
-                                Comp comp = {},
-                                Proj proj = {}) {
-  return std::prev_permutation(
-      first, last, internal::ProjectedBinaryPredicate(comp, proj, proj));
-}
-
-// Effects: Takes a sequence defined by `range` and transforms it into the
-// previous permutation. The previous permutation is found by assuming that the
-// set of all permutations is lexicographically sorted with respect to `comp`
-// and `proj`. If no such permutation exists, transforms the sequence into the
-// last permutation; that is, the decreasingly-sorted one.
-//
-// Returns: `true` if a previous permutation was found and otherwise `false`.
-//
-// Complexity: At most `size(range) / 2` swaps.
-//
-// Reference:
-// https://wg21.link/alg.permutation.generators#:~:text=prev_permutation(R
-template <typename Range,
-          typename Comp = ranges::less,
-          typename Proj = identity,
-          typename = internal::range_category_t<Range>,
-          typename = indirect_result_t<Comp&,
-                                       projected<iterator_t<Range>, Proj>,
-                                       projected<iterator_t<Range>, Proj>>>
-constexpr auto prev_permutation(Range&& range, Comp comp = {}, Proj proj = {}) {
-  return ranges::prev_permutation(ranges::begin(range), ranges::end(range),
-                                  std::move(comp), std::move(proj));
-}
-
-}  // namespace ranges
-
-}  // namespace base
-
-#endif  // BASE_RANGES_ALGORITHM_H_
diff --git a/base/ranges/functional.h b/base/ranges/functional.h
deleted file mode 100644
index 6557a19..0000000
--- a/base/ranges/functional.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_RANGES_FUNCTIONAL_H_
-#define BASE_RANGES_FUNCTIONAL_H_
-
-#include <functional>
-#include <type_traits>
-#include <utility>
-
-namespace gurl_base {
-
-namespace ranges {
-
-// Simplified implementations of C++20's std::ranges comparison function
-// objects. As opposed to the std::ranges implementation, these versions do not
-// constrain the passed-in types.
-//
-// Reference: https://wg21.link/range.cmp
-using equal_to = std::equal_to<>;
-using not_equal_to = std::not_equal_to<>;
-using greater = std::greater<>;
-using less = std::less<>;
-using greater_equal = std::greater_equal<>;
-using less_equal = std::less_equal<>;
-
-}  // namespace ranges
-
-}  // namespace base
-
-#endif  // BASE_RANGES_FUNCTIONAL_H_
diff --git a/base/ranges/ranges.h b/base/ranges/ranges.h
deleted file mode 100644
index f591c6c..0000000
--- a/base/ranges/ranges.h
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_RANGES_RANGES_H_
-#define BASE_RANGES_RANGES_H_
-
-#include <array>
-#include <iterator>
-#include <type_traits>
-#include <utility>
-
-#include "base/template_util.h"
-
-namespace gurl_base {
-
-namespace internal {
-
-// Overload for C array.
-template <typename T, size_t N>
-constexpr T* begin(T (&array)[N], priority_tag<2>) {
-  return array;
-}
-
-// Overload for mutable std::array. Required since std::array::begin is not
-// constexpr prior to C++17. Needs to dispatch to the const overload since only
-// const operator[] is constexpr in C++14.
-template <typename T, size_t N>
-constexpr T* begin(std::array<T, N>& array, priority_tag<2> tag) {
-  return const_cast<T*>(begin(const_cast<const std::array<T, N>&>(array), tag));
-}
-
-// Overload for const std::array. Required since std::array::begin is not
-// constexpr prior to C++17.
-template <typename T, size_t N>
-constexpr const T* begin(const std::array<T, N>& array, priority_tag<2>) {
-  return N != 0 ? &array[0] : nullptr;
-}
-
-// Generic container overload.
-template <typename Range>
-constexpr auto begin(Range&& range, priority_tag<1>)
-    -> decltype(std::forward<Range>(range).begin()) {
-  return std::forward<Range>(range).begin();
-}
-
-// Overload for free begin() function.
-template <typename Range>
-constexpr auto begin(Range&& range, priority_tag<0>)
-    -> decltype(begin(std::forward<Range>(range))) {
-  return begin(std::forward<Range>(range));
-}
-
-// Overload for C array.
-template <typename T, size_t N>
-constexpr T* end(T (&array)[N], priority_tag<2>) {
-  return array + N;
-}
-
-// Overload for mutable std::array. Required since std::array::end is not
-// constexpr prior to C++17. Needs to dispatch to the const overload since only
-// const operator[] is constexpr in C++14.
-template <typename T, size_t N>
-constexpr T* end(std::array<T, N>& array, priority_tag<2> tag) {
-  return const_cast<T*>(end(const_cast<const std::array<T, N>&>(array), tag));
-}
-
-// Overload for const std::array. Required since std::array::end is not
-// constexpr prior to C++17.
-template <typename T, size_t N>
-constexpr const T* end(const std::array<T, N>& array, priority_tag<2>) {
-  return N != 0 ? (&array[0]) + N : nullptr;
-}
-
-// Generic container overload.
-template <typename Range>
-constexpr auto end(Range&& range, priority_tag<1>)
-    -> decltype(std::forward<Range>(range).end()) {
-  return std::forward<Range>(range).end();
-}
-
-// Overload for free end() function.
-template <typename Range>
-constexpr auto end(Range&& range, priority_tag<0>)
-    -> decltype(end(std::forward<Range>(range))) {
-  return end(std::forward<Range>(range));
-}
-
-}  // namespace internal
-
-namespace ranges {
-
-// Simplified implementation of C++20's std::ranges::begin.
-// As opposed to std::ranges::begin, this implementation does does not check
-// whether begin() returns an iterator and does not inhibit ADL.
-//
-// The trailing return type and dispatch to the internal implementation is
-// necessary to be SFINAE friendly.
-//
-// Reference: https://wg21.link/range.access.begin
-template <typename Range>
-constexpr auto begin(Range&& range) noexcept
-    -> decltype(internal::begin(std::forward<Range>(range),
-                                internal::priority_tag<2>())) {
-  return internal::begin(std::forward<Range>(range),
-                         internal::priority_tag<2>());
-}
-
-// Simplified implementation of C++20's std::ranges::end.
-// As opposed to std::ranges::end, this implementation does does not check
-// whether end() returns an iterator and does not inhibit ADL.
-//
-// The trailing return type and dispatch to the internal implementation is
-// necessary to be SFINAE friendly.
-//
-// Reference: - https://wg21.link/range.access.end
-template <typename Range>
-constexpr auto end(Range&& range) noexcept
-    -> decltype(internal::end(std::forward<Range>(range),
-                              internal::priority_tag<2>())) {
-  return internal::end(std::forward<Range>(range), internal::priority_tag<2>());
-}
-
-// Implementation of C++20's std::ranges::iterator_t.
-//
-// Reference: https://wg21.link/ranges.syn#:~:text=iterator_t
-template <typename Range>
-using iterator_t = decltype(ranges::begin(std::declval<Range&>()));
-
-// Implementation of C++20's std::ranges::range_value_t.
-//
-// Reference: https://wg21.link/ranges.syn#:~:text=range_value_t
-template <typename Range>
-using range_value_t = iter_value_t<iterator_t<Range>>;
-
-}  // namespace ranges
-
-}  // namespace base
-
-#endif  // BASE_RANGES_RANGES_H_
diff --git a/base/stl_util.h b/base/stl_util.h
index bc0118e..faeb1df 100644
--- a/base/stl_util.h
+++ b/base/stl_util.h
@@ -8,24 +8,12 @@
 #define BASE_STL_UTIL_H_
 
 #include <algorithm>
-#include <forward_list>
 #include <iterator>
-#include <type_traits>
 
 #include "polyfills/base/check.h"
-#include "base/ranges/algorithm.h"
 
 namespace gurl_base {
 
-namespace internal {
-
-template <typename Iter>
-constexpr bool IsRandomAccessIter =
-    std::is_same_v<typename std::iterator_traits<Iter>::iterator_category,
-                   std::random_access_iterator_tag>;
-
-}  // namespace internal
-
 // Returns a const reference to the underlying container of a container adapter.
 // Works for std::priority_queue, std::queue, and std::stack.
 template <class A>
@@ -39,7 +27,7 @@
 // Clears internal memory of an STL object.
 // STL clear()/reserve(0) does not always free internal memory allocated
 // This function uses swap/destructor to ensure the internal memory is freed.
-template<class T>
+template <class T>
 void STLClearObject(T* obj) {
   T tmp;
   tmp.swap(*obj);
@@ -51,11 +39,10 @@
 // Returns a new ResultType containing the difference of two sorted containers.
 template <typename ResultType, typename Arg1, typename Arg2>
 ResultType STLSetDifference(const Arg1& a1, const Arg2& a2) {
-  GURL_DCHECK(ranges::is_sorted(a1));
-  GURL_DCHECK(ranges::is_sorted(a2));
+  GURL_DCHECK(std::ranges::is_sorted(a1));
+  GURL_DCHECK(std::ranges::is_sorted(a2));
   ResultType difference;
-  std::set_difference(a1.begin(), a1.end(),
-                      a2.begin(), a2.end(),
+  std::set_difference(a1.begin(), a1.end(), a2.begin(), a2.end(),
                       std::inserter(difference, difference.end()));
   return difference;
 }
@@ -63,11 +50,10 @@
 // Returns a new ResultType containing the union of two sorted containers.
 template <typename ResultType, typename Arg1, typename Arg2>
 ResultType STLSetUnion(const Arg1& a1, const Arg2& a2) {
-  GURL_DCHECK(ranges::is_sorted(a1));
-  GURL_DCHECK(ranges::is_sorted(a2));
+  GURL_DCHECK(std::ranges::is_sorted(a1));
+  GURL_DCHECK(std::ranges::is_sorted(a2));
   ResultType result;
-  std::set_union(a1.begin(), a1.end(),
-                 a2.begin(), a2.end(),
+  std::set_union(a1.begin(), a1.end(), a2.begin(), a2.end(),
                  std::inserter(result, result.end()));
   return result;
 }
@@ -76,11 +62,10 @@
 // containers.
 template <typename ResultType, typename Arg1, typename Arg2>
 ResultType STLSetIntersection(const Arg1& a1, const Arg2& a2) {
-  GURL_DCHECK(ranges::is_sorted(a1));
-  GURL_DCHECK(ranges::is_sorted(a2));
+  GURL_DCHECK(std::ranges::is_sorted(a1));
+  GURL_DCHECK(std::ranges::is_sorted(a2));
   ResultType result;
-  std::set_intersection(a1.begin(), a1.end(),
-                        a2.begin(), a2.end(),
+  std::set_intersection(a1.begin(), a1.end(), a2.begin(), a2.end(),
                         std::inserter(result, result.end()));
   return result;
 }
@@ -95,22 +80,21 @@
 class IsNotIn {
  public:
   explicit IsNotIn(const Collection& collection)
-      : i_(collection.begin()), end_(collection.end()) {}
+      : it_(collection.begin()), end_(collection.end()) {}
 
   bool operator()(const typename Collection::value_type& x) {
-    while (i_ != end_ && *i_ < x)
-      ++i_;
-    if (i_ == end_)
-      return true;
-    if (*i_ == x) {
-      ++i_;
-      return false;
+    while (it_ != end_ && *it_ < x) {
+      ++it_;
     }
-    return true;
+    if (it_ == end_ || *it_ != x) {
+      return true;
+    }
+    ++it_;
+    return false;
   }
 
  private:
-  typename Collection::const_iterator i_;
+  typename Collection::const_iterator it_;
   const typename Collection::const_iterator end_;
 };
 
diff --git a/base/strings/abseil_string_number_conversions.cc b/base/strings/abseil_string_number_conversions.cc
index cce321f..be572ee 100644
--- a/base/strings/abseil_string_number_conversions.cc
+++ b/base/strings/abseil_string_number_conversions.cc
@@ -4,17 +4,18 @@
 
 #include "base/strings/abseil_string_number_conversions.h"
 
+#include <string_view>
+
 #include "base/strings/string_number_conversions_internal.h"
-#include "base/strings/string_piece.h"
 #include "absl/numeric/int128.h"
 
 namespace gurl_base {
 
-bool StringToUint128(StringPiece input, absl::uint128* output) {
+bool StringToUint128(std::string_view input, absl::uint128* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool HexStringToUInt128(StringPiece input, absl::uint128* output) {
+bool HexStringToUInt128(std::string_view input, absl::uint128* output) {
   return internal::HexStringToIntImpl(input, *output);
 }
 
diff --git a/base/strings/abseil_string_number_conversions.h b/base/strings/abseil_string_number_conversions.h
index fe2874b..245c923 100644
--- a/base/strings/abseil_string_number_conversions.h
+++ b/base/strings/abseil_string_number_conversions.h
@@ -5,20 +5,22 @@
 #ifndef BASE_STRINGS_ABSEIL_STRING_NUMBER_CONVERSIONS_H_
 #define BASE_STRINGS_ABSEIL_STRING_NUMBER_CONVERSIONS_H_
 
+#include <string_view>
+
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
 #include "absl/numeric/int128.h"
 
 namespace gurl_base {
 
 // Best effort conversion, see `gurl_base::StringToInt()` for restrictions.
 // Will only successfully parse values that will fit into `output`.
-BASE_EXPORT bool StringToUint128(StringPiece input, absl::uint128* output);
+BASE_EXPORT bool StringToUint128(std::string_view input, absl::uint128* output);
 
 // Best effort conversion, see `gurl_base::StringToInt()` for restrictions.
 // Will only successfully parse hex values that will fit into `output`.
 // The string is not required to start with 0x.
-BASE_EXPORT bool HexStringToUInt128(StringPiece input, absl::uint128* output);
+BASE_EXPORT bool HexStringToUInt128(std::string_view input,
+                                    absl::uint128* output);
 
 }  // namespace base
 
diff --git a/base/strings/abseil_string_number_conversions_unittest.cc b/base/strings/abseil_string_number_conversions_unittest.cc
index 77a06b1..d3586b8 100644
--- a/base/strings/abseil_string_number_conversions_unittest.cc
+++ b/base/strings/abseil_string_number_conversions_unittest.cc
@@ -8,7 +8,6 @@
 
 #include <limits>
 
-#include "base/strings/string_piece.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "absl/numeric/int128.h"
 
diff --git a/base/strings/cstring_view.h b/base/strings/cstring_view.h
new file mode 100644
index 0000000..7180a91
--- /dev/null
+++ b/base/strings/cstring_view.h
@@ -0,0 +1,591 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_CSTRING_VIEW_H_
+#define BASE_STRINGS_CSTRING_VIEW_H_
+
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+#include <ostream>
+#include <string>
+#include <string_view>
+
+#include "polyfills/base/check.h"
+#include "polyfills/base/check_op.h"
+#include "base/compiler_specific.h"
+#include "base/containers/checked_iterators.h"
+#include "base/containers/span.h"
+#include "polyfills/base/memory/raw_ptr_exclusion.h"
+#include "base/numerics/safe_conversions.h"
+#include "build/build_config.h"
+
+namespace gurl_base {
+
+// A CString is a NUL-terminated character array, which is the C programming
+// language representation of a string. This class (and its aliases below)
+// provides a non-owning and bounds-safe view of a CString, and can replace all
+// use of native pointers (such as `const char*`) for this purpose in C++ code.
+//
+// The basic_cstring_view class is followed by aliases for the various char
+// types:
+// * cstring_view provides a view of a `const char*`.
+// * u16cstring_view provides a view of a `const char16_t*`.
+// * u32cstring_view provides a view of a `const char32_t*`.
+// * wcstring_view provides a view of a `const wchar_t*`.
+template <class Char>
+class basic_cstring_view final {
+  static_assert(!std::is_const_v<Char>);
+  static_assert(!std::is_reference_v<Char>);
+
+ public:
+  using value_type = Char;
+  using pointer = Char*;
+  using const_pointer = const Char*;
+  using reference = Char&;
+  using const_reference = const Char&;
+  using iterator = CheckedContiguousIterator<const Char>;
+  using const_iterator = CheckedContiguousIterator<const Char>;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<iterator>;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
+
+  // The `npos` constant represents a non-existent position in the cstring view.
+  constexpr static auto npos = static_cast<size_t>(-1);
+
+  // Constructs an empty cstring view, which points to an empty string with a
+  // terminating NUL.
+  constexpr basic_cstring_view() noexcept : ptr_(kEmpty), len_(0u) {}
+
+  // cstring views are trivially copyable, moveable, and destructible.
+
+  // Constructs a cstring view that points at the contents of a string literal.
+  //
+  // Example:
+  // ```
+  // const char kLiteral[] = "hello world";
+  // auto s = gurl_base::cstring_view(kLiteral);
+  // GURL_CHECK(s == "hello world");
+  // auto s2 = gurl_base::cstring_view("this works too");
+  // GURL_CHECK(s == "this works too");
+  // ```
+  //
+  // The string will end at the first NUL character in the given array.
+  //
+  // Example:
+  // ```
+  // auto s = gurl_base::cstring_view("hello\0world");
+  // GURL_CHECK(s == "hello");
+  // ```
+  template <int&..., size_t M>
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr basic_cstring_view(const Char (&lit LIFETIME_BOUND)[M]) noexcept
+      ENABLE_IF_ATTR(lit[M - 1u] == Char{0}, "requires string literal as input")
+      : ptr_(lit), len_(std::char_traits<Char>::length(lit)) {
+    // For non-clang compilers. On clang, the function is not even callable
+    // without this being known to pass at compile time.
+    //
+    // SAFETY: lit is an array of size M, so M-1 is in bounds.
+    GURL_DCHECK_EQ(UNSAFE_BUFFERS(lit[M - 1u]), Char{0});
+  }
+
+  // Constructs a cstring view from a std::string (or other std::basic_string
+  // type). The string parameter must outlive the cstring view, including that
+  // it must not be moved-from or destroyed.
+  //
+  // This conversion is implicit, which matches the conversion from std::string
+  // to std::string_view (through string's `operator string_view()`).
+  //
+  // # Interaction with SSO
+  // std::string stores its contents inline when they fit (which is an
+  // implementation defined length), instead of in a heap-allocated buffer. This
+  // is referred to as the Small String Optimization. This means that moving or
+  // destring a std::string will invalidate a cstring view and leave it with
+  // dangling pointers. This differs from the behaviour of std::vector and span,
+  // since pointers into a std::vector remain valid after moving the std::vector
+  // and destroying the original.
+  //
+  // # Preventing implicit temporaries
+  // Because std::string can be implicitly constructed, the string constructor
+  // may unintentionally be called with a temporary `std::string` when called
+  // with values that convert to `std::string`. We prevent this templating this
+  // constructor and requiring the incoming type to actually be a `std::string`
+  // (or other `std::basic_string`). This also improves compiler errors,
+  // compared to deleting a string&& overload, when passed an array that does
+  // not match the `ENABLE_IF_ATTR` constructor condition by not sending it to a
+  // deleted overload receiving `std::string`.
+  template <std::same_as<std::basic_string<Char>> String>
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr basic_cstring_view(const String& s LIFETIME_BOUND) noexcept
+      : ptr_(s.c_str()), len_(s.size()) {}
+
+  // Unsafe construction from a NUL-terminated cstring, primarily for use with C
+  // APIs. Prefer to construct cstring view from a string literal, std::string,
+  // or another cstring view.
+  //
+  // # Safety
+  // The `ptr` must point to a NUL-terminated string or Undefined Behaviour will
+  // result.
+  //
+  // # Implementation note
+  // We use a `String&&` template to ensure the input is a pointer and not an
+  // array that decayed to a pointer. This ensures the ctor will not act as a
+  // fallback for the string literal ctor when the enable_if condition fails.
+  template <class String>
+    requires(std::same_as<std::remove_cvref_t<String>, Char*> ||
+             std::same_as<std::remove_cvref_t<String>, const Char*>)
+  UNSAFE_BUFFER_USAGE explicit constexpr basic_cstring_view(
+      String&& ptr LIFETIME_BOUND) noexcept
+      : ptr_(ptr), len_(std::char_traits<Char>::length(ptr)) {}
+
+  // Returns a pointer to the NUL-terminated string, for passing to C-style APIs
+  // that require `const char*` (or whatever the `Char` type is).
+  //
+  // This is never null.
+  PURE_FUNCTION constexpr const Char* c_str() const noexcept { return ptr_; }
+
+  // Returns a pointer to underlying buffer. To get a string pointer, use
+  // `c_str()`.
+  //
+  // Pair with `size()` to construct a bounded non-NUL-terminated view, such as
+  // by `gurl_base::span`. This is never null.
+  PURE_FUNCTION constexpr const Char* data() const noexcept { return ptr_; }
+
+  // Returns the number of characters in the string, not including the
+  // terminating NUL.
+  PURE_FUNCTION constexpr size_t size() const noexcept { return len_; }
+  // An alias for `size()`, returning the number of characters in the string.
+  PURE_FUNCTION constexpr size_t length() const noexcept { return len_; }
+
+  // Returns whether the cstring view is for an empty string. When empty, it is
+  // pointing to a cstring that contains only a NUL character.
+  PURE_FUNCTION constexpr bool empty() const noexcept { return len_ == 0u; }
+
+  // Returns the maximum number of characters that can be represented inside the
+  // cstring view for character type `Char`.
+  //
+  // This is the number of `Char` objects that can fit inside an addressable
+  // byte array. Since the number of bytes allowed is fixed, the number returned
+  // is smaller when the `Char` is a larger type.
+  PURE_FUNCTION constexpr size_t max_size() const noexcept {
+    return static_cast<size_t>(-1) / sizeof(Char);
+  }
+
+  // Returns the number of bytes in the string, not including the terminating
+  // NUL. To include the NUL, add `sizeof(Char)` where `Char` is the character
+  // type of the cstring view (accessible as the `value_type` alias).
+  PURE_FUNCTION constexpr size_t size_bytes() const noexcept {
+    return len_ * sizeof(Char);
+  }
+
+  // Produces an iterator over the cstring view, excluding the terminating NUL.
+  PURE_FUNCTION constexpr iterator begin() const noexcept {
+    // SAFETY: `ptr_ + len_` for a cstring view always gives a pointer in
+    // the same allocation as `ptr_` based on the precondition of
+    // the type.
+    return UNSAFE_BUFFERS(iterator(ptr_, ptr_ + len_));
+  }
+  // Produces an iterator over the cstring view, excluding the terminating NUL.
+  PURE_FUNCTION constexpr iterator end() const noexcept {
+    // SAFETY: `ptr_ + len_` for a cstring view always gives a pointer in
+    // the same allocation as `ptr_` based on the precondition of
+    // the type.
+    return UNSAFE_BUFFERS(iterator(ptr_, ptr_ + len_, ptr_ + len_));
+  }
+  // Produces an iterator over the cstring view, excluding the terminating NUL.
+  PURE_FUNCTION constexpr const_iterator cbegin() const noexcept {
+    return begin();
+  }
+  // Produces an iterator over the cstring view, excluding the terminating NUL.
+  PURE_FUNCTION constexpr const_iterator cend() const noexcept { return end(); }
+
+  // Produces a reverse iterator over the cstring view, excluding the
+  // terminating NUL.
+  PURE_FUNCTION constexpr reverse_iterator rbegin() const noexcept {
+    return std::reverse_iterator(end());
+  }
+  // Produces a reverse iterator over the cstring view, excluding the
+  // terminating NUL.
+  PURE_FUNCTION constexpr reverse_iterator rend() const noexcept {
+    return std::reverse_iterator(begin());
+  }
+  // Produces a reverse iterator over the cstring view, excluding the
+  // terminating NUL.
+  PURE_FUNCTION constexpr const_reverse_iterator rcbegin() const noexcept {
+    return std::reverse_iterator(cend());
+  }
+  // Produces a reverse iterator over the cstring view, excluding the
+  // terminating NUL.
+  PURE_FUNCTION constexpr const_reverse_iterator rcend() const noexcept {
+    return std::reverse_iterator(cbegin());
+  }
+
+  // Returns the character at offset `idx`.
+  //
+  // This can be used to access any character in the ctring, as well as the NUL
+  // terminator.
+  //
+  // # Checks
+  // The function CHECKs that the `idx` is inside the cstring (including at its
+  // NUL terminator) and will terminate otherwise.
+  PURE_FUNCTION constexpr const Char& operator[](size_t idx) const noexcept {
+    GURL_CHECK_LE(idx, len_);
+    // SAFETY: `ptr_` points `len_` many elements plus a NUL terminator, and
+    // `idx <= len_`, so `idx` is in range for `ptr_`.
+    return UNSAFE_BUFFERS(ptr_[idx]);
+  }
+
+  // A named function that performs the same as `operator[]`.
+  PURE_FUNCTION constexpr const Char& at(size_t idx) const noexcept {
+    return (*this)[idx];
+  }
+
+  // Returns the first character in the cstring view.
+  //
+  // # Checks
+  // The function CHECKs that the string is non-empty, and will terminate
+  // otherwise.
+  PURE_FUNCTION constexpr const Char& front() const noexcept {
+    GURL_CHECK(len_);
+    // Since `len_ > 0`, 0 is a valid offset into the string contents.
+    return UNSAFE_BUFFERS(ptr_[0u]);
+  }
+
+  // Returns the last (non-NUL) character in the cstring view.
+  //
+  // # Checks
+  // The function CHECKs that the string is non-empty, and will terminate
+  // otherwise.
+  PURE_FUNCTION constexpr const Char& back() const noexcept {
+    GURL_CHECK(len_);
+    // Since `len_ > 0`, `len - 1` will not underflow. There are `len_` many
+    // chars in the string before a NUL, so `len_ - 1` is in range of the string
+    // contents.
+    return UNSAFE_BUFFERS(ptr_[len_ - 1u]);
+  }
+
+  // Modifies the cstring view in place, moving the front ahead by `n`
+  // characters.
+  //
+  // # Checks
+  // The function CHECKs that `n <= size()`, and will terminate otherwise.
+  constexpr void remove_prefix(size_t n) noexcept {
+    GURL_CHECK_LE(n, len_);
+    // SAFETY: Since `n <= len_`, the pointer at offset `n` is inside the string
+    // (or at the terminating NUL) and the `len_ - n` value will not underflow.
+    // Thus the resulting pointer is still a NUL- terminated string of length
+    // `len_ - n`.
+    ptr_ = UNSAFE_BUFFERS(ptr_ + n);
+    len_ = len_ - n;
+  }
+
+  // No `remove_suffix()` method exists as it would remove the terminating NUL
+  // character. Convert to a `std::string_view` (either by construction or with
+  // a `substr(0u)` call) to construct arbitrary substrings that are not
+  // NUL-terminated.
+  void remove_suffix(size_t n) = delete;
+
+  // Modifies the cstring view in place, swapping its contents with another view
+  // of the same type.
+  constexpr void swap(basic_cstring_view& other) noexcept {
+    std::swap(ptr_, other.ptr_);
+    std::swap(len_, other.len_);
+  }
+
+  // Returns a string view of the subrange starting as `pos` and including
+  // `count` characters. If `count` is not specified, or exceeds the length of
+  // the string after `pos`, the subrange returned will include all characters
+  // up to the terminating NUL.
+  //
+  // # Checks
+  // The function CHECKs that `pos` is in range for the string (or at the
+  // terminating NULL), and will terminate otherwise.
+  PURE_FUNCTION constexpr std::basic_string_view<Char> substr(
+      size_t pos,
+      size_t count = npos) const noexcept {
+    // Ensure `ptr_ + pos` is valid. and `len_ - pos` does not underflow.
+    GURL_CHECK_LE(pos, len_);
+    // SAFETY: We require that:
+    // * `ptr_ + pos` is a pointer in the string.
+    // * `pos + count <= len_` so that resulting substring's end is in range.
+    //
+    // The first follows directly from the GURL_CHECK above that `pos <= len_`. The
+    // second follows from clamping `count` to at most `len_ - pos`.
+    return UNSAFE_BUFFERS(
+        std::basic_string_view<Char>(ptr_ + pos, std::min(count, len_ - pos)));
+  }
+
+  // Returns whether the cstring view starts with the given `prefix`. Will
+  // always return false if `prefix` is larger than the current cstring view.
+  constexpr bool starts_with(
+      std::basic_string_view<Char> prefix) const noexcept {
+    return std::basic_string_view<Char>(*this).starts_with(prefix);
+  }
+
+  // Returns whether the cstring view starts with the given `character`.
+  constexpr bool starts_with(Char character) const noexcept {
+    return std::basic_string_view<Char>(*this).starts_with(character);
+  }
+
+  // Returns whether the cstring view ends with the given `suffix`. Will
+  // always return false if `suffix` is larger than the current cstring view.
+  constexpr bool ends_with(std::basic_string_view<Char> suffix) const noexcept {
+    return std::basic_string_view<Char>(*this).ends_with(suffix);
+  }
+
+  // Returns whether the cstring view starts with the given `character`.
+  constexpr bool ends_with(Char character) const noexcept {
+    return std::basic_string_view<Char>(*this).ends_with(character);
+  }
+
+  // Returns the first position in the cstring view at which `search` is found,
+  // starting from the offset `pos`. If `pos` is not specified, the entire
+  // cstring view is searched. Returns `npos` if `search` is not found or if
+  // `pos` is out of range.
+  constexpr size_t find(std::basic_string_view<Char> search,
+                        size_t pos = 0u) const noexcept {
+    return std::basic_string_view<Char>(*this).find(search, pos);
+  }
+  constexpr size_t find(Char search, size_t pos = 0u) const noexcept {
+    return std::basic_string_view<Char>(*this).find(search, pos);
+  }
+
+  // Returns the last position in the cstring view at which `search` is found,
+  // starting from the offset `pos`. If `pos` is not specified or is out of
+  // range, the entire cstring view is searched. Returns `npos` if `search` is
+  // not found.
+  constexpr size_t rfind(std::basic_string_view<Char> search,
+                         size_t pos = npos) const noexcept {
+    return std::basic_string_view<Char>(*this).rfind(search, pos);
+  }
+  constexpr size_t rfind(Char search, size_t pos = npos) const noexcept {
+    return std::basic_string_view<Char>(*this).rfind(search, pos);
+  }
+
+  // Returns the first position in the cstring view at any character in the
+  // `search` is found, starting from the offset `pos`. If `pos` is not
+  // specified, the entire cstring view is searched. Returns `npos` if `search`
+  // is not found or if `pos` is out of range.
+  constexpr size_t find_first_of(std::basic_string_view<Char> search,
+                                 size_t pos = 0u) const noexcept {
+    return std::basic_string_view<Char>(*this).find_first_of(search, pos);
+  }
+  constexpr size_t find_first_of(Char search, size_t pos = 0u) const noexcept {
+    return std::basic_string_view<Char>(*this).find_first_of(search, pos);
+  }
+
+  // Returns the last position in the cstring view at any character in the
+  // `search` is found, starting from the offset `pos`. If `pos` is not
+  // specified or is out of range, the entire cstring view is searched. Returns
+  // `npos` if `search` is not found.
+  constexpr size_t find_last_of(std::basic_string_view<Char> search,
+                                size_t pos = npos) const noexcept {
+    return std::basic_string_view<Char>(*this).find_last_of(search, pos);
+  }
+  constexpr size_t find_last_of(Char search, size_t pos = npos) const noexcept {
+    return std::basic_string_view<Char>(*this).find_last_of(search, pos);
+  }
+
+  // Returns the first position in the cstring view that is not equal to any
+  // character in the `search`, starting from the offset `pos`. If `pos` is not
+  // specified, the entire cstring view is searched. Returns `npos` if every
+  // character is part of `search` or if `pos` is out of range.
+  constexpr size_t find_first_not_of(std::basic_string_view<Char> search,
+                                     size_t pos = 0u) const noexcept {
+    return std::basic_string_view<Char>(*this).find_first_not_of(search, pos);
+  }
+  constexpr size_t find_first_not_of(Char search,
+                                     size_t pos = 0u) const noexcept {
+    return std::basic_string_view<Char>(*this).find_first_not_of(search, pos);
+  }
+
+  // Returns the last position in the cstring view that is not equal to any
+  // character in the `search`, starting from the offset `pos`. If `pos` is not
+  // specified or is out of range, the entire cstring view is searched.  Returns
+  // `npos` if every character is part of `search`.
+  constexpr size_t find_last_not_of(std::basic_string_view<Char> search,
+                                    size_t pos = npos) const noexcept {
+    return std::basic_string_view<Char>(*this).find_last_not_of(search, pos);
+  }
+  constexpr size_t find_last_not_of(Char search,
+                                    size_t pos = npos) const noexcept {
+    return std::basic_string_view<Char>(*this).find_last_not_of(search, pos);
+  }
+
+  // Compare two cstring views for equality, comparing the string contents.
+  friend constexpr bool operator==(basic_cstring_view l, basic_cstring_view r) {
+    return std::ranges::equal(l, r);
+  }
+
+  // Return an ordering between two cstring views, comparing the string
+  // contents.
+  //
+  // cstring views are weakly ordered, since string views pointing into
+  // different strings can compare as equal.
+  friend constexpr std::weak_ordering operator<=>(basic_cstring_view l,
+                                                  basic_cstring_view r) {
+    return std::lexicographical_compare_three_way(l.begin(), l.end(), r.begin(),
+                                                  r.end());
+  }
+
+  // Implicitly converts from cstring_view to a non-NUL-terminated
+  // std::string_view. The std::string_view type implicitly constructs from
+  // `const char*` and cstring view is meant to replace the latter, so this acts
+  // like an implicit constructor on `std::string_view` for cstring views.
+  //
+  // This operator also avoids a requirement on having overloads for both
+  // std::string_view and cstring_view. Such overloads are ambiguous because
+  // both can construct from a character array.
+  //
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr operator std::basic_string_view<Char>() const noexcept {
+    // SAFETY: The cstring view provides that `ptr_ + len_` to be valid.
+    return UNSAFE_BUFFERS(std::basic_string_view<Char>(ptr_, len_));
+  }
+
+  // Converts from cstring_view to std::string. This allocates a new string
+  // backing and copies into it.
+  //
+  // The std::string type implicitly constructs from `const char*` however it
+  // does not implicitly construct from std::string_view. This type sits between
+  // these two, and opts towards making heap allocations explicit by requiring
+  // an explicit conversion.
+  constexpr explicit operator std::basic_string<Char>() const noexcept {
+    // SAFETY: The cstring view provides that `ptr_ + len_` to be valid.
+    return UNSAFE_BUFFERS(std::basic_string<Char>(ptr_, len_));
+  }
+
+  // Concatenate a std::string with a cstring_view to produce another
+  // std::string.
+  //
+  // These act like overloads on `std::string` that work for concatenating
+  // `std::string` and `const char*`.
+  //
+  // The rvalue overloads allow `std::string` to reuse existing capacity, by
+  // calling through to the rvalue overloads on `std::string`.
+  template <class Traits, class Alloc>
+  friend constexpr std::basic_string<Char, Traits, Alloc> operator+(
+      basic_cstring_view lhs,
+      const std::basic_string<Char, Traits, Alloc>& rhs) {
+    return lhs.c_str() + rhs;
+  }
+  template <class Traits, class Alloc>
+  friend constexpr std::basic_string<Char, Traits, Alloc> operator+(
+      basic_cstring_view lhs,
+      std::basic_string<Char, Traits, Alloc>&& rhs) {
+    return lhs.c_str() + std::move(rhs);
+  }
+  template <class Traits, class Alloc>
+  friend constexpr std::basic_string<Char, Traits, Alloc> operator+(
+      const std::basic_string<Char, Traits, Alloc>& lhs,
+      basic_cstring_view rhs) {
+    return lhs + rhs.c_str();
+  }
+  template <class Traits, class Alloc>
+  friend constexpr std::basic_string<Char, Traits, Alloc> operator+(
+      std::basic_string<Char, Traits, Alloc>&& lhs,
+      basic_cstring_view rhs) {
+    return std::move(lhs) + rhs.c_str();
+  }
+
+ private:
+  // An empty string literal for the `Char` type.
+  static constexpr Char kEmpty[] = {Char{0}};
+
+  // An always-valid pointer (never null) to a NUL-terminated string.
+  //
+  // RAW_PTR_EXCLUSION: cstring_view is typically used on the stack as a local
+  // variable/function parameter, so no raw_ptr is used here.
+  RAW_PTR_EXCLUSION const Char* ptr_;
+  // The number of characters between `ptr_` and the NUL terminator.
+  //
+  // SAFETY: `ptr_ + len_` is always valid since `len_` must not exceed the
+  // number of characters in the allocation, or it would no longer indicate the
+  // position of the NUL terminator in the string allocation.
+  size_t len_;
+};
+
+// cstring_view provides a view of a NUL-terminated string. It is a replacement
+// for all use of `const char*`, in order to provide bounds checks and prevent
+// unsafe pointer usage (otherwise prevented by `-Wunsafe-buffer-usage`).
+//
+// See basic_cstring_view for more.
+using cstring_view = basic_cstring_view<char>;
+
+// u16cstring_view provides a view of a NUL-terminated string. It is a
+// replacement for all use of `const char16_t*`, in order to provide bounds
+// checks and prevent unsafe pointer usage (otherwise prevented by
+// `-Wunsafe-buffer-usage`).
+//
+// See basic_cstring_view for more.
+using u16cstring_view = basic_cstring_view<char16_t>;
+
+// u32cstring_view provides a view of a NUL-terminated string. It is a
+// replacement for all use of `const char32_t*`, in order to provide bounds
+// checks and prevent unsafe pointer usage (otherwise prevented by
+// `-Wunsafe-buffer-usage`).
+//
+// See basic_cstring_view for more.
+using u32cstring_view = basic_cstring_view<char32_t>;
+
+#if BUILDFLAG(IS_WIN)
+// wcstring_view provides a view of a NUL-terminated string. It is a
+// replacement for all use of `const wchar_t*`, in order to provide bounds
+// checks and prevent unsafe pointer usage (otherwise prevented by
+// `-Wunsafe-buffer-usage`).
+//
+// See basic_cstring_view for more.
+using wcstring_view = basic_cstring_view<wchar_t>;
+#endif
+
+// Writes the contents of the cstring view to the stream.
+template <class Char, class Traits>
+std::basic_ostream<Char, Traits>& operator<<(
+    std::basic_ostream<Char, Traits>& os,
+    basic_cstring_view<Char> view) {
+  return os << std::basic_string_view<Char>(view);
+}
+
+// Explicitly define PrintTo to avoid gtest printing these as containers
+// rather than strings.
+inline void PrintTo(cstring_view view, std::ostream* os) {
+  *os << view;
+}
+
+// Converts a `basic_cstring_view` instance to a `span<const CharT>`, preserving
+// the trailing '\0'.
+//
+// Explicitly includes the trailing nul, which would be omitted by calling the
+// range constructor.
+template <typename CharT>
+constexpr auto span_with_nul_from_cstring_view(basic_cstring_view<CharT> str) {
+  // SAFETY: It is safe to read the guaranteed null-terminator in `str`.
+  return UNSAFE_BUFFERS(span(str.data(), str.size() + 1));
+}
+// Like `span_with_nul_from_cstring_view()`, but returns a byte span.
+template <typename CharT>
+constexpr auto byte_span_with_nul_from_cstring_view(
+    basic_cstring_view<CharT> str) {
+  return as_bytes(span_with_nul_from_cstring_view(str));
+}
+
+}  // namespace base
+
+template <class Char>
+struct std::hash<gurl_base::basic_cstring_view<Char>> {
+  size_t operator()(const gurl_base::basic_cstring_view<Char>& t) const noexcept {
+    return std::hash<std::basic_string_view<Char>>()(t);
+  }
+};
+
+template <class Char>
+inline constexpr bool
+    std::ranges::enable_borrowed_range<gurl_base::basic_cstring_view<Char>> = true;
+
+template <class Char>
+inline constexpr bool std::ranges::enable_view<gurl_base::basic_cstring_view<Char>> =
+    true;
+
+#endif  // BASE_STRINGS_CSTRING_VIEW_H_
diff --git a/base/strings/cstring_view_unittest.cc b/base/strings/cstring_view_unittest.cc
new file mode 100644
index 0000000..9c77a66
--- /dev/null
+++ b/base/strings/cstring_view_unittest.cc
@@ -0,0 +1,1089 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/cstring_view.h"
+
+#include <algorithm>
+#include <concepts>
+#include <limits>
+#include <sstream>
+#include <type_traits>
+
+#include "base/containers/span.h"
+#include "polyfills/base/debug/alias.h"
+#include "base/strings/strcat.h"
+#include "base/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gurl_base {
+namespace {
+
+static_assert(std::is_default_constructible_v<cstring_view>);
+static_assert(std::is_trivially_copy_constructible_v<cstring_view>);
+static_assert(std::is_trivially_copy_assignable_v<cstring_view>);
+static_assert(std::is_trivially_move_constructible_v<cstring_view>);
+static_assert(std::is_trivially_move_assignable_v<cstring_view>);
+static_assert(std::is_trivially_destructible_v<cstring_view>);
+
+static_assert(std::ranges::contiguous_range<cstring_view>);
+static_assert(std::ranges::borrowed_range<cstring_view>);
+
+// The view is the size of 2 pointers (technically, pointer and address).
+static_assert(sizeof(cstring_view) == sizeof(uintptr_t) + sizeof(size_t));
+
+static_assert(cstring_view::npos == std::string_view::npos);
+static_assert(u16cstring_view::npos == std::u16string_view::npos);
+static_assert(u16cstring_view::npos == std::u32string_view::npos);
+#if BUILDFLAG(IS_WIN)
+static_assert(wcstring_view::npos == std::wstring_view::npos);
+#endif
+
+TEST(CStringViewTest, DefaultConstructed) {
+  constexpr auto c = cstring_view();
+  static_assert(std::same_as<decltype(c), const cstring_view>);
+  static_assert(c.size() == 0u);
+  static_assert(c[c.size()] == '\0');
+}
+
+TEST(CStringViewTest, LiteralConstructed) {
+  constexpr auto empty = cstring_view("");
+  constexpr auto stuff = cstring_view("stuff");
+  constexpr auto other = cstring_view("other");
+  static_assert(std::same_as<decltype(empty), const cstring_view>);
+  static_assert(std::same_as<decltype(stuff), const cstring_view>);
+  static_assert(std::same_as<decltype(other), const cstring_view>);
+
+  static_assert(empty.size() == 0u);
+  static_assert(stuff.size() == 5u);
+  static_assert(other.size() == 5u);
+
+  static_assert(empty[empty.size()] == '\0');
+  static_assert(stuff[stuff.size()] == '\0');
+  static_assert(other[other.size()] == '\0');
+
+  // Implicit construction.
+  {
+    cstring_view s = "stuff";
+    EXPECT_EQ(s, cstring_view("stuff"));
+  }
+}
+
+TEST(CStringViewTest, PointerConstructed) {
+  constexpr const char* c_empty = "";
+  constexpr auto empty = UNSAFE_BUFFERS(cstring_view(c_empty));
+  static_assert(std::same_as<const cstring_view, decltype(empty)>);
+  EXPECT_EQ(empty.data(), c_empty);
+  EXPECT_EQ(empty.size(), 0u);
+
+  constexpr const char* c_stuff = "stuff";
+  constexpr auto stuff = UNSAFE_BUFFERS(cstring_view(c_stuff));
+  static_assert(std::same_as<const cstring_view, decltype(stuff)>);
+  EXPECT_EQ(stuff.data(), c_stuff);
+  EXPECT_EQ(stuff.size(), 5u);
+
+  char c_things_buf[] = {'t', 'h', 'i', 'n', 'g', 's', '\0'};
+  char* c_things = c_things_buf;
+  auto things = UNSAFE_BUFFERS(cstring_view(c_things));
+  static_assert(std::same_as<cstring_view, decltype(things)>);
+  EXPECT_EQ(things.data(), c_things);
+  EXPECT_EQ(things.size(), 6u);
+}
+
+TEST(CStringViewTest, StringConstructed) {
+  std::string empty;
+  {
+    auto c = cstring_view(empty);
+    EXPECT_EQ(c.size(), 0u);
+  }
+  std::string stuff = "stuff";
+  {
+    auto c = cstring_view(stuff);
+    EXPECT_EQ(c.c_str(), stuff.c_str());
+    EXPECT_EQ(c.size(), 5u);
+  }
+  std::u16string stuff16 = u"stuff";
+  {
+    auto c = u16cstring_view(stuff16);
+    EXPECT_EQ(c.c_str(), stuff16.c_str());
+    EXPECT_EQ(c.size(), 5u);
+  }
+  std::u32string stuff32 = U"stuff";
+  {
+    auto c = u32cstring_view(stuff32);
+    EXPECT_EQ(c.c_str(), stuff32.c_str());
+    EXPECT_EQ(c.size(), 5u);
+  }
+#if BUILDFLAG(IS_WIN)
+  std::wstring stuffw = L"stuff";
+  {
+    auto c = wcstring_view(stuffw);
+    EXPECT_EQ(c.c_str(), stuffw.c_str());
+    EXPECT_EQ(c.size(), 5u);
+  }
+#endif
+
+  // Implicit construction.
+  {
+    auto s = std::string("stuff");
+    cstring_view v = s;
+    EXPECT_EQ(v, cstring_view("stuff"));
+  }
+}
+
+TEST(CStringViewTest, Equality) {
+  constexpr auto stuff = cstring_view("stuff");
+
+  static_assert(stuff != cstring_view());
+  static_assert(stuff == cstring_view("stuff"));
+  static_assert(stuff != cstring_view("other"));
+
+  // Implicit conversion to cstring_view from literal in comparison.
+  static_assert(stuff == "stuff");
+}
+
+TEST(CStringViewTest, Ordering) {
+  constexpr auto stuff = cstring_view("stuff");
+
+  static_assert(stuff <=> stuff == std::weak_ordering::equivalent);
+  static_assert(stuff <=> cstring_view() == std::weak_ordering::greater);
+  static_assert(stuff <=> cstring_view("stuff") ==
+                std::weak_ordering::equivalent);
+  static_assert(stuff <=> cstring_view("zz") == std::weak_ordering::less);
+
+  // Implicit conversion to cstring_view from literal in ordering compare.
+  static_assert(stuff <=> "stuff" == std::weak_ordering::equivalent);
+}
+
+TEST(CStringViewTest, Iterate) {
+  constexpr auto def = cstring_view();
+  static_assert(def.begin() == def.end());
+  static_assert(def.cbegin() == def.cend());
+
+  constexpr auto stuff = cstring_view("stuff");
+  static_assert(stuff.begin() != stuff.end());
+  static_assert(stuff.cbegin() != stuff.cend());
+  static_assert(std::same_as<const char&, decltype(*stuff.begin())>);
+
+  {
+    size_t i = 0u;
+    for (auto& c : stuff) {
+      static_assert(std::same_as<const char&, decltype(c)>);
+      EXPECT_EQ(&c, &stuff[i]);
+      ++i;
+    }
+    EXPECT_EQ(i, 5u);
+  }
+}
+
+TEST(CStringViewTest, IterateReverse) {
+  constexpr auto def = cstring_view();
+  static_assert(def.rbegin() == def.rend());
+  static_assert(def.rcbegin() == def.rcend());
+
+  constexpr auto stuff = cstring_view("stuff");
+  static_assert(stuff.rbegin() != stuff.rend());
+  static_assert(stuff.rcbegin() != stuff.rcend());
+  static_assert(std::same_as<const char&, decltype(*stuff.rbegin())>);
+
+  {
+    size_t i = 0u;
+    for (auto it = stuff.rbegin(); it != stuff.rend(); ++it) {
+      static_assert(std::same_as<const char&, decltype(*it)>);
+      EXPECT_EQ(&*it, &stuff[4u - i]);
+      ++i;
+    }
+    EXPECT_EQ(i, 5u);
+  }
+}
+
+TEST(CStringViewDeathTest, IterateBoundsChecked) {
+  auto use = [](auto x) { gurl_base::debug::Alias(&x); };
+
+  constexpr auto stuff = cstring_view("stuff");
+
+  // The NUL terminator is out of bounds for iterating (checked by indexing into
+  // the iterator) since it's not included in the range that the iterator walks
+  // (but is in bounds for indexing on the view).
+  BASE_EXPECT_DEATH(use(*stuff.end()), "");       // Can't deref end.
+  BASE_EXPECT_DEATH(use(stuff.begin()[5]), "");   // Can't index end.
+  BASE_EXPECT_DEATH(use(stuff.begin() + 6), "");  // Can't move past end.
+  BASE_EXPECT_DEATH(use(stuff.begin() - 1), "");  // Can't move past begin.
+
+  BASE_EXPECT_DEATH(use(*stuff.rend()), "");
+  BASE_EXPECT_DEATH(use(stuff.rbegin()[5]), "");
+  BASE_EXPECT_DEATH(use(stuff.rbegin() + 6), "");
+  BASE_EXPECT_DEATH(use(stuff.rbegin() - 1), "");
+}
+
+TEST(CStringViewTest, Index) {
+  constexpr auto empty = cstring_view();
+  static_assert(empty[0u] == '\0');
+
+  static_assert(empty.at(0u) == '\0');
+
+  constexpr auto stuff = cstring_view("stuff");
+  static_assert(stuff[0u] == 's');
+  static_assert(&stuff[0u] == stuff.data());
+  static_assert(stuff[5u] == '\0');
+  static_assert(&stuff[5u] == UNSAFE_BUFFERS(stuff.data() + 5u));
+
+  static_assert(stuff.at(0u) == 's');
+  static_assert(&stuff.at(0u) == stuff.data());
+  static_assert(stuff.at(5u) == '\0');
+  static_assert(&stuff.at(5u) == UNSAFE_BUFFERS(stuff.data() + 5u));
+}
+
+TEST(CStringViewDeathTest, IndexChecked) {
+  auto use = [](auto x) { gurl_base::debug::Alias(&x); };
+
+  constexpr auto empty = cstring_view();
+  BASE_EXPECT_DEATH(use(empty[1u]), "");
+  BASE_EXPECT_DEATH(use(empty[std::numeric_limits<size_t>::max()]), "");
+
+  BASE_EXPECT_DEATH(use(empty.at(1u)), "");
+  BASE_EXPECT_DEATH(use(empty.at(std::numeric_limits<size_t>::max())), "");
+
+  constexpr auto stuff = cstring_view("stuff");
+  BASE_EXPECT_DEATH(use(stuff[6u]), "");
+  BASE_EXPECT_DEATH(use(stuff[std::numeric_limits<size_t>::max()]), "");
+
+  BASE_EXPECT_DEATH(use(stuff.at(6u)), "");
+  BASE_EXPECT_DEATH(use(stuff.at(std::numeric_limits<size_t>::max())), "");
+}
+
+TEST(CStringViewTest, FrontBack) {
+  constexpr auto stuff = cstring_view("stuff");
+  static_assert(stuff.front() == 's');
+  static_assert(&stuff.front() == stuff.data());
+  static_assert(stuff.back() == 'f');
+  static_assert(&stuff.back() == UNSAFE_BUFFERS(stuff.data() + 4u));
+
+  constexpr auto one = cstring_view("1");
+  static_assert(one.front() == '1');
+  static_assert(&one.front() == one.data());
+  static_assert(one.back() == '1');
+  static_assert(&one.back() == one.data());
+}
+
+TEST(CStringViewDeathTest, FrontBackChecked) {
+  auto use = [](auto x) { gurl_base::debug::Alias(&x); };
+
+  constexpr auto empty = cstring_view();
+  BASE_EXPECT_DEATH(use(empty.front()), "");
+  BASE_EXPECT_DEATH(use(empty.back()), "");
+}
+
+TEST(CStringViewTest, Size) {
+  constexpr auto empty = cstring_view();
+  static_assert(empty.size() == 0u);
+  static_assert(empty.size_bytes() == 0u);
+  constexpr auto stuff = cstring_view("stuff");
+  static_assert(stuff.size() == 5u);
+  static_assert(stuff.size_bytes() == 5u);
+
+  constexpr auto empty16 = u16cstring_view();
+  static_assert(empty16.size() == 0u);
+  static_assert(empty16.size_bytes() == 0u);
+  constexpr auto stuff16 = u16cstring_view(u"stuff");
+  static_assert(stuff16.size() == 5u);
+  static_assert(stuff16.size_bytes() == 10u);
+
+  constexpr auto empty32 = u32cstring_view();
+  static_assert(empty32.size() == 0u);
+  static_assert(empty32.size_bytes() == 0u);
+  constexpr auto stuff32 = u32cstring_view(U"stuff");
+  static_assert(stuff32.size() == 5u);
+  static_assert(stuff32.size_bytes() == 20u);
+
+#if BUILDFLAG(IS_WIN)
+  constexpr auto emptyw = wcstring_view();
+  static_assert(emptyw.size() == 0u);
+  static_assert(emptyw.size_bytes() == 0u);
+  constexpr auto stuffw = wcstring_view(L"stuff");
+  static_assert(stuffw.size() == 5u);
+  static_assert(stuffw.size_bytes() == 10u);
+#endif
+}
+
+TEST(CStringViewTest, Empty) {
+  constexpr auto empty = cstring_view();
+  static_assert(empty.empty());
+  constexpr auto one = cstring_view("1");
+  static_assert(!one.empty());
+  constexpr auto stuff = cstring_view("stuff");
+  static_assert(!stuff.empty());
+
+  constexpr auto empty16 = u16cstring_view();
+  static_assert(empty16.empty());
+  constexpr auto stuff16 = u16cstring_view(u"stuff");
+  static_assert(!stuff16.empty());
+
+  constexpr auto empty32 = u32cstring_view();
+  static_assert(empty32.empty());
+  constexpr auto stuff32 = u32cstring_view(U"stuff");
+  static_assert(!stuff32.empty());
+
+#if BUILDFLAG(IS_WIN)
+  constexpr auto emptyw = wcstring_view();
+  static_assert(emptyw.empty());
+  constexpr auto stuffw = wcstring_view(L"stuff");
+  static_assert(!stuffw.empty());
+#endif
+}
+
+TEST(CStringViewTest, MaxSize) {
+  static_assert(cstring_view().max_size() ==
+                std::numeric_limits<size_t>::max());
+  static_assert(u16cstring_view().max_size() ==
+                std::numeric_limits<size_t>::max() / 2u);
+  static_assert(u32cstring_view().max_size() ==
+                std::numeric_limits<size_t>::max() / 4u);
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view().max_size() ==
+                std::numeric_limits<size_t>::max() / 2u);
+#endif
+}
+
+TEST(CStringViewTest, ToSpan) {
+  constexpr auto empty = cstring_view();
+  {
+    auto s = gurl_base::span(empty);
+    static_assert(std::same_as<gurl_base::span<const char>, decltype(s)>);
+    EXPECT_EQ(s.data(), empty.data());
+    EXPECT_EQ(s.size(), 0u);
+    EXPECT_EQ(s.size_bytes(), 0u);
+  }
+  constexpr auto stuff = cstring_view("stuff");
+  {
+    auto s = gurl_base::span(stuff);
+    static_assert(std::same_as<gurl_base::span<const char>, decltype(s)>);
+    EXPECT_EQ(s.data(), stuff.data());
+    EXPECT_EQ(s.size(), 5u);
+    EXPECT_EQ(s.size_bytes(), 5u);
+  }
+  constexpr auto stuff16 = u16cstring_view(u"stuff");
+  {
+    auto s = gurl_base::span(stuff16);
+    static_assert(std::same_as<gurl_base::span<const char16_t>, decltype(s)>);
+    EXPECT_EQ(s.data(), stuff16.data());
+    EXPECT_EQ(s.size(), 5u);
+    EXPECT_EQ(s.size_bytes(), 10u);
+  }
+  constexpr auto stuff32 = u32cstring_view(U"stuff");
+  {
+    auto s = gurl_base::span(stuff32);
+    static_assert(std::same_as<gurl_base::span<const char32_t>, decltype(s)>);
+    EXPECT_EQ(s.data(), stuff32.data());
+    EXPECT_EQ(s.size(), 5u);
+    EXPECT_EQ(s.size_bytes(), 20u);
+  }
+}
+
+TEST(CStringViewTest, Cstr) {
+  constexpr auto empty = cstring_view();
+  constexpr auto stuff = cstring_view("stuff");
+
+  static_assert(*stuff.c_str() == 's');
+
+  EXPECT_STREQ(empty.c_str(), "");
+  EXPECT_STREQ(stuff.c_str(), "stuff");
+}
+
+TEST(CStringViewTest, CopyConstuct) {
+  static_assert(std::is_trivially_copy_constructible_v<cstring_view>);
+
+  auto stuff = cstring_view("stuff");
+  auto other = stuff;
+  EXPECT_EQ(other.data(), stuff.data());
+  EXPECT_EQ(other.size(), stuff.size());
+}
+
+TEST(CStringViewTest, CopyAssign) {
+  static_assert(std::is_trivially_copy_assignable_v<cstring_view>);
+
+  auto empty = cstring_view();
+  auto stuff = cstring_view("stuff");
+  empty = stuff;
+  EXPECT_EQ(empty.data(), stuff.data());
+  EXPECT_EQ(empty.size(), stuff.size());
+}
+
+TEST(CStringViewTest, RemovePrefix) {
+  auto empty = cstring_view();
+  auto mod_empty = empty;
+  mod_empty.remove_prefix(0u);
+  EXPECT_EQ(mod_empty.data(), &empty[0u]);
+  EXPECT_EQ(mod_empty.size(), 0u);
+
+  auto stuff = cstring_view("stuff");
+  auto mod_stuff = stuff;
+  mod_stuff.remove_prefix(0u);
+  EXPECT_EQ(mod_stuff.data(), &stuff[0u]);
+  EXPECT_EQ(mod_stuff.size(), 5u);
+  mod_stuff.remove_prefix(2u);
+  EXPECT_EQ(mod_stuff.data(), &stuff[2u]);
+  EXPECT_EQ(mod_stuff.size(), 3u);
+  mod_stuff.remove_prefix(1u);
+  EXPECT_EQ(mod_stuff.data(), &stuff[3u]);
+  EXPECT_EQ(mod_stuff.size(), 2u);
+  mod_stuff.remove_prefix(2u);
+  EXPECT_EQ(mod_stuff.data(), &stuff[5u]);
+  EXPECT_EQ(mod_stuff.size(), 0u);
+
+  static_assert([] {
+    auto stuff = cstring_view("stuff");
+    stuff.remove_prefix(2u);
+    return stuff;
+  }() == "uff");
+
+  auto stuff16 = u16cstring_view(u"stuff");
+  auto mod_stuff16 = stuff16;
+  mod_stuff16.remove_prefix(2u);
+  EXPECT_EQ(mod_stuff16.data(), &stuff16[2u]);
+  EXPECT_EQ(mod_stuff16.size(), 3u);
+
+  auto stuff32 = u32cstring_view(U"stuff");
+  auto mod_stuff32 = stuff32;
+  mod_stuff32.remove_prefix(2u);
+  EXPECT_EQ(mod_stuff32.data(), &stuff32[2u]);
+  EXPECT_EQ(mod_stuff32.size(), 3u);
+
+#if BUILDFLAG(IS_WIN)
+  auto stuffw = wcstring_view(L"stuff");
+  auto mod_stuffw = stuffw;
+  mod_stuffw.remove_prefix(2u);
+  EXPECT_EQ(mod_stuffw.data(), &stuffw[2u]);
+  EXPECT_EQ(mod_stuffw.size(), 3u);
+#endif
+}
+
+TEST(CStringViewDeathTest, RemovePrefixChecked) {
+  auto empty = cstring_view();
+  BASE_EXPECT_DEATH(empty.remove_prefix(1u), "");
+
+  auto stuff = cstring_view("stuff");
+  BASE_EXPECT_DEATH(stuff.remove_prefix(6u), "");
+  stuff.remove_prefix(4u);
+  BASE_EXPECT_DEATH(stuff.remove_prefix(2u), "");
+}
+
+TEST(CStringViewTest, Swap) {
+  auto empty = cstring_view();
+  auto stuff = cstring_view("stuff");
+  empty.swap(stuff);
+  EXPECT_EQ(stuff, cstring_view(""));
+  EXPECT_EQ(empty, cstring_view("stuff"));
+
+  static_assert([] {
+    auto abc = cstring_view("abc");
+    auto ef = cstring_view("ef");
+    abc.swap(ef);
+    return ef;
+  }() == "abc");
+
+  auto one16 = u16cstring_view(u"one");
+  auto two16 = u16cstring_view(u"twotwo");
+  one16.swap(two16);
+  EXPECT_EQ(one16, u16cstring_view(u"twotwo"));
+  EXPECT_EQ(two16, u16cstring_view(u"one"));
+}
+
+TEST(CStringViewTest, Substr) {
+  auto substr = cstring_view("hello").substr(1u);
+  static_assert(std::same_as<std::string_view, decltype(substr)>);
+
+  static_assert(cstring_view("").substr(0u) == "");
+  static_assert(cstring_view("").substr(0u, 0u) == "");
+  static_assert(cstring_view("stuff").substr(0u) == "stuff");
+  static_assert(cstring_view("stuff").substr(0u, 2u) == "st");
+  static_assert(cstring_view("stuff").substr(2u) == "uff");
+  static_assert(cstring_view("stuff").substr(2u, 3u) == "uff");
+  static_assert(cstring_view("stuff").substr(2u, 1u) == "u");
+  static_assert(cstring_view("stuff").substr(2u, 0u) == "");
+
+  // `count` going off the end is clamped. Same as for string_view with
+  // hardening.
+  static_assert(cstring_view("stuff").substr(2u, 4u) == "uff");
+  static_assert(std::string_view("stuff").substr(2u, 4u) == "uff");
+}
+
+TEST(CStringViewDeathTest, SubstrBoundsChecked) {
+  auto use = [](auto x) { gurl_base::debug::Alias(&x); };
+
+  auto stuff = cstring_view("stuff");
+
+  // `pos` going off the end is CHECKed. Same as for string_view with hardening.
+  BASE_EXPECT_DEATH(use(stuff.substr(6u, 0u)), "");
+  BASE_EXPECT_DEATH(use(std::string_view("stuff").substr(6u, 0u)), "");
+  BASE_EXPECT_DEATH(use(stuff.substr(6u, 1u)), "");
+  BASE_EXPECT_DEATH(use(std::string_view("stuff").substr(6u, 1u)), "");
+}
+
+TEST(CStringViewTest, StartsWith) {
+  // Comparison with `const char*`.
+  static_assert(!cstring_view("").starts_with("hello"));
+  static_assert(cstring_view("").starts_with(""));
+  static_assert(cstring_view("hello").starts_with("hello"));
+  static_assert(cstring_view("hello").starts_with(""));
+  static_assert(cstring_view("hello").starts_with("he"));
+  static_assert(!cstring_view("hello").starts_with("ello"));
+  constexpr const char* query = "ello";
+  static_assert(!cstring_view("hello").starts_with(query));
+
+  static_assert(u16cstring_view(u"hello").starts_with(u"he"));
+  static_assert(u32cstring_view(U"hello").starts_with(U"he"));
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").starts_with(L"he"));
+#endif
+
+  // Comparison with `string/string_view/cstring_view`.
+  static_assert(cstring_view("hello").starts_with(std::string("he")));
+  static_assert(!cstring_view("hello").starts_with(std::string("el")));
+  static_assert(cstring_view("hello").starts_with(std::string_view("he")));
+  static_assert(!cstring_view("hello").starts_with(std::string_view("el")));
+  static_assert(cstring_view("hello").starts_with(cstring_view("he")));
+  static_assert(!cstring_view("hello").starts_with(cstring_view("el")));
+
+  static_assert(!cstring_view("hello").starts_with(std::string("hellos")));
+  static_assert(!cstring_view("hello").starts_with(std::string_view("hellos")));
+  static_assert(!cstring_view("hello").starts_with(cstring_view("hellos")));
+
+  static_assert(u16cstring_view(u"hello").starts_with(std::u16string(u"he")));
+  static_assert(u32cstring_view(U"hello").starts_with(std::u32string(U"he")));
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").starts_with(std::wstring(L"he")));
+#endif
+
+  // Comparison with a character.
+  static_assert(!cstring_view("").starts_with('h'));
+  static_assert(cstring_view("hello").starts_with('h'));
+  static_assert(!cstring_view("hello").starts_with('e'));
+
+  static_assert(u16cstring_view(u"hello").starts_with(u'h'));
+  static_assert(u32cstring_view(U"hello").starts_with(U'h'));
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").starts_with(L'h'));
+#endif
+}
+
+TEST(CStringViewTest, EndsWith) {
+  // Comparison with `const char*`.
+  static_assert(!cstring_view("").ends_with("hello"));
+  static_assert(cstring_view("").ends_with(""));
+  static_assert(cstring_view("hello").ends_with("hello"));
+  static_assert(cstring_view("hello").ends_with(""));
+  static_assert(cstring_view("hello").ends_with("lo"));
+  static_assert(!cstring_view("hello").ends_with("hel"));
+  constexpr const char* query = "hel";
+  static_assert(!cstring_view("hello").ends_with(query));
+
+  static_assert(u16cstring_view(u"hello").ends_with(u"lo"));
+  static_assert(u32cstring_view(U"hello").ends_with(U"lo"));
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").ends_with(L"lo"));
+#endif
+
+  // Comparison with `string/string_view/cstring_view`.
+  static_assert(cstring_view("hello").ends_with(std::string("lo")));
+  static_assert(!cstring_view("hello").ends_with(std::string("ell")));
+  static_assert(cstring_view("hello").ends_with(std::string_view("lo")));
+  static_assert(!cstring_view("hello").ends_with(std::string_view("ell")));
+  static_assert(cstring_view("hello").ends_with(cstring_view("lo")));
+  static_assert(!cstring_view("hello").ends_with(cstring_view("ell")));
+
+  static_assert(!cstring_view("hello").ends_with(std::string("shello")));
+  static_assert(!cstring_view("hello").ends_with(std::string_view("shello")));
+  static_assert(!cstring_view("hello").ends_with(cstring_view("shello")));
+
+  static_assert(u16cstring_view(u"hello").ends_with(std::u16string(u"lo")));
+  static_assert(u32cstring_view(U"hello").ends_with(std::u32string(U"lo")));
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").ends_with(std::wstring(L"lo")));
+#endif
+
+  // Comparison with a character.
+  static_assert(!cstring_view("").ends_with('h'));
+  static_assert(cstring_view("hello").ends_with('o'));
+  static_assert(!cstring_view("hello").ends_with('l'));
+
+  static_assert(u16cstring_view(u"hello").ends_with(u'o'));
+  static_assert(u32cstring_view(U"hello").ends_with(U'o'));
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").ends_with(L'o'));
+#endif
+}
+
+TEST(CStringViewTest, Find) {
+  // OOB `pos` will return npos. The NUL is never searched.
+  static_assert(cstring_view("hello").find('h', 1000u) == cstring_view::npos);
+  static_assert(cstring_view("hello").find('\0', 5u) == cstring_view::npos);
+
+  // Searching for a Char.
+  static_assert(cstring_view("hello").find('e') == 1u);
+  static_assert(cstring_view("hello").find('z') == cstring_view::npos);
+  static_assert(cstring_view("hello").find('l') == 2u);
+  static_assert(cstring_view("hello").find('l', 3u) == 3u);
+
+  static_assert(u16cstring_view(u"hello").find(u'e') == 1u);
+  static_assert(u16cstring_view(u"hello").find(u'z') == cstring_view::npos);
+  static_assert(u16cstring_view(u"hello").find(u'l') == 2u);
+  static_assert(u16cstring_view(u"hello").find(u'l', 3u) == 3u);
+
+  static_assert(u32cstring_view(U"hello").find(U'e') == 1u);
+  static_assert(u32cstring_view(U"hello").find(U'z') == cstring_view::npos);
+  static_assert(u32cstring_view(U"hello").find(U'l') == 2u);
+  static_assert(u32cstring_view(U"hello").find(U'l', 3u) == 3u);
+
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").find(L'e') == 1u);
+  static_assert(wcstring_view(L"hello").find(L'z') == cstring_view::npos);
+  static_assert(wcstring_view(L"hello").find(L'l') == 2u);
+  static_assert(wcstring_view(L"hello").find(L'l', 3u) == 3u);
+#endif
+
+  // Searching for a string.
+  static_assert(cstring_view("hello hello").find("lo") == 3u);
+  static_assert(cstring_view("hello hello").find("lol") == cstring_view::npos);
+  static_assert(u16cstring_view(u"hello hello").find(u"lo") == 3u);
+  static_assert(u16cstring_view(u"hello hello").find(u"lol") ==
+                cstring_view::npos);
+  static_assert(u32cstring_view(U"hello hello").find(U"lo") == 3u);
+  static_assert(u32cstring_view(U"hello hello").find(U"lol") ==
+                cstring_view::npos);
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello hello").find(L"lo") == 3u);
+  static_assert(wcstring_view(L"hello hello").find(L"lol") ==
+                cstring_view::npos);
+#endif
+}
+
+TEST(CStringViewTest, Rfind) {
+  // OOB `pos` will clamp to the end of the view. The NUL is never searched.
+  static_assert(cstring_view("hello").rfind('h', 0u) == 0u);
+  static_assert(cstring_view("hello").rfind('h', 1000u) == 0u);
+  static_assert(cstring_view("hello").rfind('\0', 5u) == cstring_view::npos);
+
+  // Searching for a Char.
+  static_assert(cstring_view("hello").rfind('e') == 1u);
+  static_assert(cstring_view("hello").rfind('z') == cstring_view::npos);
+  static_assert(cstring_view("hello").rfind('l') == 3u);
+  static_assert(cstring_view("hello").rfind('l', 2u) == 2u);
+
+  static_assert(u16cstring_view(u"hello").rfind(u'e') == 1u);
+  static_assert(u16cstring_view(u"hello").rfind(u'z') == cstring_view::npos);
+  static_assert(u16cstring_view(u"hello").rfind(u'l') == 3u);
+  static_assert(u16cstring_view(u"hello").rfind(u'l', 2u) == 2u);
+
+  static_assert(u32cstring_view(U"hello").rfind(U'e') == 1u);
+  static_assert(u32cstring_view(U"hello").rfind(U'z') == cstring_view::npos);
+  static_assert(u32cstring_view(U"hello").rfind(U'l') == 3u);
+  static_assert(u32cstring_view(U"hello").rfind(U'l', 2u) == 2u);
+
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").rfind(L'e') == 1u);
+  static_assert(wcstring_view(L"hello").rfind(L'z') == cstring_view::npos);
+  static_assert(wcstring_view(L"hello").rfind(L'l') == 3u);
+  static_assert(wcstring_view(L"hello").rfind(L'l', 2u) == 2u);
+#endif
+
+  // Searching for a string.
+  static_assert(cstring_view("hello hello").rfind("lo") == 9u);
+  static_assert(cstring_view("hello hello").rfind("lol") == cstring_view::npos);
+  static_assert(u16cstring_view(u"hello hello").rfind(u"lo") == 9u);
+  static_assert(u16cstring_view(u"hello hello").rfind(u"lol") ==
+                cstring_view::npos);
+  static_assert(u32cstring_view(U"hello hello").rfind(U"lo") == 9u);
+  static_assert(u32cstring_view(U"hello hello").rfind(U"lol") ==
+                cstring_view::npos);
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello hello").rfind(L"lo") == 9u);
+  static_assert(wcstring_view(L"hello hello").rfind(L"lol") ==
+                cstring_view::npos);
+#endif
+}
+
+TEST(CStringViewTest, FindFirstOf) {
+  // OOB `pos` will return npos. The NUL is never searched.
+  static_assert(cstring_view("hello").find_first_of('h', 1000u) ==
+                cstring_view::npos);
+  static_assert(cstring_view("hello").find_first_of('\0', 5u) ==
+                cstring_view::npos);
+
+  // Searching for a Char.
+  static_assert(cstring_view("hello").find_first_of('e') == 1u);
+  static_assert(cstring_view("hello").find_first_of('z') == cstring_view::npos);
+  static_assert(cstring_view("hello").find_first_of('l') == 2u);
+  static_assert(cstring_view("hello").find_first_of('l', 3u) == 3u);
+
+  static_assert(u16cstring_view(u"hello").find_first_of(u'e') == 1u);
+  static_assert(u16cstring_view(u"hello").find_first_of(u'z') ==
+                cstring_view::npos);
+  static_assert(u16cstring_view(u"hello").find_first_of(u'l') == 2u);
+  static_assert(u16cstring_view(u"hello").find_first_of(u'l', 3u) == 3u);
+
+  static_assert(u32cstring_view(U"hello").find_first_of(U'e') == 1u);
+  static_assert(u32cstring_view(U"hello").find_first_of(U'z') ==
+                cstring_view::npos);
+  static_assert(u32cstring_view(U"hello").find_first_of(U'l') == 2u);
+  static_assert(u32cstring_view(U"hello").find_first_of(U'l', 3u) == 3u);
+
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").find_first_of(L'e') == 1u);
+  static_assert(wcstring_view(L"hello").find_first_of(L'z') ==
+                cstring_view::npos);
+  static_assert(wcstring_view(L"hello").find_first_of(L'l') == 2u);
+  static_assert(wcstring_view(L"hello").find_first_of(L'l', 3u) == 3u);
+#endif
+
+  // Searching for a string.
+  static_assert(cstring_view("hello hello").find_first_of("ol") == 2u);
+  static_assert(cstring_view("hello hello").find_first_of("zz") ==
+                cstring_view::npos);
+  static_assert(u16cstring_view(u"hello hello").find_first_of(u"ol") == 2u);
+  static_assert(u16cstring_view(u"hello hello").find_first_of(u"zz") ==
+                cstring_view::npos);
+  static_assert(u32cstring_view(U"hello hello").find_first_of(U"ol") == 2u);
+  static_assert(u32cstring_view(U"hello hello").find_first_of(U"zz") ==
+                cstring_view::npos);
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello hello").find_first_of(L"ol") == 2u);
+  static_assert(wcstring_view(L"hello hello").find_first_of(L"zz") ==
+                cstring_view::npos);
+#endif
+}
+
+TEST(CStringViewTest, FindLastOf) {
+  // OOB `pos` will clamp to the end of the view. The NUL is never searched.
+  static_assert(cstring_view("hello").find_last_of('h', 0u) == 0u);
+  static_assert(cstring_view("hello").find_last_of('h', 1000u) == 0u);
+  static_assert(cstring_view("hello").find_last_of('\0', 5u) ==
+                cstring_view::npos);
+
+  // Searching for a Char.
+  static_assert(cstring_view("hello").find_last_of('e') == 1u);
+  static_assert(cstring_view("hello").find_last_of('z') == cstring_view::npos);
+  static_assert(cstring_view("hello").find_last_of('l') == 3u);
+  static_assert(cstring_view("hello").find_last_of('l', 2u) == 2u);
+
+  static_assert(u16cstring_view(u"hello").find_last_of(u'e') == 1u);
+  static_assert(u16cstring_view(u"hello").find_last_of(u'z') ==
+                cstring_view::npos);
+  static_assert(u16cstring_view(u"hello").find_last_of(u'l') == 3u);
+  static_assert(u16cstring_view(u"hello").find_last_of(u'l', 2u) == 2u);
+
+  static_assert(u32cstring_view(U"hello").find_last_of(U'e') == 1u);
+  static_assert(u32cstring_view(U"hello").find_last_of(U'z') ==
+                cstring_view::npos);
+  static_assert(u32cstring_view(U"hello").find_last_of(U'l') == 3u);
+  static_assert(u32cstring_view(U"hello").find_last_of(U'l', 2u) == 2u);
+
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").find_last_of(L'e') == 1u);
+  static_assert(wcstring_view(L"hello").find_last_of(L'z') ==
+                cstring_view::npos);
+  static_assert(wcstring_view(L"hello").find_last_of(L'l') == 3u);
+  static_assert(wcstring_view(L"hello").find_last_of(L'l', 2u) == 2u);
+#endif
+
+  // Searching for a string.
+  static_assert(cstring_view("hello hello").find_last_of("lo") == 10u);
+  static_assert(cstring_view("hello hello").find_last_of("zz") ==
+                cstring_view::npos);
+  static_assert(u16cstring_view(u"hello hello").find_last_of(u"lo") == 10u);
+  static_assert(u16cstring_view(u"hello hello").find_last_of(u"zz") ==
+                cstring_view::npos);
+  static_assert(u32cstring_view(U"hello hello").find_last_of(U"lo") == 10u);
+  static_assert(u32cstring_view(U"hello hello").find_last_of(U"zz") ==
+                cstring_view::npos);
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello hello").find_last_of(L"lo") == 10u);
+  static_assert(wcstring_view(L"hello hello").find_last_of(L"zz") ==
+                cstring_view::npos);
+#endif
+}
+
+TEST(CStringViewTest, FindFirstNotOf) {
+  // OOB `pos` will return npos. The NUL is never searched.
+  static_assert(cstring_view("hello").find_first_not_of('a', 1000u) ==
+                cstring_view::npos);
+  static_assert(cstring_view("hello").find_first_not_of('a', 5u) ==
+                cstring_view::npos);
+
+  // Searching for a Char.
+  static_assert(cstring_view("hello").find_first_not_of('h') == 1u);
+  static_assert(cstring_view("hello").find_first_not_of('e') == 0u);
+  static_assert(cstring_view("hello").find_first_not_of("eloh") ==
+                cstring_view::npos);
+
+  static_assert(u16cstring_view(u"hello").find_first_not_of(u'h') == 1u);
+  static_assert(u16cstring_view(u"hello").find_first_not_of(u'e') == 0u);
+  static_assert(u16cstring_view(u"hello").find_first_not_of(u"eloh") ==
+                cstring_view::npos);
+
+  static_assert(u32cstring_view(U"hello").find_first_not_of(U'h') == 1u);
+  static_assert(u32cstring_view(U"hello").find_first_not_of(U'e') == 0u);
+  static_assert(u32cstring_view(U"hello").find_first_not_of(U"eloh") ==
+                cstring_view::npos);
+
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").find_first_not_of(L'h') == 1u);
+  static_assert(wcstring_view(L"hello").find_first_not_of(L'e') == 0u);
+  static_assert(wcstring_view(L"hello").find_first_not_of(L"eloh") ==
+                cstring_view::npos);
+#endif
+
+  // Searching for a string.
+  static_assert(cstring_view("hello hello").find_first_not_of("eh") == 2u);
+  static_assert(cstring_view("hello hello").find_first_not_of("hello ") ==
+                cstring_view::npos);
+  static_assert(u16cstring_view(u"hello hello").find_first_not_of(u"eh") == 2u);
+  static_assert(u16cstring_view(u"hello hello").find_first_not_of(u"hello ") ==
+                cstring_view::npos);
+  static_assert(u32cstring_view(U"hello hello").find_first_not_of(U"eh") == 2u);
+  static_assert(u32cstring_view(U"hello hello").find_first_not_of(U"hello ") ==
+                cstring_view::npos);
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello hello").find_first_not_of(L"eh") == 2u);
+  static_assert(wcstring_view(L"hello hello").find_first_not_of(L"hello ") ==
+                cstring_view::npos);
+#endif
+}
+
+TEST(CStringViewTest, FindLastNotOf) {
+  // OOB `pos` will clamp to the end of the view. The NUL is never searched.
+  static_assert(cstring_view("hello").find_last_not_of('a', 1000u) == 4u);
+  static_assert(cstring_view("hello").find_last_not_of('a', 5u) == 4u);
+
+  // Searching for a Char.
+  static_assert(cstring_view("hello").find_last_not_of('l') == 4u);
+  static_assert(cstring_view("hello").find_last_not_of('o') == 3u);
+  static_assert(cstring_view("hello").find_last_not_of("eloh") ==
+                cstring_view::npos);
+
+  static_assert(u16cstring_view(u"hello").find_last_not_of(u'l') == 4u);
+  static_assert(u16cstring_view(u"hello").find_last_not_of(u'o') == 3u);
+  static_assert(u16cstring_view(u"hello").find_last_not_of(u"eloh") ==
+                cstring_view::npos);
+
+  static_assert(u32cstring_view(U"hello").find_last_not_of(U'l') == 4u);
+  static_assert(u32cstring_view(U"hello").find_last_not_of(U'o') == 3u);
+  static_assert(u32cstring_view(U"hello").find_last_not_of(U"eloh") ==
+                cstring_view::npos);
+
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello").find_last_not_of(L'l') == 4u);
+  static_assert(wcstring_view(L"hello").find_last_not_of(L'o') == 3u);
+  static_assert(wcstring_view(L"hello").find_last_not_of(L"eloh") ==
+                cstring_view::npos);
+#endif
+
+  // Searching for a string.
+  static_assert(cstring_view("hello hello").find_last_not_of("lo") == 7u);
+  static_assert(cstring_view("hello hello").find_last_not_of("hello ") ==
+                cstring_view::npos);
+  static_assert(u16cstring_view(u"hello hello").find_last_not_of(u"lo") == 7u);
+  static_assert(u16cstring_view(u"hello hello").find_last_not_of(u"hello ") ==
+                cstring_view::npos);
+  static_assert(u32cstring_view(U"hello hello").find_last_not_of(U"lo") == 7u);
+  static_assert(u32cstring_view(U"hello hello").find_last_not_of(U"hello ") ==
+                cstring_view::npos);
+#if BUILDFLAG(IS_WIN)
+  static_assert(wcstring_view(L"hello hello").find_last_not_of(L"lo") == 7u);
+  static_assert(wcstring_view(L"hello hello").find_last_not_of(L"hello ") ==
+                cstring_view::npos);
+#endif
+}
+
+TEST(CStringViewTest, ToString) {
+  // Streaming support like std::string_view.
+  std::ostringstream s;
+  s << cstring_view("hello");
+  EXPECT_EQ(s.str(), "hello");
+
+#if BUILDFLAG(IS_WIN)
+  std::wostringstream sw;
+  sw << wcstring_view(L"hello");
+  EXPECT_EQ(sw.str(), L"hello");
+#endif
+
+  // Gtest printing support.
+  EXPECT_EQ(testing::PrintToString(cstring_view("hello")), "hello");
+}
+
+TEST(CStringViewTest, Hash) {
+  [[maybe_unused]] auto s = std::hash<cstring_view>()(cstring_view("hello"));
+  static_assert(std::same_as<size_t, decltype(s)>);
+
+  [[maybe_unused]] auto s16 =
+      std::hash<u16cstring_view>()(u16cstring_view(u"hello"));
+  static_assert(std::same_as<size_t, decltype(s)>);
+
+  [[maybe_unused]] auto s32 =
+      std::hash<u32cstring_view>()(u32cstring_view(U"hello"));
+  static_assert(std::same_as<size_t, decltype(s)>);
+
+#if BUILDFLAG(IS_WIN)
+  [[maybe_unused]] auto sw =
+      std::hash<wcstring_view>()(wcstring_view(L"hello"));
+  static_assert(std::same_as<size_t, decltype(s)>);
+#endif
+}
+
+TEST(CStringViewTest, IntoStdStringView) {
+  // string_view implicitly constructs from const char*, and so also from
+  // cstring_view.
+  std::string_view sc = "hello";
+  std::string_view s = cstring_view("hello");
+  EXPECT_EQ(s, sc);
+
+  static_assert(std::string_view(cstring_view("hello")) == "hello");
+}
+
+TEST(CStringViewTest, IntoStdString) {
+  // string implicitly constructs from const char*, but not from
+  // std::string_view or cstring_view.
+  static_assert(std::convertible_to<const char*, std::string>);
+  static_assert(!std::convertible_to<std::string_view, std::string>);
+  static_assert(!std::convertible_to<cstring_view, std::string>);
+
+  static_assert(std::constructible_from<std::string, const char*>);
+  static_assert(std::constructible_from<std::string, std::string_view>);
+  static_assert(std::constructible_from<std::string, cstring_view>);
+
+  auto sv = std::string(std::string_view("hello"));
+  auto cs = std::string(cstring_view("hello"));
+  EXPECT_EQ(cs, sv);
+
+  static_assert(std::string(cstring_view("hello")) == "hello");
+}
+
+TEST(CStringViewTest, StringPlus) {
+  {
+    auto s = cstring_view("hello") + std::string("world");
+    static_assert(std::same_as<std::string, decltype(s)>);
+    EXPECT_EQ(s, "helloworld");
+  }
+  {
+    auto s = std::string("hello") + cstring_view("world");
+    static_assert(std::same_as<std::string, decltype(s)>);
+    EXPECT_EQ(s, "helloworld");
+  }
+  {
+    auto s = std::u16string(u"hello") + u16cstring_view(u"world");
+    static_assert(std::same_as<std::u16string, decltype(s)>);
+    EXPECT_EQ(s, u"helloworld");
+  }
+  {
+    auto s = std::u32string(U"hello") + u32cstring_view(U"world");
+    static_assert(std::same_as<std::u32string, decltype(s)>);
+    EXPECT_EQ(s, U"helloworld");
+  }
+  {
+#if BUILDFLAG(IS_WIN)
+    auto s = std::wstring(L"hello") + wcstring_view(L"world");
+    static_assert(std::same_as<std::wstring, decltype(s)>);
+    EXPECT_EQ(s, L"helloworld");
+#endif
+  }
+
+  // From lvalues.
+  {
+    auto h = cstring_view("hello");
+    auto w = std::string("world");
+    auto s = h + w;
+    static_assert(std::same_as<std::string, decltype(s)>);
+    EXPECT_EQ(s, "helloworld");
+  }
+
+  static_assert(cstring_view("hello") + std::string("world") == "helloworld");
+  static_assert(std::string("hello") + cstring_view("world") == "helloworld");
+}
+
+TEST(CStringViewTest, StringAppend) {
+  std::string s = "hello";
+  // string::append() can accept cstring_view like const char*.
+  s.append(cstring_view("world"));
+  EXPECT_EQ(s, "helloworld");
+}
+
+TEST(CStringViewTest, StringInsert) {
+  std::string s = "world";
+  // string::insert() can accept cstring_view like const char*.
+  s.insert(0u, cstring_view("hello"));
+  EXPECT_EQ(s, "helloworld");
+}
+
+TEST(CStringViewTest, StringReplace) {
+  std::string s = "goodbyeworld";
+  // string::replace() can accept cstring_view like const char*.
+  s.replace(0u, 7u, cstring_view("hello"));
+  EXPECT_EQ(s, "helloworld");
+}
+
+TEST(CStringViewTest, StringFind) {
+  const std::string s = "helloworld";
+  // string::find() can accept cstring_view like const char*.
+  EXPECT_EQ(s.find(cstring_view("owo")), 4u);
+}
+
+TEST(CStringViewTest, StringCompare) {
+  const std::string s = "hello";
+  // string::compare() can accept cstring_view like const char*.
+  EXPECT_EQ(s.compare(cstring_view("hello")), 0);
+  // string::operator== can accept cstring_view like const char*.
+  EXPECT_EQ(s, cstring_view("hello"));
+  // string::operator<=> can accept cstring_view like const char*.
+  EXPECT_EQ(s <=> cstring_view("hello"), std::weak_ordering::equivalent);
+  // string::operator<= etc can accept cstring_view like const char*. This
+  // follows from <=> normally but std::string has more overloads.
+  EXPECT_LE(s, cstring_view("hello"));
+}
+
+TEST(CStringViewTest, StringStartsEndsWith) {
+  const std::string s = "hello";
+  // string::starts_with() can accept cstring_view like const char*.
+  EXPECT_EQ(s.starts_with(cstring_view("hel")), true);
+  EXPECT_EQ(s.starts_with(cstring_view("lo")), false);
+  // string::ends_with() can accept cstring_view like const char*.
+  EXPECT_EQ(s.ends_with(cstring_view("hel")), false);
+  EXPECT_EQ(s.ends_with(cstring_view("lo")), true);
+}
+
+TEST(CStringViewTest, StrCat) {
+  EXPECT_EQ(gurl_base::StrCat({cstring_view("hello"), std::string_view("world")}),
+            "helloworld");
+}
+
+TEST(CStringViewTest, Example_CtorLiteral) {
+  const char kLiteral[] = "hello world";
+  auto s = gurl_base::cstring_view(kLiteral);
+  GURL_CHECK(s == "hello world");
+  auto s2 = gurl_base::cstring_view("this works too");
+  GURL_CHECK(s2 == "this works too");
+}
+
+TEST(CStringViewTest, CompatibleWithRanges) {
+  EXPECT_EQ(2, std::ranges::count(cstring_view("hello"), 'l'));
+}
+
+TEST(CStringViewTest, ConstructFromStringLiteralWithEmbeddedNul) {
+  const std::string s = "abc\0de";
+  constexpr std::string_view sv = "abc\0de";
+  constexpr gurl_base::cstring_view cv = "abc\0de";
+  EXPECT_EQ(s, std::string_view("abc"));
+  EXPECT_EQ(sv, std::string_view("abc"));
+  EXPECT_EQ(cv, std::string_view("abc"));
+
+  constexpr gurl_base::u16cstring_view cv16 = u"abc\0de";
+  EXPECT_EQ(cv16, std::u16string_view(u"abc"));
+  constexpr gurl_base::u32cstring_view cv32 = U"abc\0de";
+  EXPECT_EQ(cv32, std::u32string_view(U"abc"));
+#if BUILDFLAG(IS_WIN)
+  constexpr gurl_base::wcstring_view cvw = L"abc\0de";
+  EXPECT_EQ(cvw, std::wstring_view(L"abc"));
+#endif
+}
+
+}  // namespace
+}  // namespace base
diff --git a/base/strings/durable_string_view.h b/base/strings/durable_string_view.h
new file mode 100644
index 0000000..ad2532d
--- /dev/null
+++ b/base/strings/durable_string_view.h
@@ -0,0 +1,25 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef BASE_STRINGS_DURABLE_STRING_VIEW_H_
+#define BASE_STRINGS_DURABLE_STRING_VIEW_H_
+
+#include <string_view>
+
+#include "base/types/strong_alias.h"
+
+namespace gurl_base {
+
+// A strong type alias which denotes a std::string_view having durable storage.
+// This allows the programmer to declare that a particular string_view is over
+// memory that will not be deallocated. I.e., the programmer's use of this alias
+// is a promise that it is safe to read from within the memory bounds.
+//
+// While the underlying string view data can and should be considered const,
+// note that DurableStringView is unable to *guarantee* that underlying data
+// is truly const.
+using DurableStringView = gurl_base::StrongAlias<class DurableTag, std::string_view>;
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_DURABLE_STRING_VIEW_H_
diff --git a/base/strings/escape.cc b/base/strings/escape.cc
index 6bed700..9236ebe 100644
--- a/base/strings/escape.cc
+++ b/base/strings/escape.cc
@@ -4,13 +4,14 @@
 
 #include "base/strings/escape.h"
 
+#include <array>
 #include <ostream>
+#include <string_view>
 
 #include "polyfills/base/check_op.h"
-#include "polyfills/base/feature_list.h"
-#include "base/features.h"
+#include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversion_utils.h"
 #include "base/strings/utf_string_conversions.h"
@@ -26,10 +27,10 @@
 // Does quick bit-flicking to lookup needed characters.
 struct Charmap {
   bool Contains(unsigned char c) const {
-    return ((map[c >> 5] & (1 << (c & 31))) != 0);
+    return (map[c >> 5] & (1 << (c & 31))) != 0;
   }
 
-  uint32_t map[8];
+  std::array<uint32_t, 8> map;
 };
 
 // Given text to escape and a Charmap defining which values to escape,
@@ -37,7 +38,7 @@
 // to +, otherwise, if spaces are in the charmap, they are converted to
 // %20. And if keep_escaped is true, %XX will be kept as it is, otherwise, if
 // '%' is in the charmap, it is converted to %25.
-std::string Escape(StringPiece text,
+std::string Escape(std::string_view text,
                    const Charmap& charmap,
                    bool use_plus,
                    bool keep_escaped = false) {
@@ -65,7 +66,7 @@
 void AppendEscapedCharForHTMLImpl(typename str::value_type c, str* output) {
   static constexpr struct {
     char key;
-    StringPiece replacement;
+    std::string_view replacement;
   } kCharsToEscape[] = {
       {'<', "&lt;"},   {'>', "&gt;"},   {'&', "&amp;"},
       {'"', "&quot;"}, {'\'', "&#39;"},
@@ -159,7 +160,7 @@
 // not unescaped, to avoid turning a valid url according to spec into an
 // invalid one.
 // clang-format off
-const char kUrlUnescape[128] = {
+const std::array<char, 128> kUrlUnescape = {
 //   Null, control chars...
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -181,13 +182,15 @@
 // Attempts to unescape the sequence at |index| within |escaped_text|.  If
 // successful, sets |value| to the unescaped value.  Returns whether
 // unescaping succeeded.
-bool UnescapeUnsignedByteAtIndex(StringPiece escaped_text,
+bool UnescapeUnsignedByteAtIndex(std::string_view escaped_text,
                                  size_t index,
                                  unsigned char* value) {
-  if ((index + 2) >= escaped_text.size())
+  if ((index + 2) >= escaped_text.size()) {
     return false;
-  if (escaped_text[index] != '%')
+  }
+  if (escaped_text[index] != '%') {
     return false;
+  }
   char most_sig_digit(escaped_text[index + 1]);
   char least_sig_digit(escaped_text[index + 2]);
   if (IsHexDigit(most_sig_digit) && IsHexDigit(least_sig_digit)) {
@@ -203,15 +206,16 @@
 // the character's code point and |unescaped_out| to be the unescaped UTF-8
 // string. |unescaped_out| will always be 1/3rd the length of the substring of
 // |escaped_text| that corresponds to the unescaped character.
-bool UnescapeUTF8CharacterAtIndex(StringPiece escaped_text,
+bool UnescapeUTF8CharacterAtIndex(std::string_view escaped_text,
                                   size_t index,
                                   base_icu::UChar32* code_point_out,
                                   std::string* unescaped_out) {
   GURL_DCHECK(unescaped_out->empty());
 
-  unsigned char bytes[CBU8_MAX_LENGTH];
-  if (!UnescapeUnsignedByteAtIndex(escaped_text, index, &bytes[0]))
+  std::array<unsigned char, CBU8_MAX_LENGTH> bytes{};
+  if (!UnescapeUnsignedByteAtIndex(escaped_text, index, &bytes[0])) {
     return false;
+  }
 
   size_t num_bytes = 1;
 
@@ -229,18 +233,19 @@
     }
   }
 
+  gurl_base::span<char> bytes_span = gurl_base::as_writable_chars(gurl_base::span(bytes));
   size_t char_index = 0;
   // Check if the unicode "character" that was just unescaped is valid.
-  if (!ReadUnicodeCharacter(reinterpret_cast<char*>(bytes), num_bytes,
-                            &char_index, code_point_out)) {
+  if (!ReadUnicodeCharacter(bytes_span.data(), num_bytes, &char_index,
+                            code_point_out)) {
     return false;
   }
 
   // It's possible that a prefix of |bytes| forms a valid UTF-8 character,
   // and the rest are not valid UTF-8, so need to update |num_bytes| based
   // on the result of ReadUnicodeCharacter().
-  num_bytes = char_index + 1;
-  *unescaped_out = std::string(reinterpret_cast<char*>(bytes), num_bytes);
+  bytes_span = bytes_span.first(char_index + 1);
+  *unescaped_out = std::string(bytes_span.begin(), bytes_span.end());
   return true;
 }
 
@@ -266,7 +271,7 @@
   //
   // Can't use icu to make this cleaner, because Cronet cannot depend on
   // icu, and currently uses this file.
-  // TODO(https://crbug.com/829873): Try to make this use icu, both to
+  // TODO(crbug.com/41381359): Try to make this use icu, both to
   // protect against regressions as the Unicode standard is updated and to
   // reduce the number of long lists of characters.
   return !(
@@ -382,14 +387,16 @@
 // character.  The resulting |adjustments| will always be sorted by increasing
 // offset.
 std::string UnescapeURLWithAdjustmentsImpl(
-    StringPiece escaped_text,
+    std::string_view escaped_text,
     UnescapeRule::Type rules,
     OffsetAdjuster::Adjustments* adjustments) {
-  if (adjustments)
+  if (adjustments) {
     adjustments->clear();
+  }
   // Do not unescape anything, return the |escaped_text| text.
-  if (rules == UnescapeRule::NONE)
+  if (rules == UnescapeRule::NONE) {
     return std::string(escaped_text);
+  }
 
   // The output of the unescaping is always smaller than the input, so we can
   // reserve the input size to make sure we have enough buffer and don't have
@@ -398,23 +405,25 @@
   result.reserve(escaped_text.length());
 
   // Locations of adjusted text.
+  std::string unescaped;
   for (size_t i = 0, max = escaped_text.size(); i < max;) {
     // Try to unescape the character.
     base_icu::UChar32 code_point;
-    std::string unescaped;
+    unescaped.clear();
     if (!UnescapeUTF8CharacterAtIndex(escaped_text, i, &code_point,
                                       &unescaped)) {
       // Check if the next character can be unescaped, but not as a valid UTF-8
       // character. In that case, just unescaped and write the non-sense
       // character.
       //
-      // TODO(https://crbug.com/829868): Do not unescape illegal UTF-8
+      // TODO(crbug.com/40570496): Do not unescape illegal UTF-8
       // sequences.
       unsigned char non_utf8_byte;
       if (UnescapeUnsignedByteAtIndex(escaped_text, i, &non_utf8_byte)) {
         result.push_back(static_cast<char>(non_utf8_byte));
-        if (adjustments)
-          adjustments->push_back(OffsetAdjuster::Adjustment(i, 3, 1));
+        if (adjustments) {
+          adjustments->emplace_back(i, 3, 1);
+        }
         i += 3;
         continue;
       }
@@ -445,7 +454,7 @@
     result.append(unescaped);
     if (adjustments) {
       for (size_t j = 0; j < unescaped.length(); ++j) {
-        adjustments->push_back(OffsetAdjuster::Adjustment(i + j * 3, 3, 1));
+        adjustments->emplace_back(i + j * 3, 3, 1);
       }
     }
     i += 3 * unescaped.length();
@@ -456,37 +465,37 @@
 
 }  // namespace
 
-std::string EscapeAllExceptUnreserved(StringPiece text) {
+std::string EscapeAllExceptUnreserved(std::string_view text) {
   return Escape(text, kUnreservedCharmap, false);
 }
 
-std::string EscapeQueryParamValue(StringPiece text, bool use_plus) {
+std::string EscapeQueryParamValue(std::string_view text, bool use_plus) {
   return Escape(text, kQueryCharmap, use_plus);
 }
 
-std::string EscapePath(StringPiece path) {
+std::string EscapePath(std::string_view path) {
   return Escape(path, kPathCharmap, false);
 }
 
 #if BUILDFLAG(IS_APPLE)
-std::string EscapeNSURLPrecursor(StringPiece precursor) {
+std::string EscapeNSURLPrecursor(std::string_view precursor) {
   return Escape(precursor, kNSURLCharmap, false, true);
 }
 #endif  // BUILDFLAG(IS_APPLE)
 
-std::string EscapeUrlEncodedData(StringPiece path, bool use_plus) {
+std::string EscapeUrlEncodedData(std::string_view path, bool use_plus) {
   return Escape(path, kUrlEscape, use_plus);
 }
 
-std::string EscapeNonASCIIAndPercent(StringPiece input) {
+std::string EscapeNonASCIIAndPercent(std::string_view input) {
   return Escape(input, kNonASCIICharmapAndPercent, false);
 }
 
-std::string EscapeNonASCII(StringPiece input) {
+std::string EscapeNonASCII(std::string_view input) {
   return Escape(input, kNonASCIICharmap, false);
 }
 
-std::string EscapeExternalHandlerValue(StringPiece text) {
+std::string EscapeExternalHandlerValue(std::string_view text) {
   return Escape(text, kExternalHandlerCharmap, false, true);
 }
 
@@ -494,21 +503,21 @@
   AppendEscapedCharForHTMLImpl(c, output);
 }
 
-std::string EscapeForHTML(StringPiece input) {
+std::string EscapeForHTML(std::string_view input) {
   return EscapeForHTMLImpl(input);
 }
 
-std::u16string EscapeForHTML(StringPiece16 input) {
+std::u16string EscapeForHTML(std::u16string_view input) {
   return EscapeForHTMLImpl(input);
 }
 
-std::string UnescapeURLComponent(StringPiece escaped_text,
+std::string UnescapeURLComponent(std::string_view escaped_text,
                                  UnescapeRule::Type rules) {
   return UnescapeURLWithAdjustmentsImpl(escaped_text, rules, nullptr);
 }
 
 std::u16string UnescapeAndDecodeUTF8URLComponentWithAdjustments(
-    StringPiece text,
+    std::string_view text,
     UnescapeRule::Type rules,
     OffsetAdjuster::Adjustments* adjustments) {
   std::u16string result;
@@ -528,31 +537,20 @@
   return UTF8ToUTF16WithAdjustments(text, adjustments);
 }
 
-std::string UnescapeBinaryURLComponent(StringPiece escaped_text,
+std::string UnescapeBinaryURLComponent(std::string_view escaped_text,
                                        UnescapeRule::Type rules) {
   // Only NORMAL and REPLACE_PLUS_WITH_SPACE are supported.
   GURL_DCHECK(rules != UnescapeRule::NONE);
   GURL_DCHECK(!(rules &
            ~(UnescapeRule::NORMAL | UnescapeRule::REPLACE_PLUS_WITH_SPACE)));
 
-  // It is not possible to read the feature state when this function is invoked
-  // before FeatureList initialization. In that case, fallback to the feature's
-  // default state.
-  //
-  // TODO(crbug.com/1321924): Cleanup this feature.
-  const bool optimize_data_urls_feature_is_enabled =
-      gurl_base::FeatureList::GetInstance()
-          ? gurl_base::FeatureList::IsEnabled(features::kOptimizeDataUrls)
-          : features::kOptimizeDataUrls.default_state ==
-                gurl_base::FEATURE_ENABLED_BY_DEFAULT;
-
   // If there are no '%' characters in the string, there will be nothing to
   // unescape, so we can take the fast path.
-  if (optimize_data_urls_feature_is_enabled &&
-      escaped_text.find('%') == StringPiece::npos) {
+  if (escaped_text.find('%') == std::string_view::npos) {
     std::string unescaped_text(escaped_text);
-    if (rules & UnescapeRule::REPLACE_PLUS_WITH_SPACE)
+    if (rules & UnescapeRule::REPLACE_PLUS_WITH_SPACE) {
       std::replace(unescaped_text.begin(), unescaped_text.end(), '+', ' ');
+    }
     return unescaped_text;
   }
 
@@ -593,7 +591,7 @@
   return unescaped_text;
 }
 
-bool UnescapeBinaryURLComponentSafe(StringPiece escaped_text,
+bool UnescapeBinaryURLComponentSafe(std::string_view escaped_text,
                                     bool fail_on_path_separators,
                                     std::string* unescaped_text) {
   unescaped_text->clear();
@@ -606,22 +604,24 @@
     illegal_encoded_bytes.insert('/');
     illegal_encoded_bytes.insert('\\');
   }
-  if (ContainsEncodedBytes(escaped_text, illegal_encoded_bytes))
+  if (ContainsEncodedBytes(escaped_text, illegal_encoded_bytes)) {
     return false;
+  }
 
   *unescaped_text = UnescapeBinaryURLComponent(escaped_text);
   return true;
 }
 
-bool ContainsEncodedBytes(StringPiece escaped_text,
+bool ContainsEncodedBytes(std::string_view escaped_text,
                           const std::set<unsigned char>& bytes) {
   for (size_t i = 0, max = escaped_text.size(); i < max;) {
     unsigned char byte;
     // UnescapeUnsignedByteAtIndex does bounds checking, so this is always safe
     // to call.
     if (UnescapeUnsignedByteAtIndex(escaped_text, i, &byte)) {
-      if (bytes.find(byte) != bytes.end())
+      if (bytes.find(byte) != bytes.end()) {
         return true;
+      }
 
       i += 3;
       continue;
@@ -633,27 +633,32 @@
   return false;
 }
 
-std::u16string UnescapeForHTML(StringPiece16 input) {
-  static const struct {
+std::u16string UnescapeForHTML(std::u16string_view input) {
+  struct EscapeToChars {
     const char* ampersand_code;
     const char16_t replacement;
-  } kEscapeToChars[] = {
-      {"&lt;", '<'},   {"&gt;", '>'},   {"&amp;", '&'},
-      {"&quot;", '"'}, {"&#39;", '\''},
   };
+  static const auto kEscapeToChars = std::to_array<EscapeToChars>({
+      {"&lt;", '<'},
+      {"&gt;", '>'},
+      {"&amp;", '&'},
+      {"&quot;", '"'},
+      {"&#39;", '\''},
+  });
   constexpr size_t kEscapeToCharsCount = std::size(kEscapeToChars);
 
-  if (input.find(u"&") == std::string::npos)
+  if (input.find(u"&") == std::string::npos) {
     return std::u16string(input);
+  }
 
-  std::u16string ampersand_chars[kEscapeToCharsCount];
+  std::array<std::u16string, kEscapeToCharsCount> ampersand_chars;
   std::u16string text(input);
   for (std::u16string::iterator iter = text.begin(); iter != text.end();
        ++iter) {
     if (*iter == '&') {
       // Potential ampersand encode char.
       size_t index = static_cast<size_t>(iter - text.begin());
-      for (size_t i = 0; i < std::size(kEscapeToChars); i++) {
+      for (size_t i = 0; i < kEscapeToCharsCount; i++) {
         if (ampersand_chars[i].empty()) {
           ampersand_chars[i] = ASCIIToUTF16(kEscapeToChars[i].ampersand_code);
         }
diff --git a/base/strings/escape.h b/base/strings/escape.h
index 9943312..449797c 100644
--- a/base/strings/escape.h
+++ b/base/strings/escape.h
@@ -9,9 +9,9 @@
 
 #include <set>
 #include <string>
+#include <string_view>
 
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
 #include "base/strings/utf_offset_string_conversions.h"
 #include "build/build_config.h"
 
@@ -21,51 +21,53 @@
 
 // Escapes all characters except unreserved characters. Unreserved characters,
 // as defined in RFC 3986, include alphanumerics and -._~
-BASE_EXPORT std::string EscapeAllExceptUnreserved(StringPiece text);
+BASE_EXPORT std::string EscapeAllExceptUnreserved(std::string_view text);
 
 // Escapes characters in text suitable for use as a query parameter value.
 // We %XX everything except alphanumerics and -_.!~*'()
 // Spaces change to "+" unless you pass usePlus=false.
 // This is basically the same as encodeURIComponent in javascript.
-BASE_EXPORT std::string EscapeQueryParamValue(StringPiece text, bool use_plus);
+BASE_EXPORT std::string EscapeQueryParamValue(std::string_view text,
+                                              bool use_plus);
 
 // Escapes a partial or complete file/pathname.  This includes:
 // non-printable, non-7bit, and (including space)  "#%:<>?[\]^`{|}
-BASE_EXPORT std::string EscapePath(StringPiece path);
+BASE_EXPORT std::string EscapePath(std::string_view path);
 
 #if BUILDFLAG(IS_APPLE)
 // Escapes characters as per expectations of NSURL. This includes:
 // non-printable, non-7bit, and (including space)  "#%<>[\]^`{|}
-BASE_EXPORT std::string EscapeNSURLPrecursor(StringPiece precursor);
+BASE_EXPORT std::string EscapeNSURLPrecursor(std::string_view precursor);
 #endif  // BUILDFLAG(IS_APPLE)
 
 // Escapes application/x-www-form-urlencoded content.  This includes:
 // non-printable, non-7bit, and (including space)  ?>=<;+'&%$#"![\]^`{|}
 // Space is escaped as + (if use_plus is true) and other special characters
 // as %XX (hex).
-BASE_EXPORT std::string EscapeUrlEncodedData(StringPiece path, bool use_plus);
+BASE_EXPORT std::string EscapeUrlEncodedData(std::string_view path,
+                                             bool use_plus);
 
 // Escapes all non-ASCII input, as well as escaping % to %25.
-BASE_EXPORT std::string EscapeNonASCIIAndPercent(StringPiece input);
+BASE_EXPORT std::string EscapeNonASCIIAndPercent(std::string_view input);
 
 // Escapes all non-ASCII input. Note this function leaves % unescaped, which
 // means the unescaping the resulting string will not give back the original
 // input.
-BASE_EXPORT std::string EscapeNonASCII(StringPiece input);
+BASE_EXPORT std::string EscapeNonASCII(std::string_view input);
 
 // Escapes characters in text suitable for use as an external protocol handler
 // command.
 // We %XX everything except alphanumerics and -_.!~*'() and the restricted
 // characters (;/?:@&=+$,#[]) and a valid percent escape sequence (%XX).
-BASE_EXPORT std::string EscapeExternalHandlerValue(StringPiece text);
+BASE_EXPORT std::string EscapeExternalHandlerValue(std::string_view text);
 
 // Appends the given character to the output string, escaping the character if
 // the character would be interpreted as an HTML delimiter.
 BASE_EXPORT void AppendEscapedCharForHTML(char c, std::string* output);
 
 // Escapes chars that might cause this text to be interpreted as HTML tags.
-BASE_EXPORT std::string EscapeForHTML(StringPiece text);
-BASE_EXPORT std::u16string EscapeForHTML(StringPiece16 text);
+BASE_EXPORT std::string EscapeForHTML(std::string_view text);
+BASE_EXPORT std::u16string EscapeForHTML(std::u16string_view text);
 
 // Unescaping ------------------------------------------------------------------
 
@@ -122,7 +124,7 @@
 // UTF-8, they could be used to mislead the user. Callers that want to
 // unconditionally unescape everything for uses other than displaying data to
 // the user should use UnescapeBinaryURLComponent().
-BASE_EXPORT std::string UnescapeURLComponent(StringPiece escaped_text,
+BASE_EXPORT std::string UnescapeURLComponent(std::string_view escaped_text,
                                              UnescapeRule::Type rules);
 
 // Unescapes the given substring as a URL, and then tries to interpret the
@@ -132,7 +134,7 @@
 // information on how the original string was adjusted to get the string
 // returned.
 BASE_EXPORT std::u16string UnescapeAndDecodeUTF8URLComponentWithAdjustments(
-    StringPiece text,
+    std::string_view text,
     UnescapeRule::Type rules,
     OffsetAdjuster::Adjustments* adjustments);
 
@@ -143,7 +145,7 @@
 //
 // Only the NORMAL and REPLACE_PLUS_WITH_SPACE rules are allowed.
 BASE_EXPORT std::string UnescapeBinaryURLComponent(
-    StringPiece escaped_text,
+    std::string_view escaped_text,
     UnescapeRule::Type rules = UnescapeRule::NORMAL);
 
 // Variant of UnescapeBinaryURLComponent().  Writes output to |unescaped_text|.
@@ -153,7 +155,7 @@
 // CRLF but not space), and optionally path separators. Path separators include
 // both forward and backward slashes on all platforms. Does not fail if any of
 // those characters appear unescaped in the input string.
-BASE_EXPORT bool UnescapeBinaryURLComponentSafe(StringPiece escaped_text,
+BASE_EXPORT bool UnescapeBinaryURLComponentSafe(std::string_view escaped_text,
                                                 bool fail_on_path_separators,
                                                 std::string* unescaped_text);
 
@@ -163,12 +165,12 @@
 // For example, if |bytes| is {'%', '/'}, returns true if |escaped_text|
 // contains "%25" or "%2F", but not if it just contains bare '%' or '/'
 // characters.
-BASE_EXPORT bool ContainsEncodedBytes(StringPiece escaped_text,
+BASE_EXPORT bool ContainsEncodedBytes(std::string_view escaped_text,
                                       const std::set<unsigned char>& bytes);
 
 // Unescapes the following ampersand character codes from |text|:
 // &lt; &gt; &amp; &quot; &#39;
-BASE_EXPORT std::u16string UnescapeForHTML(StringPiece16 text);
+BASE_EXPORT std::u16string UnescapeForHTML(std::u16string_view text);
 
 }  // namespace base
 
diff --git a/base/strings/escape_unittest.cc b/base/strings/escape_unittest.cc
index ec37ed1..860beff 100644
--- a/base/strings/escape_unittest.cc
+++ b/base/strings/escape_unittest.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/strings/escape.h"
+
 #include <algorithm>
 #include <string>
 
-#include "base/strings/escape.h"
-
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
diff --git a/base/strings/latin1_string_conversions.cc b/base/strings/latin1_string_conversions.cc
index 43f0dc0..8ee775a 100644
--- a/base/strings/latin1_string_conversions.cc
+++ b/base/strings/latin1_string_conversions.cc
@@ -4,16 +4,20 @@
 
 #include "base/strings/latin1_string_conversions.h"
 
+#include "base/compiler_specific.h"
+
 namespace gurl_base {
 
 std::u16string Latin1OrUTF16ToUTF16(size_t length,
                                     const Latin1Char* latin1,
                                     const char16_t* utf16) {
-  if (!length)
+  if (!length) {
     return std::u16string();
-  if (latin1)
-    return std::u16string(latin1, latin1 + length);
-  return std::u16string(utf16, utf16 + length);
+  }
+  if (latin1) {
+    return UNSAFE_TODO(std::u16string(latin1, latin1 + length));
+  }
+  return UNSAFE_TODO(std::u16string(utf16, utf16 + length));
 }
 
 }  // namespace base
diff --git a/base/strings/latin1_string_conversions.h b/base/strings/latin1_string_conversions.h
index 83f8ef2..584f146 100644
--- a/base/strings/latin1_string_conversions.h
+++ b/base/strings/latin1_string_conversions.h
@@ -25,6 +25,10 @@
 // array to std::u16string. This function is defined here rather than in
 // WebString.h to avoid binary bloat in all the callers of the conversion
 // operator.
+// TODO(crbug.com/40284755): implement spanified version.
+// BASE_EXPORT std::u16string Latin1OrUTF16ToUTF16(
+//     gurl_base::span<const Latin1Char> latin1,
+//     gurl_base::span<const char16_t> utf16);
 BASE_EXPORT std::u16string Latin1OrUTF16ToUTF16(size_t length,
                                                 const Latin1Char* latin1,
                                                 const char16_t* utf16);
diff --git a/base/strings/pattern.cc b/base/strings/pattern.cc
index 23644e3..b69f7a4 100644
--- a/base/strings/pattern.cc
+++ b/base/strings/pattern.cc
@@ -4,6 +4,9 @@
 
 #include "base/strings/pattern.h"
 
+#include <string_view>
+
+#include "base/compiler_specific.h"
 #include "base/third_party/icu/icu_utf.h"
 
 namespace gurl_base {
@@ -32,12 +35,14 @@
     if (*pattern == pattern_end) {
       // If this is the end of the pattern, only accept the end of the string;
       // anything else falls through to the mismatch case.
-      if (*string == string_end)
+      if (*string == string_end) {
         return true;
+      }
     } else {
       // If we have found a wildcard, we're done.
-      if (!escape && IsWildcard(**pattern))
+      if (!escape && IsWildcard(**pattern)) {
         return true;
+      }
 
       // Check if the escape character is found. If so, skip it and move to the
       // next character.
@@ -49,8 +54,9 @@
 
       escape = false;
 
-      if (*string == string_end)
+      if (*string == string_end) {
         return false;
+      }
 
       // Check if the chars match, if so, increment the ptrs.
       const CHAR* pattern_next = *pattern;
@@ -70,8 +76,9 @@
     // TODO(bauerb): This is a naive implementation of substring search, which
     // could be implemented with a more efficient algorithm, e.g.
     // Knuth-Morris-Pratt (at the expense of requiring preprocessing).
-    if (maximum_distance == 0)
+    if (maximum_distance == 0) {
       return false;
+    }
 
     // Because unlimited distance is represented as -1, this will never reach 0
     // and therefore fail the match above.
@@ -124,8 +131,10 @@
   base_icu::UChar32 operator()(const char** p, const char* end) {
     base_icu::UChar32 c;
     int offset = 0;
-    CBU8_NEXT(reinterpret_cast<const uint8_t*>(*p), offset, end - *p, c);
-    *p += offset;
+    UNSAFE_TODO({
+      CBU8_NEXT(reinterpret_cast<const uint8_t*>(*p), offset, end - *p, c);
+      *p += offset;
+    });
     return c;
   }
 };
@@ -134,22 +143,26 @@
   base_icu::UChar32 operator()(const char16_t** p, const char16_t* end) {
     base_icu::UChar32 c;
     int offset = 0;
-    CBU16_NEXT(*p, offset, end - *p, c);
-    *p += offset;
+    UNSAFE_TODO({
+      CBU16_NEXT(*p, offset, end - *p, c);
+      *p += offset;
+    });
     return c;
   }
 };
 
 }  // namespace
 
-bool MatchPattern(StringPiece eval, StringPiece pattern) {
-  return MatchPatternT(eval.data(), eval.data() + eval.size(), pattern.data(),
-                       pattern.data() + pattern.size(), NextCharUTF8());
+bool MatchPattern(std::string_view eval, std::string_view pattern) {
+  return MatchPatternT(
+      eval.data(), UNSAFE_TODO(eval.data() + eval.size()), pattern.data(),
+      UNSAFE_TODO(pattern.data() + pattern.size()), NextCharUTF8());
 }
 
-bool MatchPattern(StringPiece16 eval, StringPiece16 pattern) {
-  return MatchPatternT(eval.data(), eval.data() + eval.size(), pattern.data(),
-                       pattern.data() + pattern.size(), NextCharUTF16());
+bool MatchPattern(std::u16string_view eval, std::u16string_view pattern) {
+  return MatchPatternT(
+      eval.data(), UNSAFE_TODO(eval.data() + eval.size()), pattern.data(),
+      UNSAFE_TODO(pattern.data() + pattern.size()), NextCharUTF16());
 }
 
 }  // namespace base
diff --git a/base/strings/pattern.h b/base/strings/pattern.h
index 52e25a2..dbb6a82 100644
--- a/base/strings/pattern.h
+++ b/base/strings/pattern.h
@@ -5,8 +5,9 @@
 #ifndef BASE_STRINGS_PATTERN_H_
 #define BASE_STRINGS_PATTERN_H_
 
+#include <string_view>
+
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 
@@ -15,8 +16,10 @@
 //
 // The backslash character (\) is an escape character for * and ?.
 // ? matches 0 or 1 character, while * matches 0 or more characters.
-BASE_EXPORT bool MatchPattern(StringPiece string, StringPiece pattern);
-BASE_EXPORT bool MatchPattern(StringPiece16 string, StringPiece16 pattern);
+BASE_EXPORT bool MatchPattern(std::string_view string,
+                              std::string_view pattern);
+BASE_EXPORT bool MatchPattern(std::u16string_view string,
+                              std::u16string_view pattern);
 
 }  // namespace base
 
diff --git a/base/strings/pattern_unittest.cc b/base/strings/pattern_unittest.cc
index 4797de6..9fb3936 100644
--- a/base/strings/pattern_unittest.cc
+++ b/base/strings/pattern_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/strings/pattern.h"
+
 #include "base/strings/utf_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/base/strings/safe_sprintf.cc b/base/strings/safe_sprintf.cc
index 690abc9..e0daa4e 100644
--- a/base/strings/safe_sprintf.cc
+++ b/base/strings/safe_sprintf.cc
@@ -10,6 +10,7 @@
 #include <algorithm>
 #include <limits>
 
+#include "base/compiler_specific.h"
 #include "polyfills/base/memory/raw_ptr.h"
 #include "build/build_config.h"
 
@@ -38,11 +39,14 @@
 #include "polyfills/base/check.h"
 #define DEBUG_CHECK RAW_CHECK
 #else
-#define DEBUG_CHECK(x) do { if (x) { } } while (0)
+#define DEBUG_CHECK(x) \
+  do {                 \
+    if (x) {           \
+    }                  \
+  } while (0)
 #endif
 
-namespace gurl_base {
-namespace strings {
+namespace gurl_base::strings {
 
 // The code in this file is extremely careful to be async-signal-safe.
 //
@@ -69,9 +73,9 @@
 namespace {
 const size_t kSSizeMaxConst = ((size_t)(ssize_t)-1) >> 1;
 
-const char kUpCaseHexDigits[]   = "0123456789ABCDEF";
+const char kUpCaseHexDigits[] = "0123456789ABCDEF";
 const char kDownCaseHexDigits[] = "0123456789abcdef";
-}
+}  // namespace
 
 #if defined(NDEBUG)
 // We would like to define kSSizeMax as std::numeric_limits<ssize_t>::max(),
@@ -81,7 +85,7 @@
 namespace {
 const size_t kSSizeMax = kSSizeMaxConst;
 }
-#else  // defined(NDEBUG)
+#else   // defined(NDEBUG)
 // For efficiency, we really need kSSizeMax to be a constant. But for unit
 // tests, it should be adjustable. This allows us to verify edge cases without
 // having to fill the entire available address space. As a compromise, we make
@@ -99,7 +103,7 @@
 size_t GetSafeSPrintfSSizeMaxForTest() {
   return kSSizeMax;
 }
-}
+}  // namespace internal
 #endif  // defined(NDEBUG)
 
 namespace {
@@ -110,10 +114,7 @@
   // to ensure that the buffer is at least one byte in size, so that it fits
   // the trailing NUL that will be added by the destructor. The buffer also
   // must be smaller or equal to kSSizeMax in size.
-  Buffer(char* buffer, size_t size)
-      : buffer_(buffer),
-        size_(size - 1),  // Account for trailing NUL byte
-        count_(0) {
+  Buffer(char* buffer, size_t size) : buffer_(buffer), size_(size - 1) {
 // MSVS2013's standard library doesn't mark max() as constexpr yet. cl.exe
 // supports static_cast but doesn't really implement constexpr yet so it doesn't
 // complain, but clang does.
@@ -163,7 +164,7 @@
   // have been allocated for the |buffer_|.
   inline bool Out(char ch) {
     if (size_ >= 1 && count_ < size_) {
-      buffer_[count_] = ch;
+      UNSAFE_TODO(buffer_[count_] = ch);
       return IncrementCountByOne();
     }
     // |count_| still needs to be updated, even if the buffer has been
@@ -185,7 +186,7 @@
     for (; padding > len; --padding) {
       if (!Out(pad)) {
         if (--padding) {
-          IncrementCount(padding-len);
+          IncrementCount(padding - len);
         }
         return false;
       }
@@ -251,9 +252,7 @@
   }
 
   // Convenience method for the common case of incrementing |count_| by one.
-  inline bool IncrementCountByOne() {
-    return IncrementCount(1);
-  }
+  inline bool IncrementCountByOne() { return IncrementCount(1); }
 
   // Return the current insertion point into the buffer. This is typically
   // at |buffer_| + |count_|, but could be before that if truncation
@@ -264,7 +263,8 @@
     if (idx > size_) {
       idx = size_;
     }
-    return buffer_ + idx;
+    // SAFETY: idx checked against size_ above.
+    return UNSAFE_BUFFERS(buffer_ + idx);
   }
 
   // User-provided buffer that will receive the fully formatted output string.
@@ -277,7 +277,7 @@
   // Number of bytes that would have been emitted to the buffer, if the buffer
   // was sufficiently big. This number always excludes the trailing NUL byte
   // and it is guaranteed to never grow bigger than kSSizeMax-1.
-  size_t count_;
+  size_t count_ = 0;
 };
 
 bool Buffer::IToASCII(bool sign,
@@ -335,15 +335,17 @@
         if (padding) {
           --padding;
         }
-        Out(*prefix++);
+        UNSAFE_TODO(Out(*prefix++));
       }
       prefix = nullptr;
     } else {
-      for (reverse_prefix = prefix; *reverse_prefix; ++reverse_prefix) {
+      for (reverse_prefix = prefix; *reverse_prefix;
+           UNSAFE_TODO(++reverse_prefix)) {
       }
     }
-  } else
+  } else {
     prefix = nullptr;
+  }
   const size_t prefix_length = static_cast<size_t>(reverse_prefix - prefix);
 
   // Loop until we have converted the entire number. Output at least one
@@ -362,10 +364,11 @@
         // have to discard digits in the order that we have already emitted
         // them. This is essentially equivalent to:
         //   memmove(buffer_ + start, buffer_ + start + 1, size_ - start - 1)
-        for (char* move = buffer_ + start, *end = buffer_ + size_ - 1;
-             move < end;
-             ++move) {
-          *move = move[1];
+        // SAFETY: start checked against size_ above.
+        for (char *move = UNSAFE_BUFFERS(buffer_ + start),
+                  *end = UNSAFE_BUFFERS(buffer_ + size_ - 1);
+             move < end; UNSAFE_TODO(++move)) {
+          *move = UNSAFE_TODO(move[1]);
         }
         ++discarded;
         --count_;
@@ -387,14 +390,14 @@
     // integer always ends in 2, 4, 6, or 8.
     if (!num && started) {
       if (reverse_prefix > prefix) {
-        Out(*--reverse_prefix);
+        UNSAFE_TODO(Out(*--reverse_prefix));
       } else {
         Out(pad);
       }
     } else {
       started = true;
-      Out((upcase ? kUpCaseHexDigits
-                  : kDownCaseHexDigits)[num % base + minint]);
+      UNSAFE_TODO(Out((upcase ? kUpCaseHexDigits
+                              : kDownCaseHexDigits)[num % base + minint]));
     }
 
     minint = 0;
@@ -411,7 +414,7 @@
       // any further state change can be computed arithmetically; we know that
       // by this time, our entire final output consists of padding characters
       // that have all already been output.
-      if (discarded > 8*sizeof(num) + prefix_length) {
+      if (discarded > 8 * sizeof(num) + prefix_length) {
         IncrementCount(padding);
         padding = 0;
       }
@@ -423,13 +426,16 @@
     // order. We can't easily generate them in forward order, as we can't tell
     // the number of characters needed until we are done converting.
     // So, now, we reverse the string (except for the possible '-' sign).
-    char* front = buffer_ + start;
+    // SAFETY: start checked against size_ above.
+    char* front = UNSAFE_BUFFERS(buffer_ + start);
     char* back = GetInsertionPoint();
-    while (--back > front) {
-      char ch = *back;
-      *back = *front;
-      *front++ = ch;
-    }
+    UNSAFE_TODO({
+      while (--back > front) {
+        char ch = *back;
+        *back = *front;
+        *front++ = ch;
+      }
+    });
   }
   IncrementCount(discarded);
   return !discarded;
@@ -439,14 +445,18 @@
 
 namespace internal {
 
-ssize_t SafeSNPrintf(char* buf, size_t sz, const char* fmt, const Arg* args,
+ssize_t SafeSNPrintf(char* buf,
+                     size_t sz,
+                     const char* fmt,
+                     const Arg* args,
                      const size_t max_args) {
   // Make sure that at least one NUL byte can be written, and that the buffer
   // never overflows kSSizeMax. Not only does that use up most or all of the
   // address space, it also would result in a return code that cannot be
   // represented.
-  if (static_cast<ssize_t>(sz) < 1)
+  if (static_cast<ssize_t>(sz) < 1) {
     return -1;
+  }
   sz = std::min(sz, kSSizeMax);
 
   // Iterate over format string and interpret '%' arguments as they are
@@ -454,211 +464,225 @@
   Buffer buffer(buf, sz);
   size_t padding;
   char pad;
-  for (unsigned int cur_arg = 0; *fmt && !buffer.OutOfAddressableSpace(); ) {
-    if (*fmt++ == '%') {
+  for (unsigned int cur_arg = 0; *fmt && !buffer.OutOfAddressableSpace();) {
+    if (UNSAFE_TODO(*fmt++) == '%') {
       padding = 0;
       pad = ' ';
-      char ch = *fmt++;
+      char ch = UNSAFE_TODO(*fmt++);
     format_character_found:
       switch (ch) {
-      case '0': case '1': case '2': case '3': case '4':
-      case '5': case '6': case '7': case '8': case '9':
-        // Found a width parameter. Convert to an integer value and store in
-        // "padding". If the leading digit is a zero, change the padding
-        // character from a space ' ' to a zero '0'.
-        pad = ch == '0' ? '0' : ' ';
-        for (;;) {
-          const size_t digit = static_cast<size_t>(ch - '0');
-          // The maximum allowed padding fills all the available address
-          // space and leaves just enough space to insert the trailing NUL.
-          const size_t max_padding = kSSizeMax - 1;
-          if (padding > max_padding / 10 ||
-              10 * padding > max_padding - digit) {
-            DEBUG_CHECK(padding <= max_padding / 10 &&
-                        10 * padding <= max_padding - digit);
-            // Integer overflow detected. Skip the rest of the width until
-            // we find the format character, then do the normal error handling.
-          padding_overflow:
-            padding = max_padding;
-            while ((ch = *fmt++) >= '0' && ch <= '9') {
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+          // Found a width parameter. Convert to an integer value and store in
+          // "padding". If the leading digit is a zero, change the padding
+          // character from a space ' ' to a zero '0'.
+          pad = ch == '0' ? '0' : ' ';
+          for (;;) {
+            const size_t digit = static_cast<size_t>(ch - '0');
+            // The maximum allowed padding fills all the available address
+            // space and leaves just enough space to insert the trailing NUL.
+            const size_t max_padding = kSSizeMax - 1;
+            if (padding > max_padding / 10 ||
+                10 * padding > max_padding - digit) {
+              DEBUG_CHECK(padding <= max_padding / 10 &&
+                          10 * padding <= max_padding - digit);
+              // Integer overflow detected. Skip the rest of the width until
+              // we find the format character, then do the normal error
+              // handling.
+            padding_overflow:
+              padding = max_padding;
+              while ((ch = UNSAFE_TODO(*fmt++)) >= '0' && ch <= '9') {
+              }
+              if (cur_arg < max_args) {
+                ++cur_arg;
+              }
+              goto fail_to_expand;
             }
-            if (cur_arg < max_args) {
-              ++cur_arg;
+            padding = 10 * padding + digit;
+            if (padding > max_padding) {
+              // This doesn't happen for "sane" values of kSSizeMax. But once
+              // kSSizeMax gets smaller than about 10, our earlier range checks
+              // are incomplete. Unittests do trigger this artificial corner
+              // case.
+              DEBUG_CHECK(padding <= max_padding);
+              goto padding_overflow;
             }
+            ch = UNSAFE_TODO(*fmt++);
+            if (ch < '0' || ch > '9') {
+              // Reached the end of the width parameter. This is where the
+              // format character is found.
+              goto format_character_found;
+            }
+          }
+        case 'c': {  // Output an ASCII character.
+          // Check that there are arguments left to be inserted.
+          if (cur_arg >= max_args) {
+            DEBUG_CHECK(cur_arg < max_args);
             goto fail_to_expand;
           }
-          padding = 10 * padding + digit;
-          if (padding > max_padding) {
-            // This doesn't happen for "sane" values of kSSizeMax. But once
-            // kSSizeMax gets smaller than about 10, our earlier range checks
-            // are incomplete. Unittests do trigger this artificial corner
-            // case.
-            DEBUG_CHECK(padding <= max_padding);
-            goto padding_overflow;
-          }
-          ch = *fmt++;
-          if (ch < '0' || ch > '9') {
-            // Reached the end of the width parameter. This is where the format
-            // character is found.
-            goto format_character_found;
-          }
-        }
-      case 'c': {  // Output an ASCII character.
-        // Check that there are arguments left to be inserted.
-        if (cur_arg >= max_args) {
-          DEBUG_CHECK(cur_arg < max_args);
-          goto fail_to_expand;
-        }
 
-        // Check that the argument has the expected type.
-        const Arg& arg = args[cur_arg++];
-        if (arg.type != Arg::INT && arg.type != Arg::UINT) {
-          DEBUG_CHECK(arg.type == Arg::INT || arg.type == Arg::UINT);
-          goto fail_to_expand;
-        }
-
-        // Apply padding, if needed.
-        buffer.Pad(' ', padding, 1);
-
-        // Convert the argument to an ASCII character and output it.
-        char as_char = static_cast<char>(arg.integer.i);
-        if (!as_char) {
-          goto end_of_output_buffer;
-        }
-        buffer.Out(as_char);
-        break; }
-      case 'd':    // Output a possibly signed decimal value.
-      case 'o':    // Output an unsigned octal value.
-      case 'x':    // Output an unsigned hexadecimal value.
-      case 'X':
-      case 'p': {  // Output a pointer value.
-        // Check that there are arguments left to be inserted.
-        if (cur_arg >= max_args) {
-          DEBUG_CHECK(cur_arg < max_args);
-          goto fail_to_expand;
-        }
-
-        const Arg& arg = args[cur_arg++];
-        int64_t i;
-        const char* prefix = nullptr;
-        if (ch != 'p') {
           // Check that the argument has the expected type.
+          const Arg& arg = UNSAFE_TODO(args[cur_arg++]);
           if (arg.type != Arg::INT && arg.type != Arg::UINT) {
             DEBUG_CHECK(arg.type == Arg::INT || arg.type == Arg::UINT);
             goto fail_to_expand;
           }
-          i = arg.integer.i;
 
-          if (ch != 'd') {
-            // The Arg() constructor automatically performed sign expansion on
-            // signed parameters. This is great when outputting a %d decimal
-            // number, but can result in unexpected leading 0xFF bytes when
-            // outputting a %x hexadecimal number. Mask bits, if necessary.
-            // We have to do this here, instead of in the Arg() constructor, as
-            // the Arg() constructor cannot tell whether we will output a %d
-            // or a %x. Only the latter should experience masking.
-            if (arg.integer.width < sizeof(int64_t)) {
-              i &= (1LL << (8*arg.integer.width)) - 1;
-            }
+          // Apply padding, if needed.
+          buffer.Pad(' ', padding, 1);
+
+          // Convert the argument to an ASCII character and output it.
+          char as_char = static_cast<char>(arg.integer.i);
+          if (!as_char) {
+            goto end_of_output_buffer;
           }
-        } else {
-          // Pointer values require an actual pointer or a string.
-          if (arg.type == Arg::POINTER) {
-            i = static_cast<int64_t>(reinterpret_cast<uintptr_t>(arg.ptr));
-          } else if (arg.type == Arg::STRING) {
-            i = static_cast<int64_t>(reinterpret_cast<uintptr_t>(arg.str));
-          } else if (arg.type == Arg::INT &&
-                     arg.integer.width == sizeof(NULL) &&
-                     arg.integer.i == 0) {  // Allow C++'s version of NULL
-            i = 0;
-          } else {
-            DEBUG_CHECK(arg.type == Arg::POINTER || arg.type == Arg::STRING);
+          buffer.Out(as_char);
+          break;
+        }
+        case 'd':  // Output a possibly signed decimal value.
+        case 'o':  // Output an unsigned octal value.
+        case 'x':  // Output an unsigned hexadecimal value.
+        case 'X':
+        case 'p': {  // Output a pointer value.
+          // Check that there are arguments left to be inserted.
+          if (cur_arg >= max_args) {
+            DEBUG_CHECK(cur_arg < max_args);
             goto fail_to_expand;
           }
 
-          // Pointers always include the "0x" prefix.
-          prefix = "0x";
-        }
+          const Arg& arg = UNSAFE_TODO(args[cur_arg++]);
+          int64_t i;
+          const char* prefix = nullptr;
+          if (ch != 'p') {
+            // Check that the argument has the expected type.
+            if (arg.type != Arg::INT && arg.type != Arg::UINT) {
+              DEBUG_CHECK(arg.type == Arg::INT || arg.type == Arg::UINT);
+              goto fail_to_expand;
+            }
+            i = arg.integer.i;
 
-        // Use IToASCII() to convert to ASCII representation. For decimal
-        // numbers, optionally print a sign. For hexadecimal numbers,
-        // distinguish between upper and lower case. %p addresses are always
-        // printed as upcase. Supports base 8, 10, and 16. Prints padding
-        // and/or prefixes, if so requested.
-        buffer.IToASCII(ch == 'd' && arg.type == Arg::INT,
-                        ch != 'x', i,
-                        ch == 'o' ? 8 : ch == 'd' ? 10 : 16,
-                        pad, padding, prefix);
-        break; }
-      case 's': {
-        // Check that there are arguments left to be inserted.
-        if (cur_arg >= max_args) {
-          DEBUG_CHECK(cur_arg < max_args);
-          goto fail_to_expand;
-        }
+            if (ch != 'd') {
+              // The Arg() constructor automatically performed sign expansion on
+              // signed parameters. This is great when outputting a %d decimal
+              // number, but can result in unexpected leading 0xFF bytes when
+              // outputting a %x hexadecimal number. Mask bits, if necessary.
+              // We have to do this here, instead of in the Arg() constructor,
+              // as the Arg() constructor cannot tell whether we will output a
+              // %d or a %x. Only the latter should experience masking.
+              if (arg.integer.width < sizeof(int64_t)) {
+                i &= (1LL << (8 * arg.integer.width)) - 1;
+              }
+            }
+          } else {
+            // Pointer values require an actual pointer or a string.
+            if (arg.type == Arg::POINTER) {
+              i = static_cast<int64_t>(reinterpret_cast<uintptr_t>(arg.ptr));
+            } else if (arg.type == Arg::STRING) {
+              i = static_cast<int64_t>(reinterpret_cast<uintptr_t>(arg.str));
+            } else if (arg.type == Arg::INT &&
+                       arg.integer.width == sizeof(NULL) &&
+                       arg.integer.i == 0) {  // Allow C++'s version of NULL
+              i = 0;
+            } else {
+              DEBUG_CHECK(arg.type == Arg::POINTER || arg.type == Arg::STRING);
+              goto fail_to_expand;
+            }
 
-        // Check that the argument has the expected type.
-        const Arg& arg = args[cur_arg++];
-        const char *s;
-        if (arg.type == Arg::STRING) {
-          s = arg.str ? arg.str : "<NULL>";
-        } else if (arg.type == Arg::INT && arg.integer.width == sizeof(NULL) &&
-                   arg.integer.i == 0) {  // Allow C++'s version of NULL
-          s = "<NULL>";
-        } else {
-          DEBUG_CHECK(arg.type == Arg::STRING);
-          goto fail_to_expand;
-        }
-
-        // Apply padding, if needed. This requires us to first check the
-        // length of the string that we are outputting.
-        if (padding) {
-          size_t len = 0;
-          for (const char* src = s; *src++; ) {
-            ++len;
+            // Pointers always include the "0x" prefix.
+            prefix = "0x";
           }
-          buffer.Pad(' ', padding, len);
-        }
 
-        // Printing a string involves nothing more than copying it into the
-        // output buffer and making sure we don't output more bytes than
-        // available space; Out() takes care of doing that.
-        for (const char* src = s; *src; ) {
-          buffer.Out(*src++);
+          // Use IToASCII() to convert to ASCII representation. For decimal
+          // numbers, optionally print a sign. For hexadecimal numbers,
+          // distinguish between upper and lower case. %p addresses are always
+          // printed as upcase. Supports base 8, 10, and 16. Prints padding
+          // and/or prefixes, if so requested.
+          buffer.IToASCII(ch == 'd' && arg.type == Arg::INT, ch != 'x', i,
+                          ch == 'o'   ? 8
+                          : ch == 'd' ? 10
+                                      : 16,
+                          pad, padding, prefix);
+          break;
         }
-        break; }
-      case '%':
-        // Quoted percent '%' character.
-        goto copy_verbatim;
-      fail_to_expand:
-        // C++ gives us tools to do type checking -- something that snprintf()
-        // could never really do. So, whenever we see arguments that don't
-        // match up with the format string, we refuse to output them. But
-        // since we have to be extremely conservative about being async-
-        // signal-safe, we are limited in the type of error handling that we
-        // can do in production builds (in debug builds we can use
-        // DEBUG_CHECK() and hope for the best). So, all we do is pass the
-        // format string unchanged. That should eventually get the user's
-        // attention; and in the meantime, it hopefully doesn't lose too much
-        // data.
-      default:
-        // Unknown or unsupported format character. Just copy verbatim to
-        // output.
-        buffer.Out('%');
-        DEBUG_CHECK(ch);
-        if (!ch) {
-          goto end_of_format_string;
+        case 's': {
+          // Check that there are arguments left to be inserted.
+          if (cur_arg >= max_args) {
+            DEBUG_CHECK(cur_arg < max_args);
+            goto fail_to_expand;
+          }
+
+          // Check that the argument has the expected type.
+          const Arg& arg = UNSAFE_TODO(args[cur_arg++]);
+          const char* s;
+          if (arg.type == Arg::STRING) {
+            s = arg.str ? arg.str : "<NULL>";
+          } else if (arg.type == Arg::INT &&
+                     arg.integer.width == sizeof(NULL) &&
+                     arg.integer.i == 0) {  // Allow C++'s version of NULL
+            s = "<NULL>";
+          } else {
+            DEBUG_CHECK(arg.type == Arg::STRING);
+            goto fail_to_expand;
+          }
+
+          // Apply padding, if needed. This requires us to first check the
+          // length of the string that we are outputting.
+          if (padding) {
+            size_t len = 0;
+            for (const char* src = s; UNSAFE_TODO(*src++);) {
+              ++len;
+            }
+            buffer.Pad(' ', padding, len);
+          }
+
+          // Printing a string involves nothing more than copying it into the
+          // output buffer and making sure we don't output more bytes than
+          // available space; Out() takes care of doing that.
+          for (const char* src = s; *src;) {
+            buffer.Out(UNSAFE_TODO(*src++));
+          }
+          break;
         }
-        buffer.Out(ch);
-        break;
+        case '%':
+          // Quoted percent '%' character.
+          goto copy_verbatim;
+        fail_to_expand:
+          // C++ gives us tools to do type checking -- something that snprintf()
+          // could never really do. So, whenever we see arguments that don't
+          // match up with the format string, we refuse to output them. But
+          // since we have to be extremely conservative about being async-
+          // signal-safe, we are limited in the type of error handling that we
+          // can do in production builds (in debug builds we can use
+          // DEBUG_CHECK() and hope for the best). So, all we do is pass the
+          // format string unchanged. That should eventually get the user's
+          // attention; and in the meantime, it hopefully doesn't lose too much
+          // data.
+        default:
+          // Unknown or unsupported format character. Just copy verbatim to
+          // output.
+          buffer.Out('%');
+          DEBUG_CHECK(ch);
+          if (!ch) {
+            goto end_of_format_string;
+          }
+          buffer.Out(ch);
+          break;
       }
     } else {
-  copy_verbatim:
-    buffer.Out(fmt[-1]);
+    copy_verbatim:
+      buffer.Out(UNSAFE_TODO(fmt[-1]));
     }
   }
- end_of_format_string:
- end_of_output_buffer:
+end_of_format_string:
+end_of_output_buffer:
   return buffer.GetCount();
 }
 
@@ -669,8 +693,9 @@
   // never overflows kSSizeMax. Not only does that use up most or all of the
   // address space, it also would result in a return code that cannot be
   // represented.
-  if (static_cast<ssize_t>(sz) < 1)
+  if (static_cast<ssize_t>(sz) < 1) {
     return -1;
+  }
   sz = std::min(sz, kSSizeMax);
 
   Buffer buffer(buf, sz);
@@ -680,15 +705,16 @@
   // SafeSPrintf() function always degenerates to a version of strncpy() that
   // de-duplicates '%' characters.
   const char* src = fmt;
-  for (; *src; ++src) {
-    buffer.Out(*src);
-    DEBUG_CHECK(src[0] != '%' || src[1] == '%');
-    if (src[0] == '%' && src[1] == '%') {
-      ++src;
+  UNSAFE_TODO({
+    for (; *src; ++src) {
+      buffer.Out(*src);
+      DEBUG_CHECK(src[0] != '%' || src[1] == '%');
+      if (src[0] == '%' && src[1] == '%') {
+        ++src;
+      }
     }
-  }
+  });
   return buffer.GetCount();
 }
 
-}  // namespace strings
-}  // namespace base
+}  // namespace gurl_base::strings
diff --git a/base/strings/safe_sprintf.h b/base/strings/safe_sprintf.h
index 2948712..50ffa91 100644
--- a/base/strings/safe_sprintf.h
+++ b/base/strings/safe_sprintf.h
@@ -9,6 +9,9 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+#include "polyfills/base/base_export.h"
+#include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "polyfills/base/memory/raw_ptr_exclusion.h"
 #include "build/build_config.h"
 
@@ -17,8 +20,6 @@
 #include <unistd.h>
 #endif
 
-#include "polyfills/base/base_export.h"
-
 namespace gurl_base {
 namespace strings {
 
@@ -181,16 +182,17 @@
   }
 
   // A C-style text string.
-  Arg(const char* s) : str(s), type(STRING) { }
-  Arg(char* s)       : str(s), type(STRING) { }
+  Arg(const char* s) : str(s), type(STRING) {}
+  Arg(char* s) : str(s), type(STRING) {}
 
   // Any pointer value that can be cast to a "void*".
-  template<class T> Arg(T* p) : ptr((void*)p), type(POINTER) { }
+  template <class T>
+  Arg(T* p) : ptr((void*)p), type(POINTER) {}
 
   union {
     // An integer-like value.
     struct {
-      int64_t       i;
+      int64_t i;
       unsigned char width;
     } integer;
 
@@ -207,8 +209,12 @@
 
 // This is the internal function that performs the actual formatting of
 // an snprintf()-style format string.
-BASE_EXPORT ssize_t SafeSNPrintf(char* buf, size_t sz, const char* fmt,
-                                 const Arg* args, size_t max_args);
+// TODO(tsepez): should be UNSAFE_BUFFER_USAGE().
+BASE_EXPORT ssize_t SafeSNPrintf(char* buf,
+                                 size_t sz,
+                                 const char* fmt,
+                                 const Arg* args,
+                                 size_t max_args);
 
 #if !defined(NDEBUG)
 // In debug builds, allow unit tests to artificially lower the kSSizeMax
@@ -220,27 +226,51 @@
 
 }  // namespace internal
 
-template<typename... Args>
+// TODO(tsepez): should be UNSAFE_BUFFER_USAGE.
+template <typename... Args>
 ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, Args... args) {
   // Use Arg() object to record type information and then copy arguments to an
   // array to make it easier to iterate over them.
-  const internal::Arg arg_array[] = { args... };
-  return internal::SafeSNPrintf(buf, N, fmt, arg_array, sizeof...(args));
+  const internal::Arg arg_array[] = {args...};
+  // SAFTEY: required from caller.
+  return UNSAFE_BUFFERS(
+      internal::SafeSNPrintf(buf, N, fmt, arg_array, sizeof...(args)));
 }
 
-template<size_t N, typename... Args>
+template <size_t N, typename... Args>
 ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, Args... args) {
   // Use Arg() object to record type information and then copy arguments to an
   // array to make it easier to iterate over them.
-  const internal::Arg arg_array[] = { args... };
-  return internal::SafeSNPrintf(buf, N, fmt, arg_array, sizeof...(args));
+  const internal::Arg arg_array[] = {args...};
+  // SAFETY: compiler deduced size of `buf`.
+  return UNSAFE_BUFFERS(
+      internal::SafeSNPrintf(buf, N, fmt, arg_array, sizeof...(args)));
+}
+
+template <typename... Args>
+ssize_t SafeSPrintf(gurl_base::span<char> buf, const char* fmt, Args... args) {
+  // Use Arg() object to record type information and then copy arguments to an
+  // array to make it easier to iterate over them.
+  const internal::Arg arg_array[] = {args...};
+  // SAFETY: size of buffer taken from span.
+  return UNSAFE_BUFFERS(internal::SafeSNPrintf(buf.data(), buf.size(), fmt,
+                                               arg_array, sizeof...(args)));
 }
 
 // Fast-path when we don't actually need to substitute any arguments.
+// TODO(tsepez): should be UNSAFE_BUFFER_USAGE.
 BASE_EXPORT ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt);
-template<size_t N>
+
+template <size_t N>
 inline ssize_t SafeSPrintf(char (&buf)[N], const char* fmt) {
-  return SafeSNPrintf(buf, N, fmt);
+  // SAFETY: size of buffer deduced by compiler.
+  return UNSAFE_BUFFERS(SafeSNPrintf(buf, N, fmt));
+}
+
+template <typename... Args>
+ssize_t SafeSPrintf(gurl_base::span<char> buf, const char* fmt) {
+  // SAFETY: size of buffer taken from span.
+  return UNSAFE_BUFFERS(SafeSNPrintf(buf.data(), buf.size(), fmt));
 }
 
 }  // namespace strings
diff --git a/base/strings/safe_sprintf_unittest.cc b/base/strings/safe_sprintf_unittest.cc
index 9086e52..4fc8dfe 100644
--- a/base/strings/safe_sprintf_unittest.cc
+++ b/base/strings/safe_sprintf_unittest.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "base/strings/safe_sprintf.h"
 
 #include <stddef.h>
@@ -9,12 +14,16 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <array>
 #include <limits>
 #include <memory>
 
-#include "base/allocator/partition_allocator/src/partition_alloc/partition_alloc_config.h"
 #include "polyfills/base/check_op.h"
+#include "base/containers/heap_array.h"
+#include "base/types/fixed_array.h"
 #include "build/build_config.h"
+#include "partition_alloc/partition_alloc_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 // Death tests on Android are currently very flaky. No need to add more flaky
@@ -24,11 +33,10 @@
 #define ALLOW_DEATH_TEST
 #endif
 
-namespace gurl_base {
-namespace strings {
+namespace gurl_base::strings {
 
 TEST(SafeSPrintfTest, Empty) {
-  char buf[2] = { 'X', 'X' };
+  char buf[2] = {'X', 'X'};
 
   // Negative buffer size should always result in an error.
   EXPECT_EQ(-1, SafeSNPrintf(buf, static_cast<size_t>(-1), ""));
@@ -77,22 +85,22 @@
   EXPECT_TRUE(!memcmp(buf, ref, sizeof(buf)));
 
   // A one-byte buffer should always print a single NUL byte.
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(text))-1, SafeSNPrintf(buf, 1, text));
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(text)) - 1, SafeSNPrintf(buf, 1, text));
   EXPECT_EQ(0, buf[0]);
-  EXPECT_TRUE(!memcmp(buf+1, ref+1, sizeof(buf)-1));
+  EXPECT_TRUE(!memcmp(buf + 1, ref + 1, sizeof(buf) - 1));
   memcpy(buf, ref, sizeof(buf));
 
   // A larger (but limited) buffer should always leave the trailing bytes
   // unchanged.
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(text))-1, SafeSNPrintf(buf, 2, text));
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(text)) - 1, SafeSNPrintf(buf, 2, text));
   EXPECT_EQ(text[0], buf[0]);
   EXPECT_EQ(0, buf[1]);
-  EXPECT_TRUE(!memcmp(buf+2, ref+2, sizeof(buf)-2));
+  EXPECT_TRUE(!memcmp(buf + 2, ref + 2, sizeof(buf) - 2));
   memcpy(buf, ref, sizeof(buf));
 
   // A unrestricted buffer length should always leave the trailing bytes
   // unchanged.
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(text))-1,
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(text)) - 1,
             SafeSNPrintf(buf, sizeof(buf), text));
   EXPECT_EQ(std::string(text), std::string(buf));
   EXPECT_TRUE(!memcmp(buf + sizeof(text), ref + sizeof(text),
@@ -100,7 +108,7 @@
   memcpy(buf, ref, sizeof(buf));
 
   // The same test using SafeSPrintf() instead of SafeSNPrintf().
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(text))-1, SafeSPrintf(buf, text));
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(text)) - 1, SafeSPrintf(buf, text));
   EXPECT_EQ(std::string(text), std::string(buf));
   EXPECT_TRUE(!memcmp(buf + sizeof(text), ref + sizeof(text),
                       sizeof(buf) - sizeof(text)));
@@ -127,7 +135,7 @@
 TEST(SafeSPrintfTest, OneArgument) {
   // Test basic single-argument single-character substitution.
   const char text[] = "hello world";
-  const char fmt[]  = "hello%cworld";
+  const char fmt[] = "hello%cworld";
   char ref[20], buf[20];
   memset(ref, 'X', sizeof(buf));
   memcpy(buf, ref, sizeof(buf));
@@ -141,24 +149,24 @@
   EXPECT_TRUE(!memcmp(buf, ref, sizeof(buf)));
 
   // A one-byte buffer should always print a single NUL byte.
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(text))-1,
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(text)) - 1,
             SafeSNPrintf(buf, 1, fmt, ' '));
   EXPECT_EQ(0, buf[0]);
-  EXPECT_TRUE(!memcmp(buf+1, ref+1, sizeof(buf)-1));
+  EXPECT_TRUE(!memcmp(buf + 1, ref + 1, sizeof(buf) - 1));
   memcpy(buf, ref, sizeof(buf));
 
   // A larger (but limited) buffer should always leave the trailing bytes
   // unchanged.
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(text))-1,
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(text)) - 1,
             SafeSNPrintf(buf, 2, fmt, ' '));
   EXPECT_EQ(text[0], buf[0]);
   EXPECT_EQ(0, buf[1]);
-  EXPECT_TRUE(!memcmp(buf+2, ref+2, sizeof(buf)-2));
+  EXPECT_TRUE(!memcmp(buf + 2, ref + 2, sizeof(buf) - 2));
   memcpy(buf, ref, sizeof(buf));
 
   // A unrestricted buffer length should always leave the trailing bytes
   // unchanged.
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(text))-1,
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(text)) - 1,
             SafeSNPrintf(buf, sizeof(buf), fmt, ' '));
   EXPECT_EQ(std::string(text), std::string(buf));
   EXPECT_TRUE(!memcmp(buf + sizeof(text), ref + sizeof(text),
@@ -166,7 +174,7 @@
   memcpy(buf, ref, sizeof(buf));
 
   // The same test using SafeSPrintf() instead of SafeSNPrintf().
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(text))-1, SafeSPrintf(buf, fmt, ' '));
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(text)) - 1, SafeSPrintf(buf, fmt, ' '));
   EXPECT_EQ(std::string(text), std::string(buf));
   EXPECT_TRUE(!memcmp(buf + sizeof(text), ref + sizeof(text),
                       sizeof(buf) - sizeof(text)));
@@ -205,13 +213,14 @@
   // There is a more complicated test in PrintLongString() that covers a lot
   // more edge case, but it is also harder to debug in case of a failure.
   const char kTestString[] = "This is a test";
-  std::unique_ptr<char[]> buf(new char[sizeof(kTestString)]);
+  gurl_base::FixedArray<char> buf(sizeof(kTestString));
+  memcpy(buf.data(), kTestString, sizeof(kTestString));
   EXPECT_EQ(static_cast<ssize_t>(sizeof(kTestString) - 1),
-            SafeSNPrintf(buf.get(), sizeof(kTestString), kTestString));
-  EXPECT_EQ(std::string(kTestString), std::string(buf.get()));
-  EXPECT_EQ(static_cast<ssize_t>(sizeof(kTestString) - 1),
-            SafeSNPrintf(buf.get(), sizeof(kTestString), "%s", kTestString));
-  EXPECT_EQ(std::string(kTestString), std::string(buf.get()));
+            SafeSNPrintf(buf.data(), buf.size(), kTestString));
+  EXPECT_EQ(std::string(kTestString), std::string(buf.data()));
+  EXPECT_EQ(static_cast<ssize_t>(buf.size() - 1),
+            SafeSNPrintf(buf.data(), buf.size(), "%s", kTestString));
+  EXPECT_EQ(std::string(kTestString), std::string(buf.data()));
 }
 
 TEST(SafeSPrintfTest, NArgs) {
@@ -237,11 +246,11 @@
   EXPECT_EQ("\1\2\3\4\5\6\7", std::string(buf));
   EXPECT_EQ(8, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7, 8));
   EXPECT_EQ("\1\2\3\4\5\6\7\10", std::string(buf));
-  EXPECT_EQ(9, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c",
-                           1, 2, 3, 4, 5, 6, 7, 8, 9));
+  EXPECT_EQ(9,
+            SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7, 8, 9));
   EXPECT_EQ("\1\2\3\4\5\6\7\10\11", std::string(buf));
-  EXPECT_EQ(10, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c%c",
-                            1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
+  EXPECT_EQ(10, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7, 8,
+                            9, 10));
 
   // Repeat all the tests with SafeSNPrintf() instead of SafeSPrintf().
   EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12", std::string(buf));
@@ -259,21 +268,21 @@
   EXPECT_EQ("\1\2\3\4\5\6", std::string(buf));
   EXPECT_EQ(7, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7));
   EXPECT_EQ("\1\2\3\4\5\6\7", std::string(buf));
-  EXPECT_EQ(8, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c",
-                            1, 2, 3, 4, 5, 6, 7, 8));
+  EXPECT_EQ(8,
+            SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7, 8));
   EXPECT_EQ("\1\2\3\4\5\6\7\10", std::string(buf));
-  EXPECT_EQ(9, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c%c",
-                            1, 2, 3, 4, 5, 6, 7, 8, 9));
+  EXPECT_EQ(9, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7,
+                            8, 9));
   EXPECT_EQ("\1\2\3\4\5\6\7\10\11", std::string(buf));
-  EXPECT_EQ(10, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c%c%c",
-                             1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
+  EXPECT_EQ(10, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6,
+                             7, 8, 9, 10));
   EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12", std::string(buf));
 
-  EXPECT_EQ(11, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c%c%c",
-                            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
+  EXPECT_EQ(11, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7,
+                            8, 9, 10, 11));
   EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12\13", std::string(buf));
-  EXPECT_EQ(11, SafeSNPrintf(buf, 12, "%c%c%c%c%c%c%c%c%c%c%c",
-                             1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
+  EXPECT_EQ(11, SafeSNPrintf(buf, 12, "%c%c%c%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5,
+                             6, 7, 8, 9, 10, 11));
   EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12\13", std::string(buf));
 }
 
@@ -314,7 +323,7 @@
   EXPECT_EQ(2, SafeSPrintf(buf, "%d", (int32_t)-1));
   EXPECT_EQ("-1", std::string(buf));
   // Work-around for an limitation of C90
-  EXPECT_EQ(11, SafeSPrintf(buf, "%d", (int32_t)-2147483647-1));
+  EXPECT_EQ(11, SafeSPrintf(buf, "%d", (int32_t)-2147483647 - 1));
   EXPECT_EQ("-2147483648", std::string(buf));
 
   // Quads
@@ -327,7 +336,7 @@
   EXPECT_EQ(2, SafeSPrintf(buf, "%d", (int64_t)-1));
   EXPECT_EQ("-1", std::string(buf));
   // Work-around for an limitation of C90
-  EXPECT_EQ(20, SafeSPrintf(buf, "%d", (int64_t)-9223372036854775807LL-1));
+  EXPECT_EQ(20, SafeSPrintf(buf, "%d", (int64_t)-9223372036854775807LL - 1));
   EXPECT_EQ("-9223372036854775808", std::string(buf));
 
   // Strings (both const and mutable).
@@ -341,7 +350,7 @@
   snprintf(addr, sizeof(addr), "0x%llX", (unsigned long long)(uintptr_t)buf);
   SafeSPrintf(buf, "%p", buf);
   EXPECT_EQ(std::string(addr), std::string(buf));
-  SafeSPrintf(buf, "%p", (const char *)buf);
+  SafeSPrintf(buf, "%p", (const char*)buf);
   EXPECT_EQ(std::string(addr), std::string(buf));
   snprintf(addr, sizeof(addr), "0x%llX",
            (unsigned long long)(uintptr_t)snprintf);
@@ -356,8 +365,9 @@
   EXPECT_EQ(std::string(addr), std::string(buf));
   snprintf(addr, sizeof(addr), "0x%llX", (unsigned long long)(uintptr_t)buf);
   memset(addr, ' ',
-         (char*)memmove(addr + sizeof(addr) - strlen(addr) - 1,
-                        addr, strlen(addr)+1) - addr);
+         (char*)memmove(addr + sizeof(addr) - strlen(addr) - 1, addr,
+                        strlen(addr) + 1) -
+             addr);
   SafeSPrintf(buf, "%19p", buf);
   EXPECT_EQ(std::string(addr), std::string(buf));
 }
@@ -370,8 +380,8 @@
 
   // Allocate slightly more space, so that we can verify that SafeSPrintf()
   // never writes past the end of the buffer.
-  std::unique_ptr<char[]> tmp(new char[sz + 2]);
-  memset(tmp.get(), 'X', sz+2);
+  gurl_base::FixedArray<char> tmp(sz + 2);
+  tmp.fill('X');
 
   // Use SafeSPrintf() to output a complex list of arguments:
   // - test padding and truncating %c single characters.
@@ -381,10 +391,10 @@
   // - test outputting and truncating %d MININT.
   // - test outputting and truncating %p arbitrary pointer values.
   // - test outputting, padding and truncating NULL-pointer %s strings.
-  char* out = tmp.get();
+  char* out = tmp.data();
   size_t out_sz = sz;
   size_t len;
-  for (std::unique_ptr<char[]> perfect_buf;;) {
+  for (gurl_base::HeapArray<char> perfect_buf;;) {
     size_t needed =
         SafeSNPrintf(out, out_sz,
 #if defined(NDEBUG)
@@ -399,8 +409,8 @@
     // Various sanity checks:
     // The numbered of characters needed to print the full string should always
     // be bigger or equal to the bytes that have actually been output.
-    len = strlen(tmp.get());
-    GURL_CHECK_GE(needed, len+1);
+    len = strlen(tmp.data());
+    GURL_CHECK_GE(needed, len + 1);
 
     // The number of characters output should always fit into the buffer that
     // was passed into SafeSPrintf().
@@ -415,18 +425,19 @@
     // running SafeSNPrintf() the first time, it is possible to compute the
     // correct buffer size for this test. So, allocate a second buffer and run
     // the exact same SafeSNPrintf() command again.
-    if (!perfect_buf.get()) {
+    if (perfect_buf.empty()) {
       out_sz = std::min(needed, sz);
-      out = new char[out_sz];
-      perfect_buf.reset(out);
+      perfect_buf = gurl_base::HeapArray<char>::Uninit(out_sz);
+      out = perfect_buf.data();
     } else {
       break;
     }
   }
 
   // All trailing bytes are unchanged.
-  for (size_t i = len+1; i < sz+2; ++i)
+  for (size_t i = len + 1; i < sz + 2; ++i) {
     EXPECT_EQ('X', tmp[i]);
+  }
 
   // The text that was generated by SafeSPrintf() should always match the
   // equivalent text generated by snprintf(). Please note that the format
@@ -436,13 +447,14 @@
   // N.B.: It would be so much cleaner to use snprintf(). But unfortunately,
   //       Visual Studio doesn't support this function, and the work-arounds
   //       are all really awkward.
-  char ref[256];
-  GURL_CHECK_LE(sz, sizeof(ref));
-  snprintf(ref, sizeof(ref), "A long string: %%d 00DEADBEEF %lld 0x%llX <NULL>",
+  std::array<char, 256> ref;
+  GURL_CHECK_LE(sz, (ref.size() * sizeof(decltype(ref)::value_type)));
+  snprintf(ref.data(), (ref.size() * sizeof(decltype(ref)::value_type)),
+           "A long string: %%d 00DEADBEEF %lld 0x%llX <NULL>",
            static_cast<long long>(std::numeric_limits<intptr_t>::min()),
            static_cast<unsigned long long>(
                reinterpret_cast<uintptr_t>(PrintLongString)));
-  ref[sz-1] = '\000';
+  ref[sz - 1] = '\000';
 
 #if defined(NDEBUG)
   const size_t kSSizeMax = std::numeric_limits<ssize_t>::max();
@@ -451,18 +463,19 @@
 #endif
 
   // Compare the output from SafeSPrintf() to the one from snprintf().
-  EXPECT_EQ(std::string(ref).substr(0, kSSizeMax-1), std::string(tmp.get()));
+  EXPECT_EQ(std::string(ref.data()).substr(0, kSSizeMax - 1),
+            std::string(tmp.data()));
 
   // We allocated a slightly larger buffer, so that we could perform some
   // extra sanity checks. Now that the tests have all passed, we copy the
   // data to the output buffer that the caller provided.
-  memcpy(buf, tmp.get(), len+1);
+  memcpy(buf, tmp.data(), len + 1);
 }
 
 #if !defined(NDEBUG)
 class ScopedSafeSPrintfSSizeMaxSetter {
  public:
-  ScopedSafeSPrintfSSizeMaxSetter(size_t sz) {
+  explicit ScopedSafeSPrintfSSizeMaxSetter(size_t sz) {
     old_ssize_max_ = internal::GetSafeSPrintfSSizeMaxForTest();
     internal::SetSafeSPrintfSSizeMaxForTest(sz);
   }
@@ -490,7 +503,7 @@
   // happen in a lot of different states.
   char ref[256];
   PrintLongString(ref, sizeof(ref));
-  for (size_t i = strlen(ref)+1; i; --i) {
+  for (size_t i = strlen(ref) + 1; i; --i) {
     char buf[sizeof(ref)];
     PrintLongString(buf, i);
     EXPECT_EQ(std::string(ref, i - 1), std::string(buf));
@@ -503,7 +516,7 @@
   // Repeat the truncation test and verify that this other code path in
   // SafeSPrintf() works correctly, too.
 #if !defined(NDEBUG)
-  for (size_t i = strlen(ref)+1; i > 1; --i) {
+  for (size_t i = strlen(ref) + 1; i > 1; --i) {
     ScopedSafeSPrintfSSizeMaxSetter ssize_max_setter(i);
     char buf[sizeof(ref)];
     PrintLongString(buf, sizeof(buf));
@@ -537,7 +550,8 @@
   EXPECT_EQ(4, SafeSPrintf(buf, "%-2c", 'A'));
   EXPECT_EQ("%-2c", std::string(buf));
   SafeSPrintf(fmt, "%%%dc", std::numeric_limits<ssize_t>::max() - 1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1, SafeSPrintf(buf, fmt, 'A'));
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
+            SafeSPrintf(buf, fmt, 'A'));
   SafeSPrintf(fmt, "%%%dc",
               static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
 #if defined(NDEBUG)
@@ -566,12 +580,12 @@
   EXPECT_EQ("111", std::string(buf));
   EXPECT_EQ(4, SafeSPrintf(buf, "%-2o", 1));
   EXPECT_EQ("%-2o", std::string(buf));
-  SafeSPrintf(fmt, "%%%do", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%%do", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, 1));
   EXPECT_EQ("   ", std::string(buf));
-  SafeSPrintf(fmt, "%%0%do", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%0%do", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, 1));
   EXPECT_EQ("000", std::string(buf));
   SafeSPrintf(fmt, "%%%do",
@@ -600,12 +614,12 @@
   EXPECT_EQ("-111", std::string(buf));
   EXPECT_EQ(4, SafeSPrintf(buf, "%-2d", 1));
   EXPECT_EQ("%-2d", std::string(buf));
-  SafeSPrintf(fmt, "%%%dd", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%%dd", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, 1));
   EXPECT_EQ("   ", std::string(buf));
-  SafeSPrintf(fmt, "%%0%dd", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%0%dd", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, 1));
   EXPECT_EQ("000", std::string(buf));
   SafeSPrintf(fmt, "%%%dd",
@@ -636,12 +650,12 @@
   EXPECT_EQ("111", std::string(buf));
   EXPECT_EQ(4, SafeSPrintf(buf, "%-2X", 1));
   EXPECT_EQ("%-2X", std::string(buf));
-  SafeSPrintf(fmt, "%%%dX", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%%dX", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, 1));
   EXPECT_EQ("   ", std::string(buf));
-  SafeSPrintf(fmt, "%%0%dX", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%0%dX", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, 1));
   EXPECT_EQ("000", std::string(buf));
   SafeSPrintf(fmt, "%%%dX",
@@ -664,12 +678,12 @@
   EXPECT_EQ("0x111", std::string(buf));
   EXPECT_EQ(4, SafeSPrintf(buf, "%-2p", (void*)1));
   EXPECT_EQ("%-2p", std::string(buf));
-  SafeSPrintf(fmt, "%%%dp", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%%dp", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, (void*)1));
   EXPECT_EQ("   ", std::string(buf));
-  SafeSPrintf(fmt, "%%0%dp", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%0%dp", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, (void*)1));
   EXPECT_EQ("0x0", std::string(buf));
   SafeSPrintf(fmt, "%%%dp",
@@ -692,12 +706,12 @@
   EXPECT_EQ("AAA", std::string(buf));
   EXPECT_EQ(4, SafeSPrintf(buf, "%-2s", "A"));
   EXPECT_EQ("%-2s", std::string(buf));
-  SafeSPrintf(fmt, "%%%ds", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%%ds", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, "A"));
   EXPECT_EQ("   ", std::string(buf));
-  SafeSPrintf(fmt, "%%0%ds", std::numeric_limits<ssize_t>::max()-1);
-  EXPECT_EQ(std::numeric_limits<ssize_t>::max()-1,
+  SafeSPrintf(fmt, "%%0%ds", std::numeric_limits<ssize_t>::max() - 1);
+  EXPECT_EQ(std::numeric_limits<ssize_t>::max() - 1,
             SafeSNPrintf(buf, 4, fmt, "A"));
   EXPECT_EQ("   ", std::string(buf));
   SafeSPrintf(fmt, "%%%ds",
@@ -711,11 +725,11 @@
 }
 
 TEST(SafeSPrintfTest, EmbeddedNul) {
-  char buf[] = { 'X', 'X', 'X', 'X' };
+  char buf[] = {'X', 'X', 'X', 'X'};
   EXPECT_EQ(2, SafeSPrintf(buf, "%3c", 0));
   EXPECT_EQ(' ', buf[0]);
   EXPECT_EQ(' ', buf[1]);
-  EXPECT_EQ(0,   buf[2]);
+  EXPECT_EQ(0, buf[2]);
   EXPECT_EQ('X', buf[3]);
 
   // Check handling of a NUL format character. N.B. this takes two different
@@ -756,8 +770,8 @@
   // but we want to avoid doing so for pointer types. This could be a
   // problem on systems, where pointers are only 32bit. This tests verifies
   // that there is no such problem.
-  char *str = reinterpret_cast<char *>(0x80000000u);
-  void *ptr = str;
+  char* str = reinterpret_cast<char*>(0x80000000u);
+  void* ptr = str;
   char buf[40];
   EXPECT_EQ(10, SafeSPrintf(buf, "%p", str));
   EXPECT_EQ("0x80000000", std::string(buf));
@@ -765,5 +779,12 @@
   EXPECT_EQ("0x80000000", std::string(buf));
 }
 
-}  // namespace strings
-}  // namespace base
+TEST(SafeSPrintfTest, SpanForms) {
+  std::vector<char> buf(6);
+  EXPECT_EQ(5, SafeSPrintf(buf, "abcde"));
+  EXPECT_THAT(buf, testing::ElementsAre('a', 'b', 'c', 'd', 'e', '\0'));
+  EXPECT_EQ(5, SafeSPrintf(buf, "%c=%d\n", 'x', 42));
+  EXPECT_THAT(buf, testing::ElementsAre('x', '=', '4', '2', '\n', '\0'));
+}
+
+}  // namespace gurl_base::strings
diff --git a/base/strings/span_printf.h b/base/strings/span_printf.h
new file mode 100644
index 0000000..92745a8
--- /dev/null
+++ b/base/strings/span_printf.h
@@ -0,0 +1,47 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file defines printf-like functions for working with span-based
+// buffers.
+
+#ifndef BASE_STRINGS_SPAN_PRINTF_H_
+#define BASE_STRINGS_SPAN_PRINTF_H_
+
+#include <stdarg.h>  // va_list
+
+#include "base/compiler_specific.h"
+#include "base/containers/span.h"
+#include "base/strings/string_util.h"
+
+namespace gurl_base {
+
+// We separate the declaration from the implementation of this inline
+// function just so the PRINTF_FORMAT works.
+PRINTF_FORMAT(2, 0)
+inline int VSpanPrintf(gurl_base::span<char> buffer,
+                       const char* format,
+                       va_list arguments);
+inline int VSpanPrintf(gurl_base::span<char> buffer,
+                       const char* format,
+                       va_list arguments) {
+  // SAFETY: buffer size obtained from span.
+  return UNSAFE_BUFFERS(
+      gurl_base::vsnprintf(buffer.data(), buffer.size(), format, arguments));
+}
+
+// We separate the declaration from the implementation of this inline
+// function just so the PRINTF_FORMAT works.
+PRINTF_FORMAT(2, 3)
+inline int SpanPrintf(gurl_base::span<char> buffer, const char* format, ...);
+inline int SpanPrintf(gurl_base::span<char> buffer, const char* format, ...) {
+  va_list arguments;
+  va_start(arguments, format);
+  int result = VSpanPrintf(buffer, format, arguments);
+  va_end(arguments);
+  return result;
+}
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_SPAN_PRINTF_H_
diff --git a/base/strings/span_printf_unittest.cc b/base/strings/span_printf_unittest.cc
new file mode 100644
index 0000000..8c4b71d
--- /dev/null
+++ b/base/strings/span_printf_unittest.cc
@@ -0,0 +1,24 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/span_printf.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gurl_base {
+
+TEST(SpanPrintf, Fits) {
+  char buf[6];
+  EXPECT_EQ(5, SpanPrintf(buf, "x=%d\n", 42));
+  EXPECT_THAT(buf, testing::ElementsAre('x', '=', '4', '2', '\n', '\0'));
+}
+
+TEST(SpanPrintf, DoesNotFit) {
+  char buf[2];
+  EXPECT_EQ(5, SpanPrintf(buf, "x=%d\n", 42));
+  EXPECT_THAT(buf, testing::ElementsAre('x', '\0'));
+}
+
+}  // namespace base
diff --git a/base/strings/strcat.cc b/base/strings/strcat.cc
index 24b7e8d..3eb3a02 100644
--- a/base/strings/strcat.cc
+++ b/base/strings/strcat.cc
@@ -5,16 +5,17 @@
 #include "base/strings/strcat.h"
 
 #include <string>
+#include <string_view>
 
 #include "base/strings/strcat_internal.h"
 
 namespace gurl_base {
 
-std::string StrCat(span<const StringPiece> pieces) {
+std::string StrCat(span<const std::string_view> pieces) {
   return internal::StrCatT(pieces);
 }
 
-std::u16string StrCat(span<const StringPiece16> pieces) {
+std::u16string StrCat(span<const std::u16string_view> pieces) {
   return internal::StrCatT(pieces);
 }
 
@@ -26,11 +27,11 @@
   return internal::StrCatT(pieces);
 }
 
-void StrAppend(std::string* dest, span<const StringPiece> pieces) {
+void StrAppend(std::string* dest, span<const std::string_view> pieces) {
   internal::StrAppendT(*dest, pieces);
 }
 
-void StrAppend(std::u16string* dest, span<const StringPiece16> pieces) {
+void StrAppend(std::u16string* dest, span<const std::u16string_view> pieces) {
   internal::StrAppendT(*dest, pieces);
 }
 
diff --git a/base/strings/strcat.h b/base/strings/strcat.h
index 386d542..afb257c 100644
--- a/base/strings/strcat.h
+++ b/base/strings/strcat.h
@@ -6,10 +6,10 @@
 #define BASE_STRINGS_STRCAT_H_
 
 #include <initializer_list>
+#include <string_view>
 
 #include "polyfills/base/base_export.h"
 #include "base/containers/span.h"
-#include "base/strings/string_piece.h"
 #include "build/build_config.h"
 
 #if BUILDFLAG(IS_WIN)
@@ -36,7 +36,7 @@
 // StrCat can see all arguments at once, so it can allocate one return buffer
 // of exactly the right size and copy once, as opposed to a sequence of
 // operator+ which generates a series of temporary strings, copying as it goes.
-// And by using StringPiece arguments, StrCat can avoid creating temporary
+// And by using std::string_view arguments, StrCat can avoid creating temporary
 // string objects for char* constants.
 //
 // ALTERNATIVES
@@ -53,25 +53,27 @@
 // and the call sites look nice.
 //
 // As-written Abseil's helper class for numbers generates slightly more code
-// than the raw StringPiece version. We can de-inline the helper class'
-// constructors which will cause the StringPiece constructors to be de-inlined
-// for this call and generate slightly less code. This is something we can
-// explore more in the future.
+// than the raw std::string_view version. We can de-inline the helper class'
+// constructors which will cause the std::string_view constructors to be
+// de-inlined for this call and generate slightly less code. This is something
+// we can explore more in the future.
 
-[[nodiscard]] BASE_EXPORT std::string StrCat(span<const StringPiece> pieces);
+[[nodiscard]] BASE_EXPORT std::string StrCat(
+    span<const std::string_view> pieces);
 [[nodiscard]] BASE_EXPORT std::u16string StrCat(
-    span<const StringPiece16> pieces);
+    span<const std::u16string_view> pieces);
 [[nodiscard]] BASE_EXPORT std::string StrCat(span<const std::string> pieces);
 [[nodiscard]] BASE_EXPORT std::u16string StrCat(
     span<const std::u16string> pieces);
 
 // Initializer list forwards to the array version.
-inline std::string StrCat(std::initializer_list<StringPiece> pieces) {
-  return StrCat(make_span(pieces));
+inline std::string StrCat(std::initializer_list<std::string_view> pieces) {
+  return StrCat(span(pieces));
 }
 
-inline std::u16string StrCat(std::initializer_list<StringPiece16> pieces) {
-  return StrCat(make_span(pieces));
+inline std::u16string StrCat(
+    std::initializer_list<std::u16string_view> pieces) {
+  return StrCat(span(pieces));
 }
 
 // StrAppend -------------------------------------------------------------------
@@ -82,22 +84,23 @@
 //   foo += StrCat(...);
 // because it avoids a temporary string allocation and copy.
 
-BASE_EXPORT void StrAppend(std::string* dest, span<const StringPiece> pieces);
+BASE_EXPORT void StrAppend(std::string* dest,
+                           span<const std::string_view> pieces);
 BASE_EXPORT void StrAppend(std::u16string* dest,
-                           span<const StringPiece16> pieces);
+                           span<const std::u16string_view> pieces);
 BASE_EXPORT void StrAppend(std::string* dest, span<const std::string> pieces);
 BASE_EXPORT void StrAppend(std::u16string* dest,
                            span<const std::u16string> pieces);
 
 // Initializer list forwards to the array version.
 inline void StrAppend(std::string* dest,
-                      std::initializer_list<StringPiece> pieces) {
-  StrAppend(dest, make_span(pieces));
+                      std::initializer_list<std::string_view> pieces) {
+  StrAppend(dest, span(pieces));
 }
 
 inline void StrAppend(std::u16string* dest,
-                      std::initializer_list<StringPiece16> pieces) {
-  StrAppend(dest, make_span(pieces));
+                      std::initializer_list<std::u16string_view> pieces) {
+  StrAppend(dest, span(pieces));
 }
 
 }  // namespace base
diff --git a/base/strings/strcat_internal.h b/base/strings/strcat_internal.h
index 894aac9..a7763a6 100644
--- a/base/strings/strcat_internal.h
+++ b/base/strings/strcat_internal.h
@@ -5,33 +5,36 @@
 #ifndef BASE_STRINGS_STRCAT_INTERNAL_H_
 #define BASE_STRINGS_STRCAT_INTERNAL_H_
 
+#include <concepts>
 #include <string>
 
+#include "base/compiler_specific.h"
 #include "base/containers/span.h"
-#include "base/template_util.h"
 
 namespace gurl_base {
 
 namespace internal {
 
+// Default to regular `std::basic_string::resize()`.
+template <typename CharT>
+void Resize(std::basic_string<CharT>& str, size_t total_size) {
+  str.resize(total_size);
+}
+
 // Optimized version of `std::basic_string::resize()` that skips zero
 // initialization of appended characters. Reading from the newly allocated
 // characters results in undefined behavior if they are not explicitly
-// initialized afterwards. Currently proposed for standardization as
-// std::basic_string::resize_and_overwrite: https://wg21.link/P1072R6
+// initialized afterwards. Available in C++23 as
+// `std::basic_string::resize_and_overwrite()`:
+// https://en.cppreference.com/w/cpp/string/basic_string/resize_and_overwrite
 template <typename CharT>
-auto Resize(std::basic_string<CharT>& str, size_t total_size, priority_tag<1>)
-    -> decltype(str.__resize_default_init(total_size)) {
+  requires requires(std::basic_string<CharT>& str, size_t total_size) {
+    { str.__resize_default_init(total_size) } -> std::same_as<void>;
+  }
+auto Resize(std::basic_string<CharT>& str, size_t total_size) {
   str.__resize_default_init(total_size);
 }
 
-// Fallback to regular std::basic_string::resize() if invoking
-// __resize_default_init is ill-formed.
-template <typename CharT>
-void Resize(std::basic_string<CharT>& str, size_t total_size, priority_tag<0>) {
-  str.resize(total_size);
-}
-
 // Appends `pieces` to `dest`. Instead of simply calling `dest.append()`
 // `pieces.size()` times, this method first resizes `dest` to be of the desired
 // size, and then appends each piece via `std::char_traits::copy`. This achieves
@@ -44,8 +47,9 @@
 void StrAppendT(std::basic_string<CharT>& dest, span<const StringT> pieces) {
   const size_t initial_size = dest.size();
   size_t total_size = initial_size;
-  for (const auto& cur : pieces)
+  for (const auto& cur : pieces) {
     total_size += cur.size();
+  }
 
   // Note: As opposed to `reserve()` calling `resize()` with an argument smaller
   // than the current `capacity()` does not result in the string releasing spare
@@ -54,11 +58,11 @@
   // added characters. Since this codepath is also triggered by `resize()`, we
   // don't have to manage the std::string's capacity ourselves here to avoid
   // performance hits in case `StrAppend()` gets called in a loop.
-  Resize(dest, total_size, priority_tag<1>());
+  Resize(dest, total_size);
   CharT* dest_char = &dest[initial_size];
   for (const auto& cur : pieces) {
     std::char_traits<CharT>::copy(dest_char, cur.data(), cur.size());
-    dest_char += cur.size();
+    UNSAFE_TODO(dest_char += cur.size());
   }
 }
 
diff --git a/base/strings/strcat_unittest.cc b/base/strings/strcat_unittest.cc
index 0c433c9..443fb5a 100644
--- a/base/strings/strcat_unittest.cc
+++ b/base/strings/strcat_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/strings/strcat.h"
+
 #include "base/strings/utf_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/base/strings/strcat_win.h b/base/strings/strcat_win.h
index b7dac19..830f98c 100644
--- a/base/strings/strcat_win.h
+++ b/base/strings/strcat_win.h
@@ -22,7 +22,7 @@
 
 inline void StrAppend(std::wstring* dest,
                       std::initializer_list<std::wstring_view> pieces) {
-  StrAppend(dest, make_span(pieces));
+  StrAppend(dest, span(pieces));
 }
 
 [[nodiscard]] BASE_EXPORT std::wstring StrCat(
@@ -30,7 +30,7 @@
 [[nodiscard]] BASE_EXPORT std::wstring StrCat(span<const std::wstring> pieces);
 
 inline std::wstring StrCat(std::initializer_list<std::wstring_view> pieces) {
-  return StrCat(make_span(pieces));
+  return StrCat(span(pieces));
 }
 
 }  // namespace base
diff --git a/base/strings/string_number_conversions.cc b/base/strings/string_number_conversions.cc
index 258db85..cba9e56 100644
--- a/base/strings/string_number_conversions.cc
+++ b/base/strings/string_number_conversions.cc
@@ -6,11 +6,11 @@
 
 #include <iterator>
 #include <string>
+#include <string_view>
 
 #include "base/containers/span.h"
 #include "polyfills/base/logging.h"
 #include "base/strings/string_number_conversions_internal.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 
@@ -70,57 +70,67 @@
   return internal::DoubleToStringT<std::u16string>(value);
 }
 
-bool StringToInt(StringPiece input, int* output) {
+std::string NumberToStringWithFixedPrecision(double value, int digits) {
+  return internal::DoubleToStringFixedT<std::string>(value, digits);
+}
+std::u16string NumberToString16WithFixedPrecision(double value, int digits) {
+  return internal::DoubleToStringFixedT<std::u16string>(value, digits);
+}
+
+bool StringToInt(std::string_view input, int* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToInt(StringPiece16 input, int* output) {
+bool StringToInt(std::u16string_view input, int* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToUint(StringPiece input, unsigned* output) {
+bool StringToUint(std::string_view input, unsigned* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToUint(StringPiece16 input, unsigned* output) {
+bool StringToUint(std::u16string_view input, unsigned* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToInt64(StringPiece input, int64_t* output) {
+bool StringToInt64(std::string_view input, int64_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToInt64(StringPiece16 input, int64_t* output) {
+bool StringToInt64(std::u16string_view input, int64_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToUint64(StringPiece input, uint64_t* output) {
+bool StringToUint64(std::string_view input, uint64_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToUint64(StringPiece16 input, uint64_t* output) {
+bool StringToUint64(std::u16string_view input, uint64_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToSizeT(StringPiece input, size_t* output) {
+bool StringToSizeT(std::string_view input, size_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToSizeT(StringPiece16 input, size_t* output) {
+bool StringToSizeT(std::u16string_view input, size_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToDouble(StringPiece input, double* output) {
+bool StringToDouble(std::string_view input, double* output) {
   return internal::StringToDoubleImpl(input, input.data(), *output);
 }
 
-bool StringToDouble(StringPiece16 input, double* output) {
+bool StringToDouble(std::u16string_view input, double* output) {
   return internal::StringToDoubleImpl(
       input, reinterpret_cast<const uint16_t*>(input.data()), *output);
 }
 
 std::string HexEncode(const void* bytes, size_t size) {
-  return HexEncode(span(reinterpret_cast<const uint8_t*>(bytes), size));
+  return HexEncode(
+      // TODO(crbug.com/40284755): The pointer-based overload of HexEncode
+      // should be removed.
+      UNSAFE_TODO(span(static_cast<const uint8_t*>(bytes), size)));
 }
 
 std::string HexEncode(span<const uint8_t> bytes) {
@@ -134,37 +144,57 @@
   return ret;
 }
 
-bool HexStringToInt(StringPiece input, int* output) {
+std::string HexEncode(std::string_view chars) {
+  return HexEncode(gurl_base::as_byte_span(chars));
+}
+
+std::string HexEncodeLower(gurl_base::span<const uint8_t> bytes) {
+  // Each input byte creates two output hex characters.
+  std::string ret;
+  ret.reserve(bytes.size() * 2);
+
+  for (uint8_t byte : bytes) {
+    AppendHexEncodedByte(byte, ret, /*uppercase=*/false);
+  }
+  return ret;
+}
+
+std::string HexEncodeLower(std::string_view chars) {
+  return HexEncodeLower(gurl_base::as_byte_span(chars));
+}
+
+bool HexStringToInt(std::string_view input, int* output) {
   return internal::HexStringToIntImpl(input, *output);
 }
 
-bool HexStringToUInt(StringPiece input, uint32_t* output) {
+bool HexStringToUInt(std::string_view input, uint32_t* output) {
   return internal::HexStringToIntImpl(input, *output);
 }
 
-bool HexStringToInt64(StringPiece input, int64_t* output) {
+bool HexStringToInt64(std::string_view input, int64_t* output) {
   return internal::HexStringToIntImpl(input, *output);
 }
 
-bool HexStringToUInt64(StringPiece input, uint64_t* output) {
+bool HexStringToUInt64(std::string_view input, uint64_t* output) {
   return internal::HexStringToIntImpl(input, *output);
 }
 
-bool HexStringToBytes(StringPiece input, std::vector<uint8_t>* output) {
+bool HexStringToBytes(std::string_view input, std::vector<uint8_t>* output) {
   GURL_DCHECK(output->empty());
   return internal::HexStringToByteContainer<uint8_t>(
       input, std::back_inserter(*output));
 }
 
-bool HexStringToString(StringPiece input, std::string* output) {
+bool HexStringToString(std::string_view input, std::string* output) {
   GURL_DCHECK(output->empty());
   return internal::HexStringToByteContainer<char>(input,
                                                   std::back_inserter(*output));
 }
 
-bool HexStringToSpan(StringPiece input, span<uint8_t> output) {
-  if (input.size() / 2 != output.size())
+bool HexStringToSpan(std::string_view input, span<uint8_t> output) {
+  if (input.size() / 2 != output.size()) {
     return false;
+  }
 
   return internal::HexStringToByteContainer<uint8_t>(input, output.begin());
 }
diff --git a/base/strings/string_number_conversions.h b/base/strings/string_number_conversions.h
index c62e914..3556497 100644
--- a/base/strings/string_number_conversions.h
+++ b/base/strings/string_number_conversions.h
@@ -9,11 +9,12 @@
 #include <stdint.h>
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "polyfills/base/base_export.h"
+#include "base/compiler_specific.h"
 #include "base/containers/span.h"
-#include "base/strings/string_piece.h"
 #include "build/build_config.h"
 
 // ----------------------------------------------------------------------------
@@ -49,41 +50,50 @@
 BASE_EXPORT std::u16string NumberToString16(long long value);
 BASE_EXPORT std::string NumberToString(unsigned long long value);
 BASE_EXPORT std::u16string NumberToString16(unsigned long long value);
+
+// Returns a string that contains a full representation of `value`.
 BASE_EXPORT std::string NumberToString(double value);
 BASE_EXPORT std::u16string NumberToString16(double value);
 
+// Returns a string that contains a representation of `value` expressed with
+// exactly `digits` digits after the decimal point.
+BASE_EXPORT std::string NumberToStringWithFixedPrecision(double value,
+                                                         int digits);
+BASE_EXPORT std::u16string NumberToString16WithFixedPrecision(double value,
+                                                              int digits);
+
 // String -> number conversions ------------------------------------------------
 
 // Perform a best-effort conversion of the input string to a numeric type,
-// setting |*output| to the result of the conversion.  Returns true for
+// setting `*output` to the result of the conversion.  Returns true for
 // "perfect" conversions; returns false in the following cases:
-//  - Overflow. |*output| will be set to the maximum value supported
+//  - Overflow. `*output` will be set to the maximum value supported
 //    by the data type.
-//  - Underflow. |*output| will be set to the minimum value supported
+//  - Underflow. `*output` will be set to the minimum value supported
 //    by the data type.
-//  - Trailing characters in the string after parsing the number.  |*output|
+//  - Trailing characters in the string after parsing the number.  `*output`
 //    will be set to the value of the number that was parsed.
-//  - Leading whitespace in the string before parsing the number. |*output| will
+//  - Leading whitespace in the string before parsing the number. `*output` will
 //    be set to the value of the number that was parsed.
 //  - No characters parseable as a number at the beginning of the string.
-//    |*output| will be set to 0.
-//  - Empty string.  |*output| will be set to 0.
-// WARNING: Will write to |output| even when returning false.
+//    `*output` will be set to 0.
+//  - Empty string.  `*output` will be set to 0.
+// WARNING: Will write to `output` even when returning false.
 //          Read the comments above carefully.
-BASE_EXPORT bool StringToInt(StringPiece input, int* output);
-BASE_EXPORT bool StringToInt(StringPiece16 input, int* output);
+BASE_EXPORT bool StringToInt(std::string_view input, int* output);
+BASE_EXPORT bool StringToInt(std::u16string_view input, int* output);
 
-BASE_EXPORT bool StringToUint(StringPiece input, unsigned* output);
-BASE_EXPORT bool StringToUint(StringPiece16 input, unsigned* output);
+BASE_EXPORT bool StringToUint(std::string_view input, unsigned* output);
+BASE_EXPORT bool StringToUint(std::u16string_view input, unsigned* output);
 
-BASE_EXPORT bool StringToInt64(StringPiece input, int64_t* output);
-BASE_EXPORT bool StringToInt64(StringPiece16 input, int64_t* output);
+BASE_EXPORT bool StringToInt64(std::string_view input, int64_t* output);
+BASE_EXPORT bool StringToInt64(std::u16string_view input, int64_t* output);
 
-BASE_EXPORT bool StringToUint64(StringPiece input, uint64_t* output);
-BASE_EXPORT bool StringToUint64(StringPiece16 input, uint64_t* output);
+BASE_EXPORT bool StringToUint64(std::string_view input, uint64_t* output);
+BASE_EXPORT bool StringToUint64(std::u16string_view input, uint64_t* output);
 
-BASE_EXPORT bool StringToSizeT(StringPiece input, size_t* output);
-BASE_EXPORT bool StringToSizeT(StringPiece16 input, size_t* output);
+BASE_EXPORT bool StringToSizeT(std::string_view input, size_t* output);
+BASE_EXPORT bool StringToSizeT(std::u16string_view input, size_t* output);
 
 // For floating-point conversions, only conversions of input strings in decimal
 // form are defined to work.  Behavior with strings representing floating-point
@@ -91,21 +101,27 @@
 // NaN and inf) is undefined.  Otherwise, these behave the same as the integral
 // variants.  This expects the input string to NOT be specific to the locale.
 // If your input is locale specific, use ICU to read the number.
-// WARNING: Will write to |output| even when returning false.
+// WARNING: Will write to `output` even when returning false.
 //          Read the comments here and above StringToInt() carefully.
-BASE_EXPORT bool StringToDouble(StringPiece input, double* output);
-BASE_EXPORT bool StringToDouble(StringPiece16 input, double* output);
+BASE_EXPORT bool StringToDouble(std::string_view input, double* output);
+BASE_EXPORT bool StringToDouble(std::u16string_view input, double* output);
 
 // Hex encoding ----------------------------------------------------------------
 
 // Returns a hex string representation of a binary buffer. The returned hex
-// string will be in upper case. This function does not check if |size| is
+// string will be in upper case. This function does not check if `size` is
 // within reasonable limits since it's written with trusted data in mind.  If
 // you suspect that the data you want to format might be large, the absolute
-// max size for |size| should be is
+// max size for `size` should be is
 //   std::numeric_limits<size_t>::max() / 2
-BASE_EXPORT std::string HexEncode(const void* bytes, size_t size);
 BASE_EXPORT std::string HexEncode(gurl_base::span<const uint8_t> bytes);
+BASE_EXPORT std::string HexEncode(std::string_view chars);
+// TODO(crbug.com/40284755): The pointer-based overload should be removed.
+BASE_EXPORT std::string HexEncode(const void* bytes, size_t size);
+
+// Behaves like the above, but returns the hex string in lower case.
+BASE_EXPORT std::string HexEncodeLower(gurl_base::span<const uint8_t> bytes);
+BASE_EXPORT std::string HexEncodeLower(std::string_view chars);
 
 // Appends a hex representation of `byte`, as two uppercase (by default)
 // characters, to `output`. This is a useful primitive in larger conversion
@@ -120,48 +136,50 @@
                                             '6', '7', '8', '9', 'a', 'b',
                                             'c', 'd', 'e', 'f'};
   const char* const hex_chars = uppercase ? kHexCharsUpper : kHexCharsLower;
-  output.append({hex_chars[byte >> 4], hex_chars[byte & 0xf]});
+  output.append(
+      {UNSAFE_TODO(hex_chars[byte >> 4]), UNSAFE_TODO(hex_chars[byte & 0xf])});
 }
 
 // Best effort conversion, see StringToInt above for restrictions.
-// Will only successful parse hex values that will fit into |output|, i.e.
-// -0x80000000 < |input| < 0x7FFFFFFF.
-BASE_EXPORT bool HexStringToInt(StringPiece input, int* output);
+// Will only successful parse hex values that will fit into `output`, i.e.
+// -0x8000'0000 < `input` < 0x7FFF'FFFF.
+BASE_EXPORT bool HexStringToInt(std::string_view input, int* output);
 
 // Best effort conversion, see StringToInt above for restrictions.
-// Will only successful parse hex values that will fit into |output|, i.e.
-// 0x00000000 < |input| < 0xFFFFFFFF.
-// The string is not required to start with 0x.
-BASE_EXPORT bool HexStringToUInt(StringPiece input, uint32_t* output);
+// Will only successful parse hex values that will fit into `output`, i.e.
+// 0x0000'0000 < `input` < 0xFFFF'FFFF.
+// The string is not required to start with "0x".
+BASE_EXPORT bool HexStringToUInt(std::string_view input, uint32_t* output);
 
 // Best effort conversion, see StringToInt above for restrictions.
-// Will only successful parse hex values that will fit into |output|, i.e.
-// -0x8000000000000000 < |input| < 0x7FFFFFFFFFFFFFFF.
-BASE_EXPORT bool HexStringToInt64(StringPiece input, int64_t* output);
+// Will only successful parse hex values that will fit into `output`, i.e.
+// -0x8000'0000'0000'0000 < `input` < 0x7FFF'FFFF'FFFF'FFFF.
+BASE_EXPORT bool HexStringToInt64(std::string_view input, int64_t* output);
 
 // Best effort conversion, see StringToInt above for restrictions.
-// Will only successful parse hex values that will fit into |output|, i.e.
-// 0x0000000000000000 < |input| < 0xFFFFFFFFFFFFFFFF.
-// The string is not required to start with 0x.
-BASE_EXPORT bool HexStringToUInt64(StringPiece input, uint64_t* output);
+// Will only successful parse hex values that will fit into `output`, i.e.
+// 0x0000'0000'0000'0000 < `input` < 0xFFFF'FFFF'FFFF'FFFF.
+// The string is not required to start with "0x".
+BASE_EXPORT bool HexStringToUInt64(std::string_view input, uint64_t* output);
 
 // Similar to the previous functions, except that output is a vector of bytes.
-// |*output| will contain as many bytes as were successfully parsed prior to the
+// `*output` will contain as many bytes as were successfully parsed prior to the
 // error.  There is no overflow, but input.size() must be evenly divisible by 2.
-// Leading 0x or +/- are not allowed.
-BASE_EXPORT bool HexStringToBytes(StringPiece input,
+// Leading "0x" or +/- are not allowed.
+BASE_EXPORT bool HexStringToBytes(std::string_view input,
                                   std::vector<uint8_t>* output);
 
 // Same as HexStringToBytes, but for an std::string.
-BASE_EXPORT bool HexStringToString(StringPiece input, std::string* output);
+BASE_EXPORT bool HexStringToString(std::string_view input, std::string* output);
 
-// Decodes the hex string |input| into a presized |output|. The output buffer
-// must be sized exactly to |input.size() / 2| or decoding will fail and no
-// bytes will be written to |output|. Decoding an empty input is also
+// Decodes the hex string `input` into a presized `output`. The output buffer
+// must be sized exactly to `input.size() / 2` or decoding will fail and no
+// bytes will be written to `output`. Decoding an empty input is also
 // considered a failure. When decoding fails due to encountering invalid input
-// characters, |output| will have been filled with the decoded bytes up until
+// characters, `output` will have been filled with the decoded bytes up until
 // the failure.
-BASE_EXPORT bool HexStringToSpan(StringPiece input, gurl_base::span<uint8_t> output);
+BASE_EXPORT bool HexStringToSpan(std::string_view input,
+                                 gurl_base::span<uint8_t> output);
 
 }  // namespace base
 
diff --git a/base/strings/string_number_conversions_internal.h b/base/strings/string_number_conversions_internal.h
index 6649046..2af1bd2 100644
--- a/base/strings/string_number_conversions_internal.h
+++ b/base/strings/string_number_conversions_internal.h
@@ -8,18 +8,19 @@
 #include <errno.h>
 #include <stdlib.h>
 
+#include <array>
 #include <limits>
+#include <optional>
+#include <string_view>
 
 #include "polyfills/base/check.h"
+#include "base/compiler_specific.h"
 #include "polyfills/base/logging.h"
 #include "base/numerics/safe_math.h"
 #include "base/strings/string_util.h"
 #include "base/third_party/double_conversion/double-conversion/double-conversion.h"
-#include "absl/types/optional.h"
 
-namespace gurl_base {
-
-namespace internal {
+namespace gurl_base::internal {
 
 template <typename STR, typename INT>
 static STR IntToStringT(INT value) {
@@ -31,43 +32,46 @@
   // Create the string in a temporary buffer, write it back to front, and
   // then return the substr of what we ended up using.
   using CHR = typename STR::value_type;
-  CHR outbuf[kOutputBufSize];
+  std::array<CHR, kOutputBufSize> outbuf = {};
 
   // The ValueOrDie call below can never fail, because UnsignedAbs is valid
   // for all valid inputs.
   std::make_unsigned_t<INT> res =
       CheckedNumeric<INT>(value).UnsignedAbs().ValueOrDie();
 
-  CHR* end = outbuf + kOutputBufSize;
-  CHR* i = end;
+  // Fill digits right-to-left.
+  size_t write = outbuf.size();
   do {
-    --i;
-    GURL_DCHECK(i != outbuf);
-    *i = static_cast<CHR>((res % 10) + '0');
+    const CHR digit = static_cast<CHR>((res % 10) + '0');
+    outbuf[--write] = digit;
     res /= 10;
   } while (res != 0);
+
   if (IsValueNegative(value)) {
-    --i;
-    GURL_DCHECK(i != outbuf);
-    *i = static_cast<CHR>('-');
+    outbuf[--write] = static_cast<CHR>('-');
   }
-  return STR(i, end);
+
+  auto result_span = gurl_base::span(outbuf).subspan(write);
+  return STR(result_span.begin(), result_span.end());
 }
 
 // Utility to convert a character to a digit in a given base
 template <int BASE, typename CHAR>
-absl::optional<uint8_t> CharToDigit(CHAR c) {
+std::optional<uint8_t> CharToDigit(CHAR c) {
   static_assert(1 <= BASE && BASE <= 36, "BASE needs to be in [1, 36]");
-  if (c >= '0' && c < '0' + std::min(BASE, 10))
+  if (c >= '0' && c < '0' + std::min(BASE, 10)) {
     return static_cast<uint8_t>(c - '0');
+  }
 
-  if (c >= 'a' && c < 'a' + BASE - 10)
+  if (c >= 'a' && c < 'a' + BASE - 10) {
     return static_cast<uint8_t>(c - 'a' + 10);
+  }
 
-  if (c >= 'A' && c < 'A' + BASE - 10)
+  if (c >= 'A' && c < 'A' + BASE - 10) {
     return static_cast<uint8_t>(c - 'A' + 10);
+  }
 
-  return absl::nullopt;
+  return std::nullopt;
 }
 
 template <typename Number, int kBase>
@@ -105,7 +109,7 @@
       }
 
       for (Iter current = begin; current != end; ++current) {
-        absl::optional<uint8_t> new_digit = CharToDigit<kBase>(*current);
+        std::optional<uint8_t> new_digit = CharToDigit<kBase>(*current);
 
         if (!new_digit) {
           return {value, false};
@@ -113,8 +117,9 @@
 
         if (current != begin) {
           Result result = Sign::CheckBounds(value, *new_digit);
-          if (!result.valid)
+          if (!result.valid) {
             return result;
+          }
 
           value *= kBase;
         }
@@ -152,7 +157,7 @@
 };
 
 template <typename Number, int kBase, typename CharT>
-auto StringToNumber(BasicStringPiece<CharT> input) {
+auto StringToNumber(std::basic_string_view<CharT> input) {
   using Parser = StringToNumberParser<Number, kBase>;
   using Result = typename Parser::Result;
 
@@ -206,25 +211,24 @@
   return &converter;
 }
 
-// Converts a given (data, size) pair to a desired string type. For
-// performance reasons, this dispatches to a different constructor if the
-// passed-in data matches the string's value_type.
-template <typename StringT>
-StringT ToString(const typename StringT::value_type* data, size_t size) {
-  return StringT(data, size);
-}
-
-template <typename StringT, typename CharT>
-StringT ToString(const CharT* data, size_t size) {
-  return StringT(data, data + size);
-}
-
 template <typename StringT>
 StringT DoubleToStringT(double value) {
-  char buffer[32];
-  double_conversion::StringBuilder builder(buffer, sizeof(buffer));
+  std::array<char, 32> buffer;
+  double_conversion::StringBuilder builder(buffer.data(), buffer.size());
   GetDoubleToStringConverter()->ToShortest(value, &builder);
-  return ToString<StringT>(buffer, static_cast<size_t>(builder.position()));
+  auto result_span =
+      gurl_base::span(buffer).first(static_cast<size_t>(builder.position()));
+  return StringT(result_span.begin(), result_span.end());
+}
+
+template <typename StringT>
+StringT DoubleToStringFixedT(double value, int digits) {
+  std::array<char, 32> buffer;
+  double_conversion::StringBuilder builder(buffer.data(), buffer.size());
+  GetDoubleToStringConverter()->ToFixed(value, digits, &builder);
+  auto result_span =
+      gurl_base::span(buffer).first(static_cast<size_t>(builder.position()));
+  return StringT(result_span.begin(), result_span.end());
 }
 
 template <typename STRING, typename CHAR>
@@ -255,15 +259,16 @@
 }
 
 template <typename Char, typename OutIter>
-static bool HexStringToByteContainer(StringPiece input, OutIter output) {
+static bool HexStringToByteContainer(std::string_view input, OutIter output) {
   size_t count = input.size();
-  if (count == 0 || (count % 2) != 0)
+  if (count == 0 || (count % 2) != 0) {
     return false;
+  }
   for (uintptr_t i = 0; i < count / 2; ++i) {
     // most significant 4 bits
-    absl::optional<uint8_t> msb = CharToDigit<16>(input[i * 2]);
+    std::optional<uint8_t> msb = CharToDigit<16>(input[i * 2]);
     // least significant 4 bits
-    absl::optional<uint8_t> lsb = CharToDigit<16>(input[i * 2 + 1]);
+    std::optional<uint8_t> lsb = CharToDigit<16>(input[i * 2 + 1]);
     if (!msb || !lsb) {
       return false;
     }
@@ -272,8 +277,6 @@
   return true;
 }
 
-}  // namespace internal
-
-}  // namespace base
+}  // namespace gurl_base::internal
 
 #endif  // BASE_STRINGS_STRING_NUMBER_CONVERSIONS_INTERNAL_H_
diff --git a/base/strings/string_number_conversions_unittest.cc b/base/strings/string_number_conversions_unittest.cc
index 8fbb00d..62015a1 100644
--- a/base/strings/string_number_conversions_unittest.cc
+++ b/base/strings/string_number_conversions_unittest.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "base/strings/string_number_conversions.h"
 
 #include <errno.h>
@@ -10,8 +15,10 @@
 #include <stdint.h>
 #include <stdio.h>
 
+#include <array>
 #include <cmath>
 #include <limits>
+#include <string_view>
 
 #include "base/bit_cast.h"
 #include "base/format_macros.h"
@@ -78,8 +85,9 @@
       {std::numeric_limits<uint64_t>::max(), "18446744073709551615"},
   };
 
-  for (const auto& i : cases)
+  for (const auto& i : cases) {
     EXPECT_EQ(i.output, NumberToString(i.input));
+  }
 }
 
 TEST(StringNumberConversionsTest, SizeTToString) {
@@ -90,19 +98,20 @@
     size_t input;
     std::string output;
   } cases[] = {
-    {0, "0"},
-    {9, "9"},
-    {42, "42"},
-    {INT_MAX, "2147483647"},
-    {2147483648U, "2147483648"},
+      {0, "0"},
+      {9, "9"},
+      {42, "42"},
+      {INT_MAX, "2147483647"},
+      {2147483648U, "2147483648"},
 #if SIZE_MAX > 4294967295U
-    {99999999999U, "99999999999"},
+      {99999999999U, "99999999999"},
 #endif
-    {size_t_max, size_t_max_string},
+      {size_t_max, size_t_max_string},
   };
 
-  for (const auto& i : cases)
+  for (const auto& i : cases) {
     EXPECT_EQ(i.output, NumberToString(i.input));
+  }
 }
 
 TEST(StringNumberConversionsTest, StringToInt) {
@@ -385,38 +394,38 @@
     size_t output;
     bool success;
   } cases[] = {
-    {"0", 0, true},
-    {"42", 42, true},
-    {"-2147483648", 0, false},
-    {"2147483647", INT_MAX, true},
-    {"-2147483649", 0, false},
-    {"-99999999999", 0, false},
-    {"2147483648", 2147483648U, true},
+      {"0", 0, true},
+      {"42", 42, true},
+      {"-2147483648", 0, false},
+      {"2147483647", INT_MAX, true},
+      {"-2147483649", 0, false},
+      {"-99999999999", 0, false},
+      {"2147483648", 2147483648U, true},
 #if SIZE_MAX > 4294967295U
-    {"99999999999", 99999999999U, true},
+      {"99999999999", 99999999999U, true},
 #endif
-    {"-9223372036854775808", 0, false},
-    {"09", 9, true},
-    {"-09", 0, false},
-    {"", 0, false},
-    {" 42", 42, false},
-    {"42 ", 42, false},
-    {"0x42", 0, false},
-    {"\t\n\v\f\r 42", 42, false},
-    {"blah42", 0, false},
-    {"42blah", 42, false},
-    {"blah42blah", 0, false},
-    {"-273.15", 0, false},
-    {"+98.6", 98, false},
-    {"--123", 0, false},
-    {"++123", 0, false},
-    {"-+123", 0, false},
-    {"+-123", 0, false},
-    {"-", 0, false},
-    {"-9223372036854775809", 0, false},
-    {"-99999999999999999999", 0, false},
-    {"999999999999999999999999", size_t_max, false},
-    {size_t_max_string, size_t_max, true},
+      {"-9223372036854775808", 0, false},
+      {"09", 9, true},
+      {"-09", 0, false},
+      {"", 0, false},
+      {" 42", 42, false},
+      {"42 ", 42, false},
+      {"0x42", 0, false},
+      {"\t\n\v\f\r 42", 42, false},
+      {"blah42", 0, false},
+      {"42blah", 42, false},
+      {"blah42blah", 0, false},
+      {"-273.15", 0, false},
+      {"+98.6", 98, false},
+      {"--123", 0, false},
+      {"++123", 0, false},
+      {"-+123", 0, false},
+      {"+-123", 0, false},
+      {"-", 0, false},
+      {"-9223372036854775809", 0, false},
+      {"-99999999999999999999", 0, false},
+      {"999999999999999999999999", size_t_max, false},
+      {size_t_max_string, size_t_max, true},
   };
 
   for (const auto& i : cases) {
@@ -696,12 +705,13 @@
 
 // Tests for HexStringToBytes, HexStringToString, HexStringToSpan.
 TEST(StringNumberConversionsTest, HexStringToBytesStringSpan) {
-  static const struct {
+  struct Cases {
     const std::string input;
     const char* output;
     size_t output_len;
     bool success;
-  } cases[] = {
+  };
+  static const auto cases = std::to_array<Cases>({
       {"0", "", 0, false},  // odd number of characters fails
       {"00", "\0", 1, true},
       {"42", "\x42", 1, true},
@@ -719,7 +729,7 @@
       {"0123456789ABCDEF", "\x01\x23\x45\x67\x89\xAB\xCD\xEF", 8, true},
       {"0123456789ABCDEF012345", "\x01\x23\x45\x67\x89\xAB\xCD\xEF\x01\x23\x45",
        11, true},
-  };
+  });
 
   for (size_t test_i = 0; test_i < std::size(cases); ++test_i) {
     const auto& test = cases[test_i];
@@ -765,8 +775,9 @@
     // Test HexStringToSpan() with an output that is 1 byte too small.
     {
       std::vector<uint8_t> output;
-      if (test.input.size() > 1)
+      if (test.input.size() > 1) {
         output.resize(test.input.size() / 2 - 1);
+      }
 
       EXPECT_FALSE(HexStringToSpan(test.input, output))
           << test_i << ": " << test.input;
@@ -784,11 +795,12 @@
 }
 
 TEST(StringNumberConversionsTest, StringToDouble) {
-  static const struct {
+  struct Cases {
     std::string input;
     double output;
     bool success;
-  } cases[] = {
+  };
+  static const auto cases = std::to_array<Cases>({
       // Test different forms of zero.
       {"0", 0.0, true},
       {"+0", 0.0, true},
@@ -862,7 +874,7 @@
       // crbug.org/588726
       {"-0.0010000000000000000000000000000000000000001e-256",
        -1.0000000000000001e-259, true},
-  };
+  });
 
   for (size_t i = 0; i < std::size(cases); ++i) {
     SCOPED_TRACE(
@@ -870,8 +882,9 @@
     double output;
     errno = 1;
     EXPECT_EQ(cases[i].success, StringToDouble(cases[i].input, &output));
-    if (cases[i].success)
+    if (cases[i].success) {
       EXPECT_EQ(1, errno) << i;  // confirm that errno is unchanged.
+    }
     EXPECT_DOUBLE_EQ(cases[i].output, output);
   }
 
@@ -919,6 +932,23 @@
   EXPECT_EQ("1.33489033216e+12", NumberToString(input));
 }
 
+TEST(StringNumberConversionsTest, DoubleToStringFixedPrecision) {
+  static const struct {
+    double input;
+    int digits;
+    const char* expected;
+  } cases[] = {
+      {0.0, 0, "0"},      {0.0, 3, "0.000"},     {0.5, 3, "0.500"},
+      {1.25, 3, "1.250"}, {1.33518, 3, "1.335"}, {1.33578, 3, "1.336"},
+  };
+
+  for (const auto& i : cases) {
+    EXPECT_EQ(i.expected, NumberToStringWithFixedPrecision(i.input, i.digits));
+    EXPECT_EQ(i.expected, UTF16ToUTF8(NumberToString16WithFixedPrecision(
+                              i.input, i.digits)));
+  }
+}
+
 TEST(StringNumberConversionsTest, AppendHexEncodedByte) {
   std::string hex;
   AppendHexEncodedByte(0, hex);
@@ -937,11 +967,31 @@
 }
 
 TEST(StringNumberConversionsTest, HexEncode) {
-  std::string hex(HexEncode(nullptr, 0));
-  EXPECT_EQ(hex.length(), 0U);
-  unsigned char bytes[] = {0x01, 0xff, 0x02, 0xfe, 0x03, 0x80, 0x81};
-  hex = HexEncode(bytes, sizeof(bytes));
-  EXPECT_EQ(hex, "01FF02FE038081");
+  EXPECT_EQ(HexEncode(nullptr, 0), "");
+  EXPECT_EQ(HexEncode(gurl_base::span<uint8_t>()), "");
+  EXPECT_EQ(HexEncode(std::string()), "");
+  EXPECT_EQ(HexEncodeLower(gurl_base::span<uint8_t>()), "");
+  EXPECT_EQ(HexEncodeLower(std::string()), "");
+
+  const auto kBytes = std::to_array<uint8_t>({
+      0x01,
+      0xff,
+      0x02,
+      0xfe,
+      0x03,
+      0x80,
+      0x81,
+  });
+  EXPECT_EQ(HexEncode(kBytes.data(), sizeof(kBytes)), "01FF02FE038081");
+  // Implicit span conversion:
+  EXPECT_EQ(HexEncode(kBytes), "01FF02FE038081");
+  EXPECT_EQ(HexEncodeLower(kBytes), "01ff02fe038081");
+
+  const std::string kString = "\x01\xff";
+  EXPECT_EQ(HexEncode(kString.c_str(), kString.size()), "01FF");
+  // Implicit std::string_view conversion:
+  EXPECT_EQ(HexEncode(kString), "01FF");
+  EXPECT_EQ(HexEncodeLower(kString), "01ff");
 }
 
 // Test cases of known-bad strtod conversions that motivated the use of dmg_fp.
diff --git a/base/strings/string_number_conversions_win.cc b/base/strings/string_number_conversions_win.cc
index 256adf1..d242a94 100644
--- a/base/strings/string_number_conversions_win.cc
+++ b/base/strings/string_number_conversions_win.cc
@@ -39,6 +39,10 @@
   return internal::DoubleToStringT<std::wstring>(value);
 }
 
+std::wstring NumberToWStringWithFixedPrecision(double value, int digits) {
+  return internal::DoubleToStringFixedT<std::wstring>(value, digits);
+}
+
 bool StringToInt(std::wstring_view input, int* output) {
   return internal::StringToIntImpl(input, *output);
 }
diff --git a/base/strings/string_number_conversions_win.h b/base/strings/string_number_conversions_win.h
index 1de7e87..43ae325 100644
--- a/base/strings/string_number_conversions_win.h
+++ b/base/strings/string_number_conversions_win.h
@@ -5,6 +5,7 @@
 #ifndef BASE_STRINGS_STRING_NUMBER_CONVERSIONS_WIN_H_
 #define BASE_STRINGS_STRING_NUMBER_CONVERSIONS_WIN_H_
 
+#include <cstdint>
 #include <string>
 #include <string_view>
 
@@ -19,6 +20,8 @@
 BASE_EXPORT std::wstring NumberToWString(long long value);
 BASE_EXPORT std::wstring NumberToWString(unsigned long long value);
 BASE_EXPORT std::wstring NumberToWString(double value);
+BASE_EXPORT std::wstring NumberToWStringWithFixedPrecision(double value,
+                                                           int digits);
 
 // The following section contains overloads of the cross-platform APIs for
 // std::wstring and std::wstring_view.
diff --git a/base/strings/string_piece.h b/base/strings/string_piece.h
deleted file mode 100644
index a72af4c..0000000
--- a/base/strings/string_piece.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2012 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-//
-// This header is deprecated. `gurl_base::StringPiece` is now `std::string_view`.
-// Use it and <string_view> instead.
-//
-// TODO(crbug.com/691162): Remove uses of this header.
-
-#ifndef BASE_STRINGS_STRING_PIECE_H_
-#define BASE_STRINGS_STRING_PIECE_H_
-
-#include <string_view>
-
-namespace gurl_base {
-
-template <typename CharT, typename Traits = std::char_traits<CharT>>
-using BasicStringPiece = std::basic_string_view<CharT, Traits>;
-using StringPiece = std::string_view;
-using StringPiece16 = std::u16string_view;
-
-}  // namespace base
-
-#endif  // BASE_STRINGS_STRING_PIECE_H_
diff --git a/base/strings/string_piece_rust.h b/base/strings/string_piece_rust.h
deleted file mode 100644
index 8bcb766..0000000
--- a/base/strings/string_piece_rust.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_STRINGS_STRING_PIECE_RUST_H_
-#define BASE_STRINGS_STRING_PIECE_RUST_H_
-
-#include <stdint.h>
-
-#include "base/rust_buildflags.h"
-#include "base/strings/string_piece.h"
-#include "third_party/rust/cxx/v1/cxx.h"
-
-#if !BUILDFLAG(BUILD_RUST_BASE_CONVERSIONS)
-#error "string_piece_rust.h included without BUILD_RUST_BASE_CONVERSIONS"
-#endif
-
-namespace gurl_base {
-
-// Create a Rust str from a gurl_base::BasicStringPiece. This will call std::abort
-// if there is any invalid UTF8. If you're concerned about this, then
-// instead use StringPieceToRustSlice and convert the data to a string on
-// the Rust side (or pass in a std::string).
-inline rust::Str StringPieceToRustStrUTF8(StringPiece string_piece) {
-  return rust::Str(string_piece.data(), string_piece.size());
-}
-
-// Create a Rust slice from a StringPiece. No UTF8 check is performed.
-inline rust::Slice<const uint8_t> StringPieceToRustSlice(
-    StringPiece string_piece) {
-  return rust::Slice<const uint8_t>(
-      reinterpret_cast<const uint8_t*>(string_piece.data()),
-      string_piece.length() * sizeof(StringPiece::value_type));
-}
-
-// Create a StringPiece from a Rust str.
-inline StringPiece RustStrToStringPiece(rust::Str str) {
-  return StringPiece(str.data(), str.size());
-}
-
-}  // namespace base
-
-#endif  // BASE_STRINGS_STRING_PIECE_RUST_H_
diff --git a/base/strings/string_piece_rust_unittest.cc b/base/strings/string_piece_rust_unittest.cc
index 38d50d4..8df3776 100644
--- a/base/strings/string_piece_rust_unittest.cc
+++ b/base/strings/string_piece_rust_unittest.cc
@@ -2,26 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/strings/string_piece_rust.h"
+#include <string_view>
 
+#include "base/strings/string_view_rust.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace gurl_base {
 namespace {
 
-TEST(BaseStringPieceRustTest, StrRoundTrip) {
+TEST(BaseStringViewRustTest, StrRoundTrip) {
   std::string data = "hello";
-  StringPiece data_piece(data);
-  rust::Str rust_str = StringPieceToRustStrUTF8(data_piece);
+  std::string_view data_piece(data);
+  rust::Str rust_str = StringViewToRustStrUTF8(data_piece);
   EXPECT_EQ(5ul, rust_str.length());
-  StringPiece data_piece2 = RustStrToStringPiece(rust_str);
+  std::string_view data_piece2 = RustStrToStringView(rust_str);
   EXPECT_EQ(data_piece, data_piece2);
 }
 
-TEST(BaseStringPieceRustTest, StrToSlice) {
+TEST(BaseStringViewRustTest, StrToSlice) {
   std::string data = "hello";
-  StringPiece data_piece(data);
-  rust::Slice<const uint8_t> rust_slice = StringPieceToRustSlice(data_piece);
+  std::string_view data_piece(data);
+  rust::Slice<const uint8_t> rust_slice = StringViewToRustSlice(data_piece);
   EXPECT_EQ(5ul, rust_slice.length());
   EXPECT_EQ('e', rust_slice[1]);
 }
diff --git a/base/strings/string_piece_unittest.cc b/base/strings/string_piece_unittest.cc
deleted file mode 100644
index 30819bb..0000000
--- a/base/strings/string_piece_unittest.cc
+++ /dev/null
@@ -1,920 +0,0 @@
-// Copyright 2012 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <stddef.h>
-
-#include <string>
-#include <string_view>
-
-#include "base/strings/string_piece.h"
-#include "base/strings/utf_string_conversions.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace gurl_base {
-
-template <typename CharT>
-class CommonStringPieceTest : public ::testing::Test {
- public:
-  static std::string as_string(const char* input) { return input; }
-  static const std::string& as_string(const std::string& input) {
-    return input;
-  }
-};
-
-template <>
-class CommonStringPieceTest<char16_t> : public ::testing::Test {
- public:
-  static std::u16string as_string(const char* input) {
-    return UTF8ToUTF16(input);
-  }
-  static std::u16string as_string(const std::string& input) {
-    return UTF8ToUTF16(input);
-  }
-};
-
-typedef ::testing::Types<char, char16_t> SupportedCharTypes;
-
-TYPED_TEST_SUITE(CommonStringPieceTest, SupportedCharTypes);
-
-TYPED_TEST(CommonStringPieceTest, CheckComparisonOperators) {
-#define CMP_Y(op, x, y)                                                   \
-  {                                                                       \
-    std::basic_string<TypeParam> lhs(TestFixture::as_string(x));          \
-    std::basic_string<TypeParam> rhs(TestFixture::as_string(y));          \
-    ASSERT_TRUE((BasicStringPiece<TypeParam>((lhs.c_str()))               \
-                     op BasicStringPiece<TypeParam>((rhs.c_str()))));     \
-    ASSERT_TRUE(BasicStringPiece<TypeParam>(lhs) op rhs);                 \
-    ASSERT_TRUE(lhs op BasicStringPiece<TypeParam>(rhs));                 \
-    ASSERT_TRUE((BasicStringPiece<TypeParam>((lhs.c_str()))               \
-                     .compare(BasicStringPiece<TypeParam>((rhs.c_str()))) \
-                         op 0));                                          \
-  }
-
-#define CMP_N(op, x, y)                                                    \
-  {                                                                        \
-    std::basic_string<TypeParam> lhs(TestFixture::as_string(x));           \
-    std::basic_string<TypeParam> rhs(TestFixture::as_string(y));           \
-    ASSERT_FALSE((BasicStringPiece<TypeParam>((lhs.c_str()))               \
-                      op BasicStringPiece<TypeParam>((rhs.c_str()))));     \
-    ASSERT_FALSE(BasicStringPiece<TypeParam>(lhs) op rhs);                 \
-    ASSERT_FALSE(lhs op BasicStringPiece<TypeParam>(rhs));                 \
-    ASSERT_FALSE((BasicStringPiece<TypeParam>((lhs.c_str()))               \
-                      .compare(BasicStringPiece<TypeParam>((rhs.c_str()))) \
-                          op 0));                                          \
-  }
-
-  CMP_Y(==, "", "")
-  CMP_Y(==, "a", "a")
-  CMP_Y(==, "aa", "aa")
-  CMP_N(==, "a", "")
-  CMP_N(==, "", "a")
-  CMP_N(==, "a", "b")
-  CMP_N(==, "a", "aa")
-  CMP_N(==, "aa", "a")
-
-  CMP_N(!=, "", "")
-  CMP_N(!=, "a", "a")
-  CMP_N(!=, "aa", "aa")
-  CMP_Y(!=, "a", "")
-  CMP_Y(!=, "", "a")
-  CMP_Y(!=, "a", "b")
-  CMP_Y(!=, "a", "aa")
-  CMP_Y(!=, "aa", "a")
-
-  CMP_Y(<, "a", "b")
-  CMP_Y(<, "a", "aa")
-  CMP_Y(<, "aa", "b")
-  CMP_Y(<, "aa", "bb")
-  CMP_N(<, "a", "a")
-  CMP_N(<, "b", "a")
-  CMP_N(<, "aa", "a")
-  CMP_N(<, "b", "aa")
-  CMP_N(<, "bb", "aa")
-
-  CMP_Y(<=, "a", "a")
-  CMP_Y(<=, "a", "b")
-  CMP_Y(<=, "a", "aa")
-  CMP_Y(<=, "aa", "b")
-  CMP_Y(<=, "aa", "bb")
-  CMP_N(<=, "b", "a")
-  CMP_N(<=, "aa", "a")
-  CMP_N(<=, "b", "aa")
-  CMP_N(<=, "bb", "aa")
-
-  CMP_N(>=, "a", "b")
-  CMP_N(>=, "a", "aa")
-  CMP_N(>=, "aa", "b")
-  CMP_N(>=, "aa", "bb")
-  CMP_Y(>=, "a", "a")
-  CMP_Y(>=, "b", "a")
-  CMP_Y(>=, "aa", "a")
-  CMP_Y(>=, "b", "aa")
-  CMP_Y(>=, "bb", "aa")
-
-  CMP_N(>, "a", "a")
-  CMP_N(>, "a", "b")
-  CMP_N(>, "a", "aa")
-  CMP_N(>, "aa", "b")
-  CMP_N(>, "aa", "bb")
-  CMP_Y(>, "b", "a")
-  CMP_Y(>, "aa", "a")
-  CMP_Y(>, "b", "aa")
-  CMP_Y(>, "bb", "aa")
-
-  std::string x;
-  for (int i = 0; i < 256; i++) {
-    x += 'a';
-    std::string y = x;
-    CMP_Y(==, x, y);
-    for (int j = 0; j < i; j++) {
-      std::string z = x;
-      z[j] = 'b';       // Differs in position 'j'
-      CMP_N(==, x, z);
-    }
-  }
-
-#undef CMP_Y
-#undef CMP_N
-}
-
-TYPED_TEST(CommonStringPieceTest, CheckSTL) {
-  std::basic_string<TypeParam> alphabet(
-      TestFixture::as_string("abcdefghijklmnopqrstuvwxyz"));
-  std::basic_string<TypeParam> abc(TestFixture::as_string("abc"));
-  std::basic_string<TypeParam> xyz(TestFixture::as_string("xyz"));
-  std::basic_string<TypeParam> foobar(TestFixture::as_string("foobar"));
-
-  BasicStringPiece<TypeParam> a(alphabet);
-  BasicStringPiece<TypeParam> b(abc);
-  BasicStringPiece<TypeParam> c(xyz);
-  BasicStringPiece<TypeParam> d(foobar);
-  BasicStringPiece<TypeParam> e;
-  std::basic_string<TypeParam> temp(TestFixture::as_string("123"));
-  temp += static_cast<TypeParam>(0);
-  temp += TestFixture::as_string("456");
-  BasicStringPiece<TypeParam> f(temp);
-
-  ASSERT_EQ(a[6], static_cast<TypeParam>('g'));
-  ASSERT_EQ(b[0], static_cast<TypeParam>('a'));
-  ASSERT_EQ(c[2], static_cast<TypeParam>('z'));
-  ASSERT_EQ(f[3], static_cast<TypeParam>('\0'));
-  ASSERT_EQ(f[5], static_cast<TypeParam>('5'));
-
-  ASSERT_EQ(*d.data(), static_cast<TypeParam>('f'));
-  ASSERT_EQ(d.data()[5], static_cast<TypeParam>('r'));
-  ASSERT_EQ(e.data(), nullptr);
-
-  ASSERT_EQ(*a.begin(), static_cast<TypeParam>('a'));
-  ASSERT_EQ(*(b.begin() + 2), static_cast<TypeParam>('c'));
-  ASSERT_EQ(*(c.end() - 1), static_cast<TypeParam>('z'));
-
-  ASSERT_EQ(*a.rbegin(), static_cast<TypeParam>('z'));
-  ASSERT_EQ(*(b.rbegin() + 2), static_cast<TypeParam>('a'));
-  ASSERT_EQ(*(c.rend() - 1), static_cast<TypeParam>('x'));
-  ASSERT_EQ(a.rbegin() + 26, a.rend());
-
-  ASSERT_EQ(a.size(), 26U);
-  ASSERT_EQ(b.size(), 3U);
-  ASSERT_EQ(c.size(), 3U);
-  ASSERT_EQ(d.size(), 6U);
-  ASSERT_EQ(e.size(), 0U);
-  ASSERT_EQ(f.size(), 7U);
-
-  ASSERT_TRUE(!d.empty());
-  ASSERT_TRUE(d.begin() != d.end());
-  ASSERT_EQ(d.begin() + 6, d.end());
-
-  ASSERT_TRUE(e.empty());
-  ASSERT_EQ(e.begin(), e.end());
-
-  d = BasicStringPiece<TypeParam>();
-  ASSERT_EQ(d.size(), 0U);
-  ASSERT_TRUE(d.empty());
-  ASSERT_EQ(d.data(), nullptr);
-  ASSERT_EQ(d.begin(), d.end());
-
-  ASSERT_GE(a.max_size(), a.size());
-}
-
-TYPED_TEST(CommonStringPieceTest, CheckFind) {
-  typedef BasicStringPiece<TypeParam> Piece;
-
-  std::basic_string<TypeParam> alphabet(
-      TestFixture::as_string("abcdefghijklmnopqrstuvwxyz"));
-  std::basic_string<TypeParam> abc(TestFixture::as_string("abc"));
-  std::basic_string<TypeParam> xyz(TestFixture::as_string("xyz"));
-  std::basic_string<TypeParam> foobar(TestFixture::as_string("foobar"));
-
-  BasicStringPiece<TypeParam> a(alphabet);
-  BasicStringPiece<TypeParam> b(abc);
-  BasicStringPiece<TypeParam> c(xyz);
-  BasicStringPiece<TypeParam> d(foobar);
-
-  d = Piece();
-  Piece e;
-  std::basic_string<TypeParam> temp(TestFixture::as_string("123"));
-  temp.push_back('\0');
-  temp += TestFixture::as_string("456");
-  Piece f(temp);
-
-  TypeParam buf[4] = {'%', '%', '%', '%'};
-  ASSERT_EQ(a.copy(buf, 4), 4U);
-  ASSERT_EQ(buf[0], a[0]);
-  ASSERT_EQ(buf[1], a[1]);
-  ASSERT_EQ(buf[2], a[2]);
-  ASSERT_EQ(buf[3], a[3]);
-  ASSERT_EQ(a.copy(buf, 3, 7), 3U);
-  ASSERT_EQ(buf[0], a[7]);
-  ASSERT_EQ(buf[1], a[8]);
-  ASSERT_EQ(buf[2], a[9]);
-  ASSERT_EQ(buf[3], a[3]);
-  ASSERT_EQ(c.copy(buf, 99), 3U);
-  ASSERT_EQ(buf[0], c[0]);
-  ASSERT_EQ(buf[1], c[1]);
-  ASSERT_EQ(buf[2], c[2]);
-  ASSERT_EQ(buf[3], a[3]);
-
-  ASSERT_EQ(Piece::npos, std::basic_string<TypeParam>::npos);
-
-  ASSERT_EQ(a.find(b), 0U);
-  ASSERT_EQ(a.find(b, 1), Piece::npos);
-  ASSERT_EQ(a.find(c), 23U);
-  ASSERT_EQ(a.find(c, 9), 23U);
-  ASSERT_EQ(a.find(c, Piece::npos), Piece::npos);
-  ASSERT_EQ(b.find(c), Piece::npos);
-  ASSERT_EQ(b.find(c, Piece::npos), Piece::npos);
-  ASSERT_EQ(a.find(d), 0U);
-  ASSERT_EQ(a.find(e), 0U);
-  ASSERT_EQ(a.find(d, 12), 12U);
-  ASSERT_EQ(a.find(e, 17), 17U);
-  std::basic_string<TypeParam> not_found(
-      TestFixture::as_string("xx not found bb"));
-  Piece g(not_found);
-  ASSERT_EQ(a.find(g), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(d.find(b), Piece::npos);
-  ASSERT_EQ(e.find(b), Piece::npos);
-  ASSERT_EQ(d.find(b, 4), Piece::npos);
-  ASSERT_EQ(e.find(b, 7), Piece::npos);
-
-  size_t empty_search_pos =
-      std::basic_string<TypeParam>().find(std::basic_string<TypeParam>());
-  ASSERT_EQ(d.find(d), empty_search_pos);
-  ASSERT_EQ(d.find(e), empty_search_pos);
-  ASSERT_EQ(e.find(d), empty_search_pos);
-  ASSERT_EQ(e.find(e), empty_search_pos);
-  ASSERT_EQ(d.find(d, 4), std::string().find(std::string(), 4));
-  ASSERT_EQ(d.find(e, 4), std::string().find(std::string(), 4));
-  ASSERT_EQ(e.find(d, 4), std::string().find(std::string(), 4));
-  ASSERT_EQ(e.find(e, 4), std::string().find(std::string(), 4));
-
-  constexpr TypeParam kNul = '\0';
-  ASSERT_EQ(a.find('a'), 0U);
-  ASSERT_EQ(a.find('c'), 2U);
-  ASSERT_EQ(a.find('z'), 25U);
-  ASSERT_EQ(a.find('$'), Piece::npos);
-  ASSERT_EQ(a.find(kNul), Piece::npos);
-  ASSERT_EQ(f.find(kNul), 3U);
-  ASSERT_EQ(f.find('3'), 2U);
-  ASSERT_EQ(f.find('5'), 5U);
-  ASSERT_EQ(g.find('o'), 4U);
-  ASSERT_EQ(g.find('o', 4), 4U);
-  ASSERT_EQ(g.find('o', 5), 8U);
-  ASSERT_EQ(a.find('b', 5), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(d.find(kNul), Piece::npos);
-  ASSERT_EQ(e.find(kNul), Piece::npos);
-  ASSERT_EQ(d.find(kNul, 4), Piece::npos);
-  ASSERT_EQ(e.find(kNul, 7), Piece::npos);
-  ASSERT_EQ(d.find('x'), Piece::npos);
-  ASSERT_EQ(e.find('x'), Piece::npos);
-  ASSERT_EQ(d.find('x', 4), Piece::npos);
-  ASSERT_EQ(e.find('x', 7), Piece::npos);
-
-  ASSERT_EQ(a.find(b.data(), 1, 0), 1U);
-  ASSERT_EQ(a.find(c.data(), 9, 0), 9U);
-  ASSERT_EQ(a.find(c.data(), Piece::npos, 0), Piece::npos);
-  ASSERT_EQ(b.find(c.data(), Piece::npos, 0), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(d.find(b.data(), 4, 0), Piece::npos);
-  ASSERT_EQ(e.find(b.data(), 7, 0), Piece::npos);
-
-  ASSERT_EQ(a.find(b.data(), 1), Piece::npos);
-  ASSERT_EQ(a.find(c.data(), 9), 23U);
-  ASSERT_EQ(a.find(c.data(), Piece::npos), Piece::npos);
-  ASSERT_EQ(b.find(c.data(), Piece::npos), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(d.find(b.data(), 4), Piece::npos);
-  ASSERT_EQ(e.find(b.data(), 7), Piece::npos);
-
-  ASSERT_EQ(a.rfind(b), 0U);
-  ASSERT_EQ(a.rfind(b, 1), 0U);
-  ASSERT_EQ(a.rfind(c), 23U);
-  ASSERT_EQ(a.rfind(c, 22U), Piece::npos);
-  ASSERT_EQ(a.rfind(c, 1U), Piece::npos);
-  ASSERT_EQ(a.rfind(c, 0U), Piece::npos);
-  ASSERT_EQ(b.rfind(c), Piece::npos);
-  ASSERT_EQ(b.rfind(c, 0U), Piece::npos);
-  ASSERT_EQ(a.rfind(d),
-            static_cast<size_t>(a.rfind(std::basic_string<TypeParam>())));
-  ASSERT_EQ(a.rfind(e), a.rfind(std::basic_string<TypeParam>()));
-  ASSERT_EQ(a.rfind(d),
-            static_cast<size_t>(std::basic_string<TypeParam>(a).rfind(
-                std::basic_string<TypeParam>())));
-  ASSERT_EQ(a.rfind(e), std::basic_string<TypeParam>(a).rfind(
-                            std::basic_string<TypeParam>()));
-  ASSERT_EQ(a.rfind(d, 12), 12U);
-  ASSERT_EQ(a.rfind(e, 17), 17U);
-  ASSERT_EQ(a.rfind(g), Piece::npos);
-  ASSERT_EQ(d.rfind(b), Piece::npos);
-  ASSERT_EQ(e.rfind(b), Piece::npos);
-  ASSERT_EQ(d.rfind(b, 4), Piece::npos);
-  ASSERT_EQ(e.rfind(b, 7), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(d.rfind(d, 4), std::string().rfind(std::string()));
-  ASSERT_EQ(e.rfind(d, 7), std::string().rfind(std::string()));
-  ASSERT_EQ(d.rfind(e, 4), std::string().rfind(std::string()));
-  ASSERT_EQ(e.rfind(e, 7), std::string().rfind(std::string()));
-  ASSERT_EQ(d.rfind(d), std::string().rfind(std::string()));
-  ASSERT_EQ(e.rfind(d), std::string().rfind(std::string()));
-  ASSERT_EQ(d.rfind(e), std::string().rfind(std::string()));
-  ASSERT_EQ(e.rfind(e), std::string().rfind(std::string()));
-
-  ASSERT_EQ(g.rfind('o'), 8U);
-  ASSERT_EQ(g.rfind('q'), Piece::npos);
-  ASSERT_EQ(g.rfind('o', 8), 8U);
-  ASSERT_EQ(g.rfind('o', 7), 4U);
-  ASSERT_EQ(g.rfind('o', 3), Piece::npos);
-  ASSERT_EQ(f.rfind(kNul), 3U);
-  ASSERT_EQ(f.rfind(kNul, 12), 3U);
-  ASSERT_EQ(f.rfind('3'), 2U);
-  ASSERT_EQ(f.rfind('5'), 5U);
-  // empty string nonsense
-  ASSERT_EQ(d.rfind('o'), Piece::npos);
-  ASSERT_EQ(e.rfind('o'), Piece::npos);
-  ASSERT_EQ(d.rfind('o', 4), Piece::npos);
-  ASSERT_EQ(e.rfind('o', 7), Piece::npos);
-
-  ASSERT_EQ(a.rfind(b.data(), 1, 0), 1U);
-  ASSERT_EQ(a.rfind(c.data(), 22U, 0), 22U);
-  ASSERT_EQ(a.rfind(c.data(), 1U, 0), 1U);
-  ASSERT_EQ(a.rfind(c.data(), 0U, 0), 0U);
-  ASSERT_EQ(b.rfind(c.data(), 0U, 0), 0U);
-  ASSERT_EQ(d.rfind(b.data(), 4, 0), 0U);
-  ASSERT_EQ(e.rfind(b.data(), 7, 0), 0U);
-
-  std::basic_string<TypeParam> one_two_three_four(
-      TestFixture::as_string("one,two:three;four"));
-  std::basic_string<TypeParam> comma_colon(TestFixture::as_string(",:"));
-  ASSERT_EQ(3U, Piece(one_two_three_four).find_first_of(comma_colon));
-  ASSERT_EQ(a.find_first_of(b), 0U);
-  ASSERT_EQ(a.find_first_of(b, 0), 0U);
-  ASSERT_EQ(a.find_first_of(b, 1), 1U);
-  ASSERT_EQ(a.find_first_of(b, 2), 2U);
-  ASSERT_EQ(a.find_first_of(b, 3), Piece::npos);
-  ASSERT_EQ(a.find_first_of(c), 23U);
-  ASSERT_EQ(a.find_first_of(c, 23), 23U);
-  ASSERT_EQ(a.find_first_of(c, 24), 24U);
-  ASSERT_EQ(a.find_first_of(c, 25), 25U);
-  ASSERT_EQ(a.find_first_of(c, 26), Piece::npos);
-  ASSERT_EQ(g.find_first_of(b), 13U);
-  ASSERT_EQ(g.find_first_of(c), 0U);
-  ASSERT_EQ(a.find_first_of(f), Piece::npos);
-  ASSERT_EQ(f.find_first_of(a), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(a.find_first_of(d), Piece::npos);
-  ASSERT_EQ(a.find_first_of(e), Piece::npos);
-  ASSERT_EQ(d.find_first_of(b), Piece::npos);
-  ASSERT_EQ(e.find_first_of(b), Piece::npos);
-  ASSERT_EQ(d.find_first_of(d), Piece::npos);
-  ASSERT_EQ(e.find_first_of(d), Piece::npos);
-  ASSERT_EQ(d.find_first_of(e), Piece::npos);
-  ASSERT_EQ(e.find_first_of(e), Piece::npos);
-
-  ASSERT_EQ(a.find_first_not_of(b), 3U);
-  ASSERT_EQ(a.find_first_not_of(c), 0U);
-  ASSERT_EQ(b.find_first_not_of(a), Piece::npos);
-  ASSERT_EQ(c.find_first_not_of(a), Piece::npos);
-  ASSERT_EQ(f.find_first_not_of(a), 0U);
-  ASSERT_EQ(a.find_first_not_of(f), 0U);
-  ASSERT_EQ(a.find_first_not_of(d), 0U);
-  ASSERT_EQ(a.find_first_not_of(e), 0U);
-  ASSERT_EQ(a.find_first_not_of(d, 1), 1U);
-  ASSERT_EQ(a.find_first_not_of(e, 1), 1U);
-  ASSERT_EQ(a.find_first_not_of(d, a.size()), Piece::npos);
-  ASSERT_EQ(a.find_first_not_of(e, a.size()), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(d.find_first_not_of(a), Piece::npos);
-  ASSERT_EQ(e.find_first_not_of(a), Piece::npos);
-  ASSERT_EQ(d.find_first_not_of(d), Piece::npos);
-  ASSERT_EQ(e.find_first_not_of(d), Piece::npos);
-  ASSERT_EQ(d.find_first_not_of(e), Piece::npos);
-  ASSERT_EQ(e.find_first_not_of(e), Piece::npos);
-
-  std::basic_string<TypeParam> equals(TestFixture::as_string("===="));
-  Piece h(equals);
-  ASSERT_EQ(h.find_first_not_of('='), Piece::npos);
-  ASSERT_EQ(h.find_first_not_of('=', 3), Piece::npos);
-  ASSERT_EQ(h.find_first_not_of(kNul), 0U);
-  ASSERT_EQ(g.find_first_not_of('x'), 2U);
-  ASSERT_EQ(f.find_first_not_of(kNul), 0U);
-  ASSERT_EQ(f.find_first_not_of(kNul, 3), 4U);
-  ASSERT_EQ(f.find_first_not_of(kNul, 2), 2U);
-  // empty string nonsense
-  ASSERT_EQ(d.find_first_not_of('x'), Piece::npos);
-  ASSERT_EQ(e.find_first_not_of('x'), Piece::npos);
-  ASSERT_EQ(d.find_first_not_of(kNul), Piece::npos);
-  ASSERT_EQ(e.find_first_not_of(kNul), Piece::npos);
-
-  //  Piece g("xx not found bb");
-  std::basic_string<TypeParam> fifty_six(TestFixture::as_string("56"));
-  Piece i(fifty_six);
-  ASSERT_EQ(h.find_last_of(a), Piece::npos);
-  ASSERT_EQ(g.find_last_of(a), g.size()-1);
-  ASSERT_EQ(a.find_last_of(b), 2U);
-  ASSERT_EQ(a.find_last_of(c), a.size()-1);
-  ASSERT_EQ(f.find_last_of(i), 6U);
-  ASSERT_EQ(a.find_last_of('a'), 0U);
-  ASSERT_EQ(a.find_last_of('b'), 1U);
-  ASSERT_EQ(a.find_last_of('z'), 25U);
-  ASSERT_EQ(a.find_last_of('a', 5), 0U);
-  ASSERT_EQ(a.find_last_of('b', 5), 1U);
-  ASSERT_EQ(a.find_last_of('b', 0), Piece::npos);
-  ASSERT_EQ(a.find_last_of('z', 25), 25U);
-  ASSERT_EQ(a.find_last_of('z', 24), Piece::npos);
-  ASSERT_EQ(f.find_last_of(i, 5), 5U);
-  ASSERT_EQ(f.find_last_of(i, 6), 6U);
-  ASSERT_EQ(f.find_last_of(a, 4), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(f.find_last_of(d), Piece::npos);
-  ASSERT_EQ(f.find_last_of(e), Piece::npos);
-  ASSERT_EQ(f.find_last_of(d, 4), Piece::npos);
-  ASSERT_EQ(f.find_last_of(e, 4), Piece::npos);
-  ASSERT_EQ(d.find_last_of(d), Piece::npos);
-  ASSERT_EQ(d.find_last_of(e), Piece::npos);
-  ASSERT_EQ(e.find_last_of(d), Piece::npos);
-  ASSERT_EQ(e.find_last_of(e), Piece::npos);
-  ASSERT_EQ(d.find_last_of(f), Piece::npos);
-  ASSERT_EQ(e.find_last_of(f), Piece::npos);
-  ASSERT_EQ(d.find_last_of(d, 4), Piece::npos);
-  ASSERT_EQ(d.find_last_of(e, 4), Piece::npos);
-  ASSERT_EQ(e.find_last_of(d, 4), Piece::npos);
-  ASSERT_EQ(e.find_last_of(e, 4), Piece::npos);
-  ASSERT_EQ(d.find_last_of(f, 4), Piece::npos);
-  ASSERT_EQ(e.find_last_of(f, 4), Piece::npos);
-
-  ASSERT_EQ(a.find_last_not_of(b), a.size()-1);
-  ASSERT_EQ(a.find_last_not_of(c), 22U);
-  ASSERT_EQ(b.find_last_not_of(a), Piece::npos);
-  ASSERT_EQ(b.find_last_not_of(b), Piece::npos);
-  ASSERT_EQ(f.find_last_not_of(i), 4U);
-  ASSERT_EQ(a.find_last_not_of(c, 24), 22U);
-  ASSERT_EQ(a.find_last_not_of(b, 3), 3U);
-  ASSERT_EQ(a.find_last_not_of(b, 2), Piece::npos);
-  // empty string nonsense
-  ASSERT_EQ(f.find_last_not_of(d), f.size()-1);
-  ASSERT_EQ(f.find_last_not_of(e), f.size()-1);
-  ASSERT_EQ(f.find_last_not_of(d, 4), 4U);
-  ASSERT_EQ(f.find_last_not_of(e, 4), 4U);
-  ASSERT_EQ(d.find_last_not_of(d), Piece::npos);
-  ASSERT_EQ(d.find_last_not_of(e), Piece::npos);
-  ASSERT_EQ(e.find_last_not_of(d), Piece::npos);
-  ASSERT_EQ(e.find_last_not_of(e), Piece::npos);
-  ASSERT_EQ(d.find_last_not_of(f), Piece::npos);
-  ASSERT_EQ(e.find_last_not_of(f), Piece::npos);
-  ASSERT_EQ(d.find_last_not_of(d, 4), Piece::npos);
-  ASSERT_EQ(d.find_last_not_of(e, 4), Piece::npos);
-  ASSERT_EQ(e.find_last_not_of(d, 4), Piece::npos);
-  ASSERT_EQ(e.find_last_not_of(e, 4), Piece::npos);
-  ASSERT_EQ(d.find_last_not_of(f, 4), Piece::npos);
-  ASSERT_EQ(e.find_last_not_of(f, 4), Piece::npos);
-
-  ASSERT_EQ(h.find_last_not_of('x'), h.size() - 1);
-  ASSERT_EQ(h.find_last_not_of('='), Piece::npos);
-  ASSERT_EQ(b.find_last_not_of('c'), 1U);
-  ASSERT_EQ(h.find_last_not_of('x', 2), 2U);
-  ASSERT_EQ(h.find_last_not_of('=', 2), Piece::npos);
-  ASSERT_EQ(b.find_last_not_of('b', 1), 0U);
-  // empty string nonsense
-  ASSERT_EQ(d.find_last_not_of('x'), Piece::npos);
-  ASSERT_EQ(e.find_last_not_of('x'), Piece::npos);
-  ASSERT_EQ(d.find_last_not_of(kNul), Piece::npos);
-  ASSERT_EQ(e.find_last_not_of(kNul), Piece::npos);
-
-  ASSERT_EQ(a.substr(0, 3), b);
-  ASSERT_EQ(a.substr(23), c);
-  ASSERT_EQ(a.substr(23, 3), c);
-  ASSERT_EQ(a.substr(23, 99), c);
-  ASSERT_EQ(a.substr(), a);
-  ASSERT_EQ(a.substr(0), a);
-  ASSERT_EQ(a.substr(3, 2), TestFixture::as_string("de"));
-  ASSERT_EQ(d.substr(0, 99), e);
-}
-
-TYPED_TEST(CommonStringPieceTest, CheckCustom) {
-  std::basic_string<TypeParam> foobar(TestFixture::as_string("foobar"));
-  BasicStringPiece<TypeParam> a(foobar);
-  std::basic_string<TypeParam> s1(TestFixture::as_string("123"));
-  s1 += static_cast<TypeParam>('\0');
-  s1 += TestFixture::as_string("456");
-  [[maybe_unused]] BasicStringPiece<TypeParam> b(s1);
-  BasicStringPiece<TypeParam> e;
-  std::basic_string<TypeParam> s2;
-
-  // remove_prefix
-  BasicStringPiece<TypeParam> c(a);
-  c.remove_prefix(3);
-  ASSERT_EQ(c, TestFixture::as_string("bar"));
-  c = a;
-  c.remove_prefix(0);
-  ASSERT_EQ(c, a);
-  c.remove_prefix(c.size());
-  ASSERT_EQ(c, e);
-
-  // remove_suffix
-  c = a;
-  c.remove_suffix(3);
-  ASSERT_EQ(c, TestFixture::as_string("foo"));
-  c = a;
-  c.remove_suffix(0);
-  ASSERT_EQ(c, a);
-  c.remove_suffix(c.size());
-  ASSERT_EQ(c, e);
-
-  // assignment
-  c = foobar.c_str();
-  ASSERT_EQ(c, a);
-  c = {foobar.c_str(), 6};
-  ASSERT_EQ(c, a);
-  c = {foobar.c_str(), 0};
-  ASSERT_EQ(c, e);
-  c = {foobar.c_str(), 7};  // Note, has an embedded NULL
-  ASSERT_NE(c, a);
-
-  // operator STRING_TYPE()
-  std::basic_string<TypeParam> s5(std::basic_string<TypeParam>(a).c_str(),
-                                  7);  // Note, has an embedded NULL
-  ASSERT_EQ(c, s5);
-  std::basic_string<TypeParam> s6(e);
-  ASSERT_TRUE(s6.empty());
-}
-
-TEST(StringPieceTest, CheckCustom) {
-  StringPiece a("foobar");
-  std::string s1("123");
-  s1 += '\0';
-  s1 += "456";
-  [[maybe_unused]] StringPiece b(s1);
-  StringPiece e;
-  std::string s2;
-
-  StringPiece c;
-  c = {"foobar", 6};
-  ASSERT_EQ(c, a);
-  c = {"foobar", 0};
-  ASSERT_EQ(c, e);
-  c = {"foobar", 7};
-  ASSERT_NE(c, a);
-}
-
-TYPED_TEST(CommonStringPieceTest, CheckNULL) {
-  BasicStringPiece<TypeParam> s;
-  ASSERT_EQ(s.data(), nullptr);
-  ASSERT_EQ(s.size(), 0U);
-
-  std::basic_string<TypeParam> str(s);
-  ASSERT_EQ(str.length(), 0U);
-  ASSERT_EQ(str, std::basic_string<TypeParam>());
-}
-
-TYPED_TEST(CommonStringPieceTest, CheckComparisons2) {
-  std::basic_string<TypeParam> alphabet(
-      TestFixture::as_string("abcdefghijklmnopqrstuvwxyz"));
-  std::basic_string<TypeParam> alphabet_z(
-      TestFixture::as_string("abcdefghijklmnopqrstuvwxyzz"));
-  std::basic_string<TypeParam> alphabet_y(
-      TestFixture::as_string("abcdefghijklmnopqrstuvwxyy"));
-  BasicStringPiece<TypeParam> abc(alphabet);
-
-  // check comparison operations on strings longer than 4 bytes.
-  ASSERT_EQ(abc, BasicStringPiece<TypeParam>(alphabet));
-  ASSERT_EQ(abc.compare(BasicStringPiece<TypeParam>(alphabet)), 0);
-
-  ASSERT_TRUE(abc < BasicStringPiece<TypeParam>(alphabet_z));
-  ASSERT_LT(abc.compare(BasicStringPiece<TypeParam>(alphabet_z)), 0);
-
-  ASSERT_TRUE(abc > BasicStringPiece<TypeParam>(alphabet_y));
-  ASSERT_GT(abc.compare(BasicStringPiece<TypeParam>(alphabet_y)), 0);
-}
-
-TYPED_TEST(CommonStringPieceTest, StringCompareNotAmbiguous) {
-  ASSERT_TRUE(TestFixture::as_string("hello").c_str() ==
-              TestFixture::as_string("hello"));
-  ASSERT_TRUE(TestFixture::as_string("hello").c_str() <
-              TestFixture::as_string("world"));
-}
-
-TYPED_TEST(CommonStringPieceTest, HeterogenousStringPieceEquals) {
-  std::basic_string<TypeParam> hello(TestFixture::as_string("hello"));
-
-  ASSERT_EQ(BasicStringPiece<TypeParam>(hello), hello);
-  ASSERT_EQ(hello.c_str(), BasicStringPiece<TypeParam>(hello));
-}
-
-// std::u16string-specific stuff
-TEST(StringPiece16Test, CheckSTL) {
-  // Check some non-ascii characters.
-  std::u16string fifth(u"123");
-  fifth.push_back(0x0000);
-  fifth.push_back(0xd8c5);
-  fifth.push_back(0xdffe);
-  StringPiece16 f(fifth);
-
-  ASSERT_EQ(f[3], '\0');
-  ASSERT_EQ(f[5], 0xdffe);
-
-  ASSERT_EQ(f.size(), 6U);
-}
-
-TEST(StringPiece16Test, CheckConversion) {
-  // Make sure that we can convert from UTF8 to UTF16 and back. We use a
-  // character (G clef) outside the BMP to test this.
-  const char* kTest = "\U0001D11E";
-  ASSERT_EQ(UTF16ToUTF8(UTF8ToUTF16(kTest)), kTest);
-}
-
-TYPED_TEST(CommonStringPieceTest, CheckConstructors) {
-  std::basic_string<TypeParam> str(TestFixture::as_string("hello world"));
-  std::basic_string<TypeParam> empty;
-
-  ASSERT_EQ(str, BasicStringPiece<TypeParam>(str));
-  ASSERT_EQ(str, BasicStringPiece<TypeParam>(str.c_str()));
-  ASSERT_TRUE(TestFixture::as_string("hello") ==
-              BasicStringPiece<TypeParam>(str.c_str(), 5));
-  ASSERT_EQ(
-      empty,
-      BasicStringPiece<TypeParam>(
-          str.c_str(),
-          static_cast<typename BasicStringPiece<TypeParam>::size_type>(0)));
-  ASSERT_EQ(empty, BasicStringPiece<TypeParam>());
-  ASSERT_TRUE(
-      empty ==
-      BasicStringPiece<TypeParam>(
-          nullptr,
-          static_cast<typename BasicStringPiece<TypeParam>::size_type>(0)));
-  ASSERT_EQ(empty, BasicStringPiece<TypeParam>());
-  ASSERT_EQ(empty, BasicStringPiece<TypeParam>(empty));
-}
-
-TEST(StringPieceTest, ConstexprCtor) {
-  {
-    constexpr StringPiece piece;
-    std::ignore = piece;
-  }
-
-  {
-    constexpr StringPiece piece("abc");
-    std::ignore = piece;
-  }
-
-  {
-    constexpr StringPiece piece("abc", 2);
-    std::ignore = piece;
-  }
-}
-
-// Chromium development assumes StringPiece (which is std::string_view) is
-// implemented with an STL that enables hardening checks. We treat bugs that
-// trigger one of these conditions as functional rather than security bugs. If
-// this test fails on some embedder, it should not be disabled. Instead, the
-// embedder should fix their STL or build configuration to enable corresponding
-// hardening checks.
-//
-// See https://chromium.googlesource.com/chromium/src/+/main/docs/security/faq.md#indexing-a-container-out-of-bounds-hits-a-libcpp_verbose_abort_is-this-a-security-bug
-TEST(StringPieceTest, OutOfBoundsDeath) {
-  {
-    constexpr StringPiece piece;
-    ASSERT_DEATH_IF_SUPPORTED(piece[0], "");
-  }
-
-  {
-    constexpr StringPiece piece;
-    ASSERT_DEATH_IF_SUPPORTED(piece.front(), "");
-  }
-
-  {
-    constexpr StringPiece piece;
-    ASSERT_DEATH_IF_SUPPORTED(piece.back(), "");
-  }
-
-  {
-    StringPiece piece;
-    ASSERT_DEATH_IF_SUPPORTED(piece.remove_suffix(1), "");
-  }
-
-  {
-    StringPiece piece;
-    ASSERT_DEATH_IF_SUPPORTED(piece.remove_prefix(1), "");
-  }
-
-  {
-    StringPiece piece;
-    ASSERT_DEATH_IF_SUPPORTED(piece.copy(nullptr, 0, 1), "");
-  }
-
-  {
-    StringPiece piece;
-    ASSERT_DEATH_IF_SUPPORTED(piece.substr(1), "");
-  }
-}
-
-TEST(StringPieceTest, InvalidLengthDeath) {
-  int length = -1;
-  ASSERT_DEATH_IF_SUPPORTED({ StringPiece piece("hello", length); }, "");
-}
-
-TEST(StringPieceTest, ConstexprData) {
-  {
-    constexpr StringPiece piece;
-    static_assert(piece.data() == nullptr, "");
-  }
-
-  {
-    constexpr StringPiece piece("abc");
-    static_assert(piece.data()[0] == 'a', "");
-    static_assert(piece.data()[1] == 'b', "");
-    static_assert(piece.data()[2] == 'c', "");
-  }
-
-  {
-    constexpr StringPiece piece("def", 2);
-    static_assert(piece.data()[0] == 'd', "");
-    static_assert(piece.data()[1] == 'e', "");
-  }
-}
-
-TEST(StringPieceTest, ConstexprSize) {
-  {
-    constexpr StringPiece piece;
-    static_assert(piece.size() == 0, "");
-  }
-
-  {
-    constexpr StringPiece piece("abc");
-    static_assert(piece.size() == 3, "");
-  }
-
-  {
-    constexpr StringPiece piece("def", 2);
-    static_assert(piece.size() == 2, "");
-  }
-}
-
-TEST(StringPieceTest, ConstexprFront) {
-  static_assert(StringPiece("abc").front() == 'a', "");
-}
-
-TEST(StringPieceTest, ConstexprBack) {
-  static_assert(StringPiece("abc").back() == 'c', "");
-}
-
-TEST(StringPieceTest, Compare) {
-  constexpr StringPiece piece = "def";
-
-  static_assert(piece.compare("ab") == 1, "");
-  static_assert(piece.compare("abc") == 1, "");
-  static_assert(piece.compare("abcd") == 1, "");
-  static_assert(piece.compare("de") == 1, "");
-  static_assert(piece.compare("def") == 0, "");
-  static_assert(piece.compare("defg") == -1, "");
-  static_assert(piece.compare("gh") == -1, "");
-  static_assert(piece.compare("ghi") == -1, "");
-  static_assert(piece.compare("ghij") == -1, "");
-
-  static_assert(piece.compare(0, 0, "") == 0, "");
-  static_assert(piece.compare(0, 1, "d") == 0, "");
-  static_assert(piece.compare(0, 2, "de") == 0, "");
-  static_assert(piece.compare(0, 3, "def") == 0, "");
-  static_assert(piece.compare(1, 0, "") == 0, "");
-  static_assert(piece.compare(1, 1, "e") == 0, "");
-  static_assert(piece.compare(1, 2, "ef") == 0, "");
-  static_assert(piece.compare(1, 3, "ef") == 0, "");
-  static_assert(piece.compare(2, 0, "") == 0, "");
-  static_assert(piece.compare(2, 1, "f") == 0, "");
-  static_assert(piece.compare(2, 2, "f") == 0, "");
-  static_assert(piece.compare(2, 3, "f") == 0, "");
-  static_assert(piece.compare(3, 0, "") == 0, "");
-  static_assert(piece.compare(3, 1, "") == 0, "");
-  static_assert(piece.compare(3, 2, "") == 0, "");
-  static_assert(piece.compare(3, 3, "") == 0, "");
-
-  static_assert(piece.compare(0, 0, "def", 0) == 0, "");
-  static_assert(piece.compare(0, 1, "def", 1) == 0, "");
-  static_assert(piece.compare(0, 2, "def", 2) == 0, "");
-  static_assert(piece.compare(0, 3, "def", 3) == 0, "");
-  static_assert(piece.compare(1, 0, "ef", 0) == 0, "");
-  static_assert(piece.compare(1, 1, "ef", 1) == 0, "");
-  static_assert(piece.compare(1, 2, "ef", 2) == 0, "");
-  static_assert(piece.compare(1, 3, "ef", 2) == 0, "");
-  static_assert(piece.compare(2, 0, "f", 0) == 0, "");
-  static_assert(piece.compare(2, 1, "f", 1) == 0, "");
-  static_assert(piece.compare(2, 2, "f", 1) == 0, "");
-  static_assert(piece.compare(2, 3, "f", 1) == 0, "");
-  static_assert(piece.compare(3, 0, "", 0) == 0, "");
-  static_assert(piece.compare(3, 1, "", 0) == 0, "");
-  static_assert(piece.compare(3, 2, "", 0) == 0, "");
-  static_assert(piece.compare(3, 3, "", 0) == 0, "");
-
-  static_assert(piece.compare(0, 0, "def", 0, 0) == 0, "");
-  static_assert(piece.compare(0, 1, "def", 0, 1) == 0, "");
-  static_assert(piece.compare(0, 2, "def", 0, 2) == 0, "");
-  static_assert(piece.compare(0, 3, "def", 0, 3) == 0, "");
-  static_assert(piece.compare(1, 0, "def", 1, 0) == 0, "");
-  static_assert(piece.compare(1, 1, "def", 1, 1) == 0, "");
-  static_assert(piece.compare(1, 2, "def", 1, 2) == 0, "");
-  static_assert(piece.compare(1, 3, "def", 1, 3) == 0, "");
-  static_assert(piece.compare(2, 0, "def", 2, 0) == 0, "");
-  static_assert(piece.compare(2, 1, "def", 2, 1) == 0, "");
-  static_assert(piece.compare(2, 2, "def", 2, 2) == 0, "");
-  static_assert(piece.compare(2, 3, "def", 2, 3) == 0, "");
-  static_assert(piece.compare(3, 0, "def", 3, 0) == 0, "");
-  static_assert(piece.compare(3, 1, "def", 3, 1) == 0, "");
-  static_assert(piece.compare(3, 2, "def", 3, 2) == 0, "");
-  static_assert(piece.compare(3, 3, "def", 3, 3) == 0, "");
-}
-
-TEST(StringPieceTest, Substr) {
-  constexpr StringPiece piece = "abcdefghijklmnopqrstuvwxyz";
-
-  static_assert(piece.substr(0, 2) == "ab", "");
-  static_assert(piece.substr(0, 3) == "abc", "");
-  static_assert(piece.substr(0, 4) == "abcd", "");
-  static_assert(piece.substr(3, 2) == "de", "");
-  static_assert(piece.substr(3, 3) == "def", "");
-  static_assert(piece.substr(23) == "xyz", "");
-  static_assert(piece.substr(23, 3) == "xyz", "");
-  static_assert(piece.substr(23, 99) == "xyz", "");
-  static_assert(piece.substr() == piece, "");
-  static_assert(piece.substr(0) == piece, "");
-  static_assert(piece.substr(0, 99) == piece, "");
-}
-
-TEST(StringPieceTest, Find) {
-  constexpr StringPiece foobar("foobar", 6);
-  constexpr StringPiece foo = foobar.substr(0, 3);
-  constexpr StringPiece bar = foobar.substr(3);
-
-  // find
-  static_assert(foobar.find(bar, 0) == 3, "");
-  static_assert(foobar.find('o', 0) == 1, "");
-  static_assert(foobar.find("ox", 0, 1) == 1, "");
-  static_assert(foobar.find("ox", 0) == StringPiece::npos, "");
-
-  // rfind
-  static_assert(foobar.rfind(bar, 5) == 3, "");
-  static_assert(foobar.rfind('o', 5) == 2, "");
-  static_assert(foobar.rfind("ox", 5, 1) == 2, "");
-  static_assert(foobar.rfind("ox", 5) == StringPiece::npos, "");
-
-  // find_first_of
-  static_assert(foobar.find_first_of(foo, 2) == 2, "");
-  static_assert(foobar.find_first_of('o', 2) == 2, "");
-  static_assert(foobar.find_first_of("ox", 2, 2) == 2, "");
-  static_assert(foobar.find_first_of("ox", 2) == 2, "");
-
-  // find_last_of
-  static_assert(foobar.find_last_of(foo, 5) == 2, "");
-  static_assert(foobar.find_last_of('o', 5) == 2, "");
-  static_assert(foobar.find_last_of("ox", 5, 2) == 2, "");
-  static_assert(foobar.find_last_of("ox", 5) == 2, "");
-
-  // find_first_not_of
-  static_assert(foobar.find_first_not_of(foo, 2) == 3, "");
-  static_assert(foobar.find_first_not_of('o', 2) == 3, "");
-  static_assert(foobar.find_first_not_of("ox", 2, 2) == 3, "");
-  static_assert(foobar.find_first_not_of("ox", 2) == 3, "");
-
-  // find_last_not_of
-  static_assert(foobar.find_last_not_of(bar, 5) == 2, "");
-  static_assert(foobar.find_last_not_of('a', 4) == 3, "");
-  static_assert(foobar.find_last_not_of("ox", 2, 2) == 0, "");
-  static_assert(foobar.find_last_not_of("ox", 2) == 0, "");
-}
-
-// Test that `gurl_base::StringPiece` and `std::string_view` are interoperable.
-TEST(StringPieceTest, StringPieceToStringView) {
-  constexpr StringPiece kPiece = "foo";
-  constexpr std::string_view kView = kPiece;
-  static_assert(kPiece.data() == kView.data());
-  static_assert(kPiece.size() == kView.size());
-}
-
-TEST(StringPieceTest, StringViewToStringPiece) {
-  constexpr std::string_view kView = "bar";
-  constexpr StringPiece kPiece = kView;
-  static_assert(kView.data() == kPiece.data());
-  static_assert(kView.size() == kPiece.size());
-}
-
-}  // namespace base
diff --git a/base/strings/string_slice.h b/base/strings/string_slice.h
new file mode 100644
index 0000000..6c1989e
--- /dev/null
+++ b/base/strings/string_slice.h
@@ -0,0 +1,100 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRING_SLICE_H_
+#define BASE_STRINGS_STRING_SLICE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <bit>
+#include <string_view>
+
+namespace gurl_base::internal {
+
+// Determines the minimum unsigned integer type needed to hold `kSize`.
+template <size_t kSize>
+struct IndexTypeForSize {
+ private:
+  static constexpr auto Helper() {
+    constexpr int kMinBits = std::bit_width(kSize);
+    if constexpr (kMinBits <= 8) {
+      return uint8_t();
+    } else if constexpr (kMinBits <= 16) {
+      return uint16_t();
+    } else if constexpr (kMinBits <= 32) {
+      return uint32_t();
+    } else {
+      return size_t();
+    }
+  }
+
+ public:
+  using Type = decltype(Helper());
+};
+
+}  // namespace gurl_base::internal
+
+namespace gurl_base::subtle {
+
+// This is intended for use in tables generated by scripts and should not be
+// directly used. Consider a global constant like this:
+//
+// constexpr std::string_view kNames[] = {
+//   "Alice",
+//   "Bob",
+//   "Eve",
+// };
+//
+// "Alice", "Bob", and "Eve" are also constants, but they are not stored inline
+// in `kNames`; `kNames[0]` points to "Alice", `kNames[1]` points to "Bob", and
+// `kNames[2]` points to "Eve". However, images can be loaded at arbitrary base
+// addresses, so the actual pointer values are unknown at build time.
+//
+// To solve this, the tooling stores `kNames` in `.data.rel.ro` and records 3
+// relocations in `.rela.dyn`. When the image is loaded, the linker applies the
+// relocations to fix up the addresses before marking the section read-only.
+//
+// Unfortunately, this has both a binary size cost (relocation entries are
+// relatively large unless RELR is in use) and a runtime cost (to apply the
+// relocations).
+//
+// StringSlice avoids relocations by only storing an offset and a length and
+// dynamically resolving to a std::string_view at runtime. Using `StringSlice`,
+// the above example might look like this instead:
+//
+// constexpr char kData[] = "AliceBobEve";
+// constexpr StringSlice<sizeof(kData), kData> kNames[] = {
+//   {0, 5},
+//   {5, 3},
+//   {8, 3},
+// };
+//
+// While this has a small runtime cost (typically a PC-relative load), modern
+// CPUs are quite good at this sort of math.
+template <size_t N, const char (&kData)[N]>
+struct StringSlice {
+  using IndexType = typename internal::IndexTypeForSize<N>::Type;
+
+  IndexType offset;
+  IndexType length;
+
+  friend constexpr bool operator==(StringSlice lhs, StringSlice rhs) {
+    return std::string_view(lhs) == std::string_view(rhs);
+  }
+  friend constexpr auto operator<=>(StringSlice lhs, StringSlice rhs) {
+    return std::string_view(lhs) <=> std::string_view(rhs);
+  }
+  constexpr operator std::string_view() const {
+    // Note 1: using as_string_view() or span() can cause issues with constexpr
+    // evaluation limits.
+    // Note 2: Subtract 1 from kData since this is intended for use with string
+    // literals, and the terminating nul should not be included.
+    return std::string_view(kData, sizeof(kData) - 1).substr(offset, length);
+  }
+};
+
+}  // namespace gurl_base::subtle
+
+#endif  // BASE_STRINGS_STRING_SLICE_H_
diff --git a/base/strings/string_slice_unittest.cc b/base/strings/string_slice_unittest.cc
new file mode 100644
index 0000000..72ca44d
--- /dev/null
+++ b/base/strings/string_slice_unittest.cc
@@ -0,0 +1,47 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_slice.h"
+
+#include <stdint.h>
+
+#include <concepts>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gurl_base {
+
+namespace {
+
+using subtle::StringSlice;
+
+TEST(StringSliceTest, IndexType) {
+  {
+    static char kData[255] = {};
+    using Slice = StringSlice<sizeof(kData), kData>;
+    static_assert(std::same_as<Slice::IndexType, uint8_t>);
+  }
+
+  {
+    static char kData[256] = {};
+    using Slice = StringSlice<sizeof(kData), kData>;
+    static_assert(std::same_as<Slice::IndexType, uint16_t>);
+  }
+
+  {
+    static char kData[65535] = {};
+    using Slice = StringSlice<sizeof(kData), kData>;
+    static_assert(std::same_as<Slice::IndexType, uint16_t>);
+  }
+
+  {
+    static char kData[65536] = {};
+    using Slice = StringSlice<sizeof(kData), kData>;
+    static_assert(std::same_as<Slice::IndexType, uint32_t>);
+  }
+}
+
+}  // namespace
+
+}  // namespace base
diff --git a/base/strings/string_split.cc b/base/strings/string_split.cc
index 05d4101..615dd88 100644
--- a/base/strings/string_split.cc
+++ b/base/strings/string_split.cc
@@ -6,6 +6,8 @@
 
 #include <stddef.h>
 
+#include <string_view>
+
 #include "polyfills/base/logging.h"
 #include "base/strings/string_split_internal.h"
 #include "base/strings/string_util.h"
@@ -15,9 +17,20 @@
 
 namespace {
 
-bool AppendStringKeyValue(StringPiece input,
-                          char delimiter,
-                          StringPairs* result) {
+// Helper for the various *SplitStringOnce implementations. When returning a
+// pair of `std::string_view`, does not include the character at `position`.
+std::optional<std::pair<std::string_view, std::string_view>>
+SplitStringAtExclusive(std::string_view input, size_t position) {
+  if (position == std::string_view::npos) {
+    return std::nullopt;
+  }
+
+  return std::pair(input.substr(0, position), input.substr(position + 1));
+}
+
+bool AppendStringViewKeyValue(std::string_view input,
+                              char delimiter,
+                              StringViewPairs* result) {
   // Always append a new item regardless of success (it might be empty). The
   // below code will copy the strings directly into the result pair.
   result->resize(result->size() + 1);
@@ -27,81 +40,137 @@
   size_t end_key_pos = input.find_first_of(delimiter);
   if (end_key_pos == std::string::npos) {
     DVLOG(1) << "cannot find delimiter in: " << input;
-    return false;    // No delimiter.
+    return false;  // No delimiter.
   }
-  result_pair.first = std::string(input.substr(0, end_key_pos));
+  result_pair.first = input.substr(0, end_key_pos);
 
   // Find the value string.
-  StringPiece remains = input.substr(end_key_pos, input.size() - end_key_pos);
+  std::string_view remains =
+      input.substr(end_key_pos, input.size() - end_key_pos);
   size_t begin_value_pos = remains.find_first_not_of(delimiter);
-  if (begin_value_pos == StringPiece::npos) {
+  if (begin_value_pos == std::string_view::npos) {
     DVLOG(1) << "cannot parse value from input: " << input;
-    return false;   // No value.
+    return false;  // No value.
   }
 
-  result_pair.second = std::string(
-      remains.substr(begin_value_pos, remains.size() - begin_value_pos));
+  result_pair.second =
+      remains.substr(begin_value_pos, remains.size() - begin_value_pos);
 
   return true;
 }
 
 }  // namespace
 
-std::vector<std::string> SplitString(StringPiece input,
-                                     StringPiece separators,
+std::optional<std::pair<std::string_view, std::string_view>> SplitStringOnce(
+    std::string_view input,
+    char separator) {
+  return SplitStringAtExclusive(input, input.find(separator));
+}
+
+std::optional<std::pair<std::string_view, std::string_view>> SplitStringOnce(
+    std::string_view input,
+    std::string_view separators) {
+  return SplitStringAtExclusive(input, input.find_first_of(separators));
+}
+
+std::optional<std::pair<std::string_view, std::string_view>> RSplitStringOnce(
+    std::string_view input,
+    char separator) {
+  return SplitStringAtExclusive(input, input.rfind(separator));
+}
+
+std::optional<std::pair<std::string_view, std::string_view>> RSplitStringOnce(
+    std::string_view input,
+    std::string_view separators) {
+  return SplitStringAtExclusive(input, input.find_last_of(separators));
+}
+
+std::vector<std::string> SplitString(std::string_view input,
+                                     std::string_view separators,
                                      WhitespaceHandling whitespace,
                                      SplitResult result_type) {
   return internal::SplitStringT<std::string>(input, separators, whitespace,
                                              result_type);
 }
 
-std::vector<std::u16string> SplitString(StringPiece16 input,
-                                        StringPiece16 separators,
+std::vector<std::u16string> SplitString(std::u16string_view input,
+                                        std::u16string_view separators,
                                         WhitespaceHandling whitespace,
                                         SplitResult result_type) {
   return internal::SplitStringT<std::u16string>(input, separators, whitespace,
                                                 result_type);
 }
 
-std::vector<StringPiece> SplitStringPiece(StringPiece input,
-                                          StringPiece separators,
-                                          WhitespaceHandling whitespace,
-                                          SplitResult result_type) {
-  return internal::SplitStringT<StringPiece>(input, separators, whitespace,
-                                             result_type);
+std::vector<std::string_view> SplitStringPiece(std::string_view input,
+                                               std::string_view separators,
+                                               WhitespaceHandling whitespace,
+                                               SplitResult result_type) {
+  return internal::SplitStringT<std::string_view>(input, separators, whitespace,
+                                                  result_type);
 }
 
-std::vector<StringPiece16> SplitStringPiece(StringPiece16 input,
-                                            StringPiece16 separators,
-                                            WhitespaceHandling whitespace,
-                                            SplitResult result_type) {
-  return internal::SplitStringT<StringPiece16>(input, separators, whitespace,
-                                               result_type);
+std::vector<std::u16string_view> SplitStringPiece(
+    std::u16string_view input,
+    std::u16string_view separators,
+    WhitespaceHandling whitespace,
+    SplitResult result_type) {
+  return internal::SplitStringT<std::u16string_view>(input, separators,
+                                                     whitespace, result_type);
 }
 
-bool SplitStringIntoKeyValuePairs(StringPiece input,
+bool SplitStringIntoKeyValuePairs(std::string_view input,
                                   char key_value_delimiter,
                                   char key_value_pair_delimiter,
                                   StringPairs* key_value_pairs) {
   return SplitStringIntoKeyValuePairsUsingSubstr(
-      input, key_value_delimiter, StringPiece(&key_value_pair_delimiter, 1),
-      key_value_pairs);
+      input, key_value_delimiter,
+      std::string_view(&key_value_pair_delimiter, 1), key_value_pairs);
+}
+
+bool SplitStringIntoKeyValueViewPairs(std::string_view input,
+                                      char key_value_delimiter,
+                                      char key_value_pair_delimiter,
+                                      StringViewPairs* key_value_pairs) {
+  return SplitStringIntoKeyValueViewPairsUsingSubstr(
+      input, key_value_delimiter,
+      std::string_view(&key_value_pair_delimiter, 1), key_value_pairs);
 }
 
 bool SplitStringIntoKeyValuePairsUsingSubstr(
-    StringPiece input,
+    std::string_view input,
     char key_value_delimiter,
-    StringPiece key_value_pair_delimiter,
+    std::string_view key_value_pair_delimiter,
     StringPairs* key_value_pairs) {
+  StringViewPairs key_value_view_pairs;
+  bool success = SplitStringIntoKeyValueViewPairsUsingSubstr(
+      input, key_value_delimiter, key_value_pair_delimiter,
+      &key_value_view_pairs);
+
+  // Copy key_value_view_pairs regardless of success to allow for pairs without
+  // associated value or key.
+  key_value_pairs->clear();
+  key_value_pairs->reserve(key_value_view_pairs.size());
+  for (const auto& [key, value] : key_value_view_pairs) {
+    key_value_pairs->emplace_back(std::string(key), std::string(value));
+  }
+
+  return success;
+}
+
+bool SplitStringIntoKeyValueViewPairsUsingSubstr(
+    std::string_view input,
+    char key_value_delimiter,
+    std::string_view key_value_pair_delimiter,
+    StringViewPairs* key_value_pairs) {
   key_value_pairs->clear();
 
-  std::vector<StringPiece> pairs = SplitStringPieceUsingSubstr(
+  std::vector<std::string_view> pairs = SplitStringPieceUsingSubstr(
       input, key_value_pair_delimiter, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
   key_value_pairs->reserve(pairs.size());
 
   bool success = true;
-  for (const StringPiece& pair : pairs) {
-    if (!AppendStringKeyValue(pair, key_value_delimiter, key_value_pairs)) {
+  for (std::string_view pair : pairs) {
+    if (!AppendStringViewKeyValue(pair, key_value_delimiter, key_value_pairs)) {
       // Don't return here, to allow for pairs without associated
       // value or key; just record that the split failed.
       success = false;
@@ -111,38 +180,38 @@
 }
 
 std::vector<std::u16string> SplitStringUsingSubstr(
-    StringPiece16 input,
-    StringPiece16 delimiter,
+    std::u16string_view input,
+    std::u16string_view delimiter,
     WhitespaceHandling whitespace,
     SplitResult result_type) {
   return internal::SplitStringUsingSubstrT<std::u16string>(
       input, delimiter, whitespace, result_type);
 }
 
-std::vector<std::string> SplitStringUsingSubstr(StringPiece input,
-                                                StringPiece delimiter,
+std::vector<std::string> SplitStringUsingSubstr(std::string_view input,
+                                                std::string_view delimiter,
                                                 WhitespaceHandling whitespace,
                                                 SplitResult result_type) {
   return internal::SplitStringUsingSubstrT<std::string>(
       input, delimiter, whitespace, result_type);
 }
 
-std::vector<StringPiece16> SplitStringPieceUsingSubstr(
-    StringPiece16 input,
-    StringPiece16 delimiter,
+std::vector<std::u16string_view> SplitStringPieceUsingSubstr(
+    std::u16string_view input,
+    std::u16string_view delimiter,
     WhitespaceHandling whitespace,
     SplitResult result_type) {
-  std::vector<StringPiece16> result;
-  return internal::SplitStringUsingSubstrT<StringPiece16>(
+  std::vector<std::u16string_view> result;
+  return internal::SplitStringUsingSubstrT<std::u16string_view>(
       input, delimiter, whitespace, result_type);
 }
 
-std::vector<StringPiece> SplitStringPieceUsingSubstr(
-    StringPiece input,
-    StringPiece delimiter,
+std::vector<std::string_view> SplitStringPieceUsingSubstr(
+    std::string_view input,
+    std::string_view delimiter,
     WhitespaceHandling whitespace,
     SplitResult result_type) {
-  return internal::SplitStringUsingSubstrT<StringPiece>(
+  return internal::SplitStringUsingSubstrT<std::string_view>(
       input, delimiter, whitespace, result_type);
 }
 
diff --git a/base/strings/string_split.h b/base/strings/string_split.h
index ca16457..fb20dfa 100644
--- a/base/strings/string_split.h
+++ b/base/strings/string_split.h
@@ -5,16 +5,44 @@
 #ifndef BASE_STRINGS_STRING_SPLIT_H_
 #define BASE_STRINGS_STRING_SPLIT_H_
 
+#include <optional>
 #include <string>
+#include <string_view>
 #include <utility>
 #include <vector>
 
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
+#include "base/compiler_specific.h"
 #include "build/build_config.h"
 
 namespace gurl_base {
 
+// Splits a string at the first instance of `separator`, returning a pair of
+// `std::string_view`: `first` is the (potentially empty) part that comes before
+// the separator, and `second` is the (potentially empty) part that comes after.
+// If `separator` is not in `input`, returns `std::nullopt`.
+BASE_EXPORT std::optional<std::pair<std::string_view, std::string_view>>
+SplitStringOnce(std::string_view input LIFETIME_BOUND, char separator);
+
+// Similar to the above, but splits the string at the first instance of any
+// separator in `separators`.
+BASE_EXPORT std::optional<std::pair<std::string_view, std::string_view>>
+SplitStringOnce(std::string_view input LIFETIME_BOUND,
+                std::string_view separators);
+
+// Splits a string at the last instance of `separator`, returning a pair of
+// `std::string_view`: `first` is the (potentially empty) part that comes before
+// the separator, and `second` is the (potentially empty) part that comes after.
+// If `separator` is not in `input`, returns `std::nullopt`.
+BASE_EXPORT std::optional<std::pair<std::string_view, std::string_view>>
+RSplitStringOnce(std::string_view input LIFETIME_BOUND, char separator);
+
+// Similar to the above, but splits the string at the last instance of any
+// separator in `separators`.
+BASE_EXPORT std::optional<std::pair<std::string_view, std::string_view>>
+RSplitStringOnce(std::string_view input LIFETIME_BOUND,
+                 std::string_view separators);
+
 enum WhitespaceHandling {
   KEEP_WHITESPACE,
   TRIM_WHITESPACE,
@@ -46,17 +74,17 @@
 //   std::vector<std::string> tokens = gurl_base::SplitString(
 //       input, ",;", gurl_base::KEEP_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
 [[nodiscard]] BASE_EXPORT std::vector<std::string> SplitString(
-    StringPiece input,
-    StringPiece separators,
+    std::string_view input,
+    std::string_view separators,
     WhitespaceHandling whitespace,
     SplitResult result_type);
 [[nodiscard]] BASE_EXPORT std::vector<std::u16string> SplitString(
-    StringPiece16 input,
-    StringPiece16 separators,
+    std::u16string_view input,
+    std::u16string_view separators,
     WhitespaceHandling whitespace,
     SplitResult result_type);
 
-// Like SplitString above except it returns a vector of StringPieces which
+// Like SplitString above except it returns a vector of std::string_views which
 // reference the original buffer without copying. Although you have to be
 // careful to keep the original string unmodified, this provides an efficient
 // way to iterate through tokens in a string.
@@ -70,46 +98,64 @@
 //                               gurl_base::KEEP_WHITESPACE,
 //                               gurl_base::SPLIT_WANT_NONEMPTY)) {
 //     ...
-[[nodiscard]] BASE_EXPORT std::vector<StringPiece> SplitStringPiece(
-    StringPiece input,
-    StringPiece separators,
+[[nodiscard]] BASE_EXPORT std::vector<std::string_view> SplitStringPiece(
+    std::string_view input LIFETIME_BOUND,
+    std::string_view separators,
     WhitespaceHandling whitespace,
     SplitResult result_type);
-[[nodiscard]] BASE_EXPORT std::vector<StringPiece16> SplitStringPiece(
-    StringPiece16 input,
-    StringPiece16 separators,
+[[nodiscard]] BASE_EXPORT std::vector<std::u16string_view> SplitStringPiece(
+    std::u16string_view input LIFETIME_BOUND,
+    std::u16string_view separators,
     WhitespaceHandling whitespace,
     SplitResult result_type);
 
 using StringPairs = std::vector<std::pair<std::string, std::string>>;
+using StringViewPairs =
+    std::vector<std::pair<std::string_view, std::string_view>>;
 
 // Splits |line| into key value pairs according to the given delimiters and
 // removes whitespace leading each key and trailing each value. Returns true
 // only if each pair has a non-empty key and value. |key_value_pairs| will
 // include ("","") pairs for entries without |key_value_delimiter|.
-BASE_EXPORT bool SplitStringIntoKeyValuePairs(StringPiece input,
+BASE_EXPORT bool SplitStringIntoKeyValuePairs(std::string_view input,
                                               char key_value_delimiter,
                                               char key_value_pair_delimiter,
                                               StringPairs* key_value_pairs);
 
+// Like SplitStringIntoKeyValuePairs above except it uses a vector of
+// std::string_views which reference the original buffer without copying.
+BASE_EXPORT bool SplitStringIntoKeyValueViewPairs(
+    std::string_view input,
+    char key_value_delimiter,
+    char key_value_pair_delimiter,
+    StringViewPairs* key_value_pairs);
+
 // Similar to SplitStringIntoKeyValuePairs, but use a substring
 // |key_value_pair_delimiter| instead of a single char.
 BASE_EXPORT bool SplitStringIntoKeyValuePairsUsingSubstr(
-    StringPiece input,
+    std::string_view input,
     char key_value_delimiter,
-    StringPiece key_value_pair_delimiter,
+    std::string_view key_value_pair_delimiter,
     StringPairs* key_value_pairs);
 
+// Like SplitStringIntoKeyValuePairsUsingSubstr above except it uses a vector of
+// std::string_views which reference the original buffer without copying.
+BASE_EXPORT bool SplitStringIntoKeyValueViewPairsUsingSubstr(
+    std::string_view input,
+    char key_value_delimiter,
+    std::string_view key_value_pair_delimiter,
+    StringViewPairs* key_value_pairs);
+
 // Similar to SplitString, but use a substring delimiter instead of a list of
 // characters that are all possible delimiters.
 [[nodiscard]] BASE_EXPORT std::vector<std::u16string> SplitStringUsingSubstr(
-    StringPiece16 input,
-    StringPiece16 delimiter,
+    std::u16string_view input,
+    std::u16string_view delimiter,
     WhitespaceHandling whitespace,
     SplitResult result_type);
 [[nodiscard]] BASE_EXPORT std::vector<std::string> SplitStringUsingSubstr(
-    StringPiece input,
-    StringPiece delimiter,
+    std::string_view input,
+    std::string_view delimiter,
     WhitespaceHandling whitespace,
     SplitResult result_type);
 
@@ -125,16 +171,16 @@
 //                                     gurl_base::KEEP_WHITESPACE,
 //                                     gurl_base::SPLIT_WANT_NONEMPTY)) {
 //     ...
-[[nodiscard]] BASE_EXPORT std::vector<StringPiece16>
-SplitStringPieceUsingSubstr(StringPiece16 input,
-                            StringPiece16 delimiter,
+[[nodiscard]] BASE_EXPORT std::vector<std::u16string_view>
+SplitStringPieceUsingSubstr(std::u16string_view input LIFETIME_BOUND,
+                            std::u16string_view delimiter,
                             WhitespaceHandling whitespace,
                             SplitResult result_type);
-[[nodiscard]] BASE_EXPORT std::vector<StringPiece> SplitStringPieceUsingSubstr(
-    StringPiece input,
-    StringPiece delimiter,
-    WhitespaceHandling whitespace,
-    SplitResult result_type);
+[[nodiscard]] BASE_EXPORT std::vector<std::string_view>
+SplitStringPieceUsingSubstr(std::string_view input LIFETIME_BOUND,
+                            std::string_view delimiter,
+                            WhitespaceHandling whitespace,
+                            SplitResult result_type);
 
 }  // namespace base
 
diff --git a/base/strings/string_split_internal.h b/base/strings/string_split_internal.h
index 8228bd6..43ea85d 100644
--- a/base/strings/string_split_internal.h
+++ b/base/strings/string_split_internal.h
@@ -5,9 +5,9 @@
 #ifndef BASE_STRINGS_STRING_SPLIT_INTERNAL_H_
 #define BASE_STRINGS_STRING_SPLIT_INTERNAL_H_
 
+#include <string_view>
 #include <vector>
 
-#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 
 namespace gurl_base {
@@ -16,19 +16,19 @@
 
 // Returns either the ASCII or UTF-16 whitespace.
 template <typename CharT>
-BasicStringPiece<CharT> WhitespaceForType();
+std::basic_string_view<CharT> WhitespaceForType();
 
 template <>
-inline StringPiece16 WhitespaceForType<char16_t>() {
+inline std::u16string_view WhitespaceForType<char16_t>() {
   return kWhitespaceUTF16;
 }
 template <>
-inline StringPiece WhitespaceForType<char>() {
+inline std::string_view WhitespaceForType<char>() {
   return kWhitespaceASCII;
 }
 
 // General string splitter template. Can take 8- or 16-bit input, can produce
-// the corresponding string or StringPiece output.
+// the corresponding string or std::string_view output.
 template <typename OutputStringType,
           typename T,
           typename CharT = typename T::value_type>
@@ -37,14 +37,15 @@
                                                   WhitespaceHandling whitespace,
                                                   SplitResult result_type) {
   std::vector<OutputStringType> result;
-  if (str.empty())
+  if (str.empty()) {
     return result;
+  }
 
   size_t start = 0;
   while (start != std::basic_string<CharT>::npos) {
     size_t end = str.find_first_of(delimiter, start);
 
-    BasicStringPiece<CharT> piece;
+    std::basic_string_view<CharT> piece;
     if (end == std::basic_string<CharT>::npos) {
       piece = str.substr(start);
       start = std::basic_string<CharT>::npos;
@@ -53,11 +54,13 @@
       start = end + 1;
     }
 
-    if (whitespace == TRIM_WHITESPACE)
+    if (whitespace == TRIM_WHITESPACE) {
       piece = TrimString(piece, WhitespaceForType<CharT>(), TRIM_ALL);
+    }
 
-    if (result_type == SPLIT_WANT_ALL || !piece.empty())
+    if (result_type == SPLIT_WANT_ALL || !piece.empty()) {
       result.emplace_back(piece);
+    }
   }
   return result;
 }
@@ -70,7 +73,7 @@
     T delimiter,
     WhitespaceHandling whitespace,
     SplitResult result_type) {
-  using Piece = BasicStringPiece<CharT>;
+  using Piece = std::basic_string_view<CharT>;
   using size_type = typename Piece::size_type;
 
   std::vector<OutputStringType> result;
@@ -86,11 +89,13 @@
                      ? input.substr(begin_index)
                      : input.substr(begin_index, end_index - begin_index);
 
-    if (whitespace == TRIM_WHITESPACE)
+    if (whitespace == TRIM_WHITESPACE) {
       term = TrimString(term, WhitespaceForType<CharT>(), TRIM_ALL);
+    }
 
-    if (result_type == SPLIT_WANT_ALL || !term.empty())
+    if (result_type == SPLIT_WANT_ALL || !term.empty()) {
       result.emplace_back(term);
+    }
   }
 
   return result;
diff --git a/base/strings/string_split_unittest.cc b/base/strings/string_split_unittest.cc
index dc2bc3a..085db20 100644
--- a/base/strings/string_split_unittest.cc
+++ b/base/strings/string_split_unittest.cc
@@ -6,38 +6,99 @@
 
 #include <stddef.h>
 
+#include <string>
+#include <string_view>
+#include <vector>
+
 #include "base/strings/string_util.h"
+#include "base/strings/string_view_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::ElementsAre;
+using ::testing::Optional;
+using ::testing::Pair;
 
 namespace gurl_base {
 
-class SplitStringIntoKeyValuePairsTest : public testing::Test {
+template <typename StringPairsType>
+class SplitStringIntoKeyValuePairsTest;
+
+template <>
+class SplitStringIntoKeyValuePairsTest<StringPairs> : public ::testing::Test {
  protected:
-  gurl_base::StringPairs kv_pairs;
+  static std::string EmptyString() { return std::string(); }
+
+  static bool SplitStringIntoKeyValuePairs(std::string_view input,
+                                           char key_value_delimiter,
+                                           char key_value_pair_delimiter,
+                                           StringPairs* key_value_pairs) {
+    return gurl_base::SplitStringIntoKeyValuePairs(
+        input, key_value_delimiter, key_value_pair_delimiter, key_value_pairs);
+  }
+
+  static bool SplitStringIntoKeyValuePairsUsingSubstr(
+      std::string_view input,
+      char key_value_delimiter,
+      std::string_view key_value_pair_delimiter,
+      StringPairs* key_value_pairs) {
+    return gurl_base::SplitStringIntoKeyValuePairsUsingSubstr(
+        input, key_value_delimiter, key_value_pair_delimiter, key_value_pairs);
+  }
 };
 
-using SplitStringIntoKeyValuePairsUsingSubstrTest =
-    SplitStringIntoKeyValuePairsTest;
+template <>
+class SplitStringIntoKeyValuePairsTest<StringViewPairs>
+    : public ::testing::Test {
+ protected:
+  static std::string_view EmptyString() { return std::string_view(); }
 
-TEST_F(SplitStringIntoKeyValuePairsUsingSubstrTest, EmptyString) {
-  EXPECT_TRUE(
-      SplitStringIntoKeyValuePairsUsingSubstr(std::string(),
-                                              ':',  // Key-value delimiter
-                                              ",",  // Key-value pair delimiter
-                                              &kv_pairs));
+  static bool SplitStringIntoKeyValuePairs(std::string_view input,
+                                           char key_value_delimiter,
+                                           char key_value_pair_delimiter,
+                                           StringViewPairs* key_value_pairs) {
+    return SplitStringIntoKeyValueViewPairs(
+        input, key_value_delimiter, key_value_pair_delimiter, key_value_pairs);
+  }
+
+  static bool SplitStringIntoKeyValuePairsUsingSubstr(
+      std::string_view input,
+      char key_value_delimiter,
+      std::string_view key_value_pair_delimiter,
+      StringViewPairs* key_value_pairs) {
+    return SplitStringIntoKeyValueViewPairsUsingSubstr(
+        input, key_value_delimiter, key_value_pair_delimiter, key_value_pairs);
+  }
+};
+
+template <typename StringPairsType>
+using SplitStringIntoKeyValuePairsUsingSubstrTest =
+    SplitStringIntoKeyValuePairsTest<StringPairsType>;
+
+using AllStringPairsTypes = ::testing::Types<StringPairs, StringViewPairs>;
+TYPED_TEST_SUITE(SplitStringIntoKeyValuePairsTest, AllStringPairsTypes);
+TYPED_TEST_SUITE(SplitStringIntoKeyValuePairsUsingSubstrTest,
+                 AllStringPairsTypes);
+
+TYPED_TEST(SplitStringIntoKeyValuePairsUsingSubstrTest, EmptyString) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairsUsingSubstr(
+      TestFixture::EmptyString(),
+      ':',  // Key-value delimiter
+      ",",  // Key-value pair delimiter
+      &kv_pairs));
   EXPECT_TRUE(kv_pairs.empty());
 }
 
-TEST_F(SplitStringIntoKeyValuePairsUsingSubstrTest, MissingKeyValueDelimiter) {
-  EXPECT_FALSE(
-      SplitStringIntoKeyValuePairsUsingSubstr("key1,,key2:value2",
-                                              ':',   // Key-value delimiter
-                                              ",,",  // Key-value pair delimiter
-                                              &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsUsingSubstrTest,
+           MissingKeyValueDelimiter) {
+  TypeParam kv_pairs;
+  EXPECT_FALSE(TestFixture::SplitStringIntoKeyValuePairsUsingSubstr(
+      "key1,,key2:value2",
+      ':',   // Key-value delimiter
+      ",,",  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_TRUE(kv_pairs[0].first.empty());
   EXPECT_TRUE(kv_pairs[0].second.empty());
@@ -45,9 +106,10 @@
   EXPECT_EQ("value2", kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsUsingSubstrTest,
-       MissingKeyValuePairDelimiter) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairsUsingSubstr(
+TYPED_TEST(SplitStringIntoKeyValuePairsUsingSubstrTest,
+           MissingKeyValuePairDelimiter) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairsUsingSubstr(
       "key1:value1,,key3:value3",
       ':',    // Key-value delimiter
       ",,,",  // Key-value pair delimiter
@@ -57,24 +119,28 @@
   EXPECT_EQ("value1,,key3:value3", kv_pairs[0].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsUsingSubstrTest, UntrimmedWhitespace) {
-  EXPECT_TRUE(
-      SplitStringIntoKeyValuePairsUsingSubstr("key1 : value1",
-                                              ':',  // Key-value delimiter
-                                              ",",  // Key-value pair delimiter
-                                              &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsUsingSubstrTest, UntrimmedWhitespace) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairsUsingSubstr(
+      "key1 : value1",
+      ':',  // Key-value delimiter
+      ",",  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(1U, kv_pairs.size());
   EXPECT_EQ("key1 ", kv_pairs[0].first);
   EXPECT_EQ(" value1", kv_pairs[0].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsUsingSubstrTest, OnlySplitAtGivenSeparator) {
+TYPED_TEST(SplitStringIntoKeyValuePairsUsingSubstrTest,
+           OnlySplitAtGivenSeparator) {
+  TypeParam kv_pairs;
   std::string a("a ?!@#$%^&*()_+:/{}\\\t\nb");
-  EXPECT_TRUE(
-      SplitStringIntoKeyValuePairsUsingSubstr(a + "X" + a + "XY" + a + "YX" + a,
-                                              'X',   // Key-value delimiter
-                                              "XY",  // Key-value pair delimiter
-                                              &kv_pairs));
+  std::string b(a + "X" + a + "XY" + a + "YX" + a);
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairsUsingSubstr(
+      b,
+      'X',   // Key-value delimiter
+      "XY",  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_EQ(a, kv_pairs[0].first);
   EXPECT_EQ(a, kv_pairs[0].second);
@@ -82,19 +148,23 @@
   EXPECT_EQ(a, kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, EmptyString) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs(std::string(),
-                                           ':',  // Key-value delimiter
-                                           ',',  // Key-value pair delimiter
-                                           &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, EmptyString) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      TestFixture::EmptyString(),
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   EXPECT_TRUE(kv_pairs.empty());
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, MissingKeyValueDelimiter) {
-  EXPECT_FALSE(SplitStringIntoKeyValuePairs("key1,key2:value2",
-                                            ':',  // Key-value delimiter
-                                            ',',  // Key-value pair delimiter
-                                            &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, MissingKeyValueDelimiter) {
+  TypeParam kv_pairs;
+  EXPECT_FALSE(TestFixture::SplitStringIntoKeyValuePairs(
+      "key1,key2:value2",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_TRUE(kv_pairs[0].first.empty());
   EXPECT_TRUE(kv_pairs[0].second.empty());
@@ -102,11 +172,13 @@
   EXPECT_EQ("value2", kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, EmptyKeyWithKeyValueDelimiter) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs(":value1,key2:value2",
-                                           ':',  // Key-value delimiter
-                                           ',',  // Key-value pair delimiter
-                                           &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, EmptyKeyWithKeyValueDelimiter) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      ":value1,key2:value2",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_TRUE(kv_pairs[0].first.empty());
   EXPECT_EQ("value1", kv_pairs[0].second);
@@ -114,11 +186,13 @@
   EXPECT_EQ("value2", kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, TrailingAndLeadingPairDelimiter) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs(",key1:value1,key2:value2,",
-                                           ':',   // Key-value delimiter
-                                           ',',   // Key-value pair delimiter
-                                           &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, TrailingAndLeadingPairDelimiter) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      ",key1:value1,key2:value2,",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_EQ("key1", kv_pairs[0].first);
   EXPECT_EQ("value1", kv_pairs[0].second);
@@ -126,11 +200,13 @@
   EXPECT_EQ("value2", kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, EmptyPair) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:value1,,key3:value3",
-                                           ':',   // Key-value delimiter
-                                           ',',   // Key-value pair delimiter
-                                           &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, EmptyPair) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      "key1:value1,,key3:value3",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_EQ("key1", kv_pairs[0].first);
   EXPECT_EQ("value1", kv_pairs[0].second);
@@ -138,11 +214,13 @@
   EXPECT_EQ("value3", kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, EmptyValue) {
-  EXPECT_FALSE(SplitStringIntoKeyValuePairs("key1:,key2:value2",
-                                            ':',   // Key-value delimiter
-                                            ',',   // Key-value pair delimiter
-                                            &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, EmptyValue) {
+  TypeParam kv_pairs;
+  EXPECT_FALSE(TestFixture::SplitStringIntoKeyValuePairs(
+      "key1:,key2:value2",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_EQ("key1", kv_pairs[0].first);
   EXPECT_EQ("", kv_pairs[0].second);
@@ -150,21 +228,25 @@
   EXPECT_EQ("value2", kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, UntrimmedWhitespace) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1 : value1",
-                                           ':',  // Key-value delimiter
-                                           ',',  // Key-value pair delimiter
-                                           &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, UntrimmedWhitespace) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      "key1 : value1",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(1U, kv_pairs.size());
   EXPECT_EQ("key1 ", kv_pairs[0].first);
   EXPECT_EQ(" value1", kv_pairs[0].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, TrimmedWhitespace) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:value1 , key2:value2",
-                                           ':',   // Key-value delimiter
-                                           ',',   // Key-value pair delimiter
-                                           &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, TrimmedWhitespace) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      "key1:value1 , key2:value2",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_EQ("key1", kv_pairs[0].first);
   EXPECT_EQ("value1", kv_pairs[0].second);
@@ -172,11 +254,13 @@
   EXPECT_EQ("value2", kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, MultipleKeyValueDelimiters) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:::value1,key2:value2",
-                                           ':',   // Key-value delimiter
-                                           ',',   // Key-value pair delimiter
-                                           &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, MultipleKeyValueDelimiters) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      "key1:::value1,key2:value2",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_EQ("key1", kv_pairs[0].first);
   EXPECT_EQ("value1", kv_pairs[0].second);
@@ -184,12 +268,15 @@
   EXPECT_EQ("value2", kv_pairs[1].second);
 }
 
-TEST_F(SplitStringIntoKeyValuePairsTest, OnlySplitAtGivenSeparator) {
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, OnlySplitAtGivenSeparator) {
+  TypeParam kv_pairs;
   std::string a("a ?!@#$%^&*()_+:/{}\\\t\nb");
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs(a + "X" + a + "Y" + a + "X" + a,
-                                           'X',  // Key-value delimiter
-                                           'Y',  // Key-value pair delimiter
-                                           &kv_pairs));
+  std::string b(a + "X" + a + "Y" + a + "X" + a);
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      b,
+      'X',  // Key-value delimiter
+      'Y',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_EQ(a, kv_pairs[0].first);
   EXPECT_EQ(a, kv_pairs[0].second);
@@ -197,12 +284,13 @@
   EXPECT_EQ(a, kv_pairs[1].second);
 }
 
-
-TEST_F(SplitStringIntoKeyValuePairsTest, DelimiterInValue) {
-  EXPECT_TRUE(SplitStringIntoKeyValuePairs("key1:va:ue1,key2:value2",
-                                           ':',   // Key-value delimiter
-                                           ',',   // Key-value pair delimiter
-                                           &kv_pairs));
+TYPED_TEST(SplitStringIntoKeyValuePairsTest, DelimiterInValue) {
+  TypeParam kv_pairs;
+  EXPECT_TRUE(TestFixture::SplitStringIntoKeyValuePairs(
+      "key1:va:ue1,key2:value2",
+      ':',  // Key-value delimiter
+      ',',  // Key-value pair delimiter
+      &kv_pairs));
   ASSERT_EQ(2U, kv_pairs.size());
   EXPECT_EQ("key1", kv_pairs[0].first);
   EXPECT_EQ("va:ue1", kv_pairs[0].second);
@@ -238,8 +326,9 @@
   // Should split on any of the separators.
   r = SplitString("::,,;;", ",:;", KEEP_WHITESPACE, SPLIT_WANT_ALL);
   ASSERT_EQ(7u, r.size());
-  for (auto str : r)
+  for (const auto& str : r) {
     ASSERT_TRUE(str.empty());
+  }
 
   r = SplitString("red, green; blue:", ",:;", TRIM_WHITESPACE,
                   SPLIT_WANT_NONEMPTY);
@@ -300,16 +389,15 @@
 
 TEST(SplitStringUsingSubstrTest, StringWithNoDelimiter) {
   std::vector<std::string> results = SplitStringUsingSubstr(
-      "alongwordwithnodelimiter", "DELIMITER", TRIM_WHITESPACE,
-      SPLIT_WANT_ALL);
+      "alongwordwithnodelimiter", "DELIMITER", TRIM_WHITESPACE, SPLIT_WANT_ALL);
   ASSERT_EQ(1u, results.size());
   EXPECT_THAT(results, ElementsAre("alongwordwithnodelimiter"));
 }
 
 TEST(SplitStringUsingSubstrTest, LeadingDelimitersSkipped) {
   std::vector<std::string> results = SplitStringUsingSubstr(
-      "DELIMITERDELIMITERDELIMITERoneDELIMITERtwoDELIMITERthree",
-      "DELIMITER", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+      "DELIMITERDELIMITERDELIMITERoneDELIMITERtwoDELIMITERthree", "DELIMITER",
+      TRIM_WHITESPACE, SPLIT_WANT_ALL);
   ASSERT_EQ(6u, results.size());
   EXPECT_THAT(results, ElementsAre("", "", "", "one", "two", "three"));
 }
@@ -327,12 +415,12 @@
       "unDELIMITERdeuxDELIMITERtroisDELIMITERquatreDELIMITERDELIMITERDELIMITER",
       "DELIMITER", TRIM_WHITESPACE, SPLIT_WANT_ALL);
   ASSERT_EQ(7u, results.size());
-  EXPECT_THAT(
-      results, ElementsAre("un", "deux", "trois", "quatre", "", "", ""));
+  EXPECT_THAT(results,
+              ElementsAre("un", "deux", "trois", "quatre", "", "", ""));
 }
 
 TEST(SplitStringPieceUsingSubstrTest, StringWithNoDelimiter) {
-  std::vector<gurl_base::StringPiece> results =
+  std::vector<std::string_view> results =
       SplitStringPieceUsingSubstr("alongwordwithnodelimiter", "DELIMITER",
                                   gurl_base::TRIM_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
   ASSERT_EQ(1u, results.size());
@@ -340,7 +428,7 @@
 }
 
 TEST(SplitStringPieceUsingSubstrTest, LeadingDelimitersSkipped) {
-  std::vector<gurl_base::StringPiece> results = SplitStringPieceUsingSubstr(
+  std::vector<std::string_view> results = SplitStringPieceUsingSubstr(
       "DELIMITERDELIMITERDELIMITERoneDELIMITERtwoDELIMITERthree", "DELIMITER",
       gurl_base::TRIM_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
   ASSERT_EQ(6u, results.size());
@@ -348,7 +436,7 @@
 }
 
 TEST(SplitStringPieceUsingSubstrTest, ConsecutiveDelimitersSkipped) {
-  std::vector<gurl_base::StringPiece> results = SplitStringPieceUsingSubstr(
+  std::vector<std::string_view> results = SplitStringPieceUsingSubstr(
       "unoDELIMITERDELIMITERDELIMITERdosDELIMITERtresDELIMITERDELIMITERcuatro",
       "DELIMITER", gurl_base::TRIM_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
   ASSERT_EQ(7u, results.size());
@@ -356,7 +444,7 @@
 }
 
 TEST(SplitStringPieceUsingSubstrTest, TrailingDelimitersSkipped) {
-  std::vector<gurl_base::StringPiece> results = SplitStringPieceUsingSubstr(
+  std::vector<std::string_view> results = SplitStringPieceUsingSubstr(
       "unDELIMITERdeuxDELIMITERtroisDELIMITERquatreDELIMITERDELIMITERDELIMITER",
       "DELIMITER", gurl_base::TRIM_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
   ASSERT_EQ(7u, results.size());
@@ -365,7 +453,7 @@
 }
 
 TEST(SplitStringPieceUsingSubstrTest, KeepWhitespace) {
-  std::vector<gurl_base::StringPiece> results = SplitStringPieceUsingSubstr(
+  std::vector<std::string_view> results = SplitStringPieceUsingSubstr(
       "un DELIMITERdeux\tDELIMITERtrois\nDELIMITERquatre", "DELIMITER",
       gurl_base::KEEP_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
   ASSERT_EQ(4u, results.size());
@@ -373,7 +461,7 @@
 }
 
 TEST(SplitStringPieceUsingSubstrTest, TrimWhitespace) {
-  std::vector<gurl_base::StringPiece> results = SplitStringPieceUsingSubstr(
+  std::vector<std::string_view> results = SplitStringPieceUsingSubstr(
       "un DELIMITERdeux\tDELIMITERtrois\nDELIMITERquatre", "DELIMITER",
       gurl_base::TRIM_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
   ASSERT_EQ(4u, results.size());
@@ -381,7 +469,7 @@
 }
 
 TEST(SplitStringPieceUsingSubstrTest, SplitWantAll) {
-  std::vector<gurl_base::StringPiece> results = SplitStringPieceUsingSubstr(
+  std::vector<std::string_view> results = SplitStringPieceUsingSubstr(
       "unDELIMITERdeuxDELIMITERtroisDELIMITERDELIMITER", "DELIMITER",
       gurl_base::TRIM_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
   ASSERT_EQ(5u, results.size());
@@ -389,13 +477,81 @@
 }
 
 TEST(SplitStringPieceUsingSubstrTest, SplitWantNonEmpty) {
-  std::vector<gurl_base::StringPiece> results = SplitStringPieceUsingSubstr(
+  std::vector<std::string_view> results = SplitStringPieceUsingSubstr(
       "unDELIMITERdeuxDELIMITERtroisDELIMITERDELIMITER", "DELIMITER",
       gurl_base::TRIM_WHITESPACE, gurl_base::SPLIT_WANT_NONEMPTY);
   ASSERT_EQ(3u, results.size());
   EXPECT_THAT(results, ElementsAre("un", "deux", "trois"));
 }
 
+TEST(StringSplitTest, SplitStringOnce) {
+  // None of the separators are in the input, so should always be std::nullopt.
+  EXPECT_EQ(std::nullopt, SplitStringOnce("", ""));
+  EXPECT_EQ(std::nullopt, SplitStringOnce("a", ""));
+  EXPECT_EQ(std::nullopt, SplitStringOnce("ab", ""));
+
+  EXPECT_THAT(SplitStringOnce("a:b:c", ':'), Optional(Pair("a", "b:c")));
+  EXPECT_THAT(SplitStringOnce("a:", ':'), Optional(Pair("a", "")));
+  EXPECT_THAT(SplitStringOnce(":b", ':'), Optional(Pair("", "b")));
+
+  // Now the same using the multiple separators overload, but with only one
+  // separator specified.
+  EXPECT_THAT(SplitStringOnce("a:b:c", ":"), Optional(Pair("a", "b:c")));
+  EXPECT_THAT(SplitStringOnce("a:", ":"), Optional(Pair("a", "")));
+  EXPECT_THAT(SplitStringOnce(":b", ":"), Optional(Pair("", "b")));
+
+  // Multiple separators overload, but only the first one present.
+  EXPECT_THAT(SplitStringOnce("a:b:c", ":="), Optional(Pair("a", "b:c")));
+  EXPECT_THAT(SplitStringOnce("a:", ":="), Optional(Pair("a", "")));
+  EXPECT_THAT(SplitStringOnce(":b", ":="), Optional(Pair("", "b")));
+
+  // Multiple separators overload, but only the second one present.
+  EXPECT_THAT(SplitStringOnce("a:b:c", "=:"), Optional(Pair("a", "b:c")));
+  EXPECT_THAT(SplitStringOnce("a:", "=:"), Optional(Pair("a", "")));
+  EXPECT_THAT(SplitStringOnce(":b", "=:"), Optional(Pair("", "b")));
+
+  // Multiple separators overload, both present. The separator that comes first
+  // in the input string (not separators string) should win.
+  EXPECT_THAT(SplitStringOnce("a:b=c", ":="), Optional(Pair("a", "b=c")));
+  EXPECT_THAT(SplitStringOnce("a:b=c", "=:"), Optional(Pair("a", "b=c")));
+  EXPECT_THAT(SplitStringOnce("a=b:c", ":="), Optional(Pair("a", "b:c")));
+  EXPECT_THAT(SplitStringOnce("a=b:c", "=:"), Optional(Pair("a", "b:c")));
+}
+
+TEST(StringSplitTest, RSplitStringOnce) {
+  // None of the separators are in the input, so should always be std::nullopt.
+  EXPECT_EQ(std::nullopt, RSplitStringOnce("", ""));
+  EXPECT_EQ(std::nullopt, RSplitStringOnce("a", ""));
+  EXPECT_EQ(std::nullopt, RSplitStringOnce("ab", ""));
+
+  EXPECT_THAT(RSplitStringOnce("a:b:c", ':'), Optional(Pair("a:b", "c")));
+  EXPECT_THAT(RSplitStringOnce("a:", ':'), Optional(Pair("a", "")));
+  EXPECT_THAT(RSplitStringOnce(":b", ':'), Optional(Pair("", "b")));
+
+  // Now the same using the multiple separators overload, but with only one
+  // separator specified.
+  EXPECT_THAT(RSplitStringOnce("a:b:c", ":"), Optional(Pair("a:b", "c")));
+  EXPECT_THAT(RSplitStringOnce("a:", ":"), Optional(Pair("a", "")));
+  EXPECT_THAT(RSplitStringOnce(":b", ":"), Optional(Pair("", "b")));
+
+  // Multiple separators overload, but only the first one present.
+  EXPECT_THAT(RSplitStringOnce("a:b:c", ":="), Optional(Pair("a:b", "c")));
+  EXPECT_THAT(RSplitStringOnce("a:", ":="), Optional(Pair("a", "")));
+  EXPECT_THAT(RSplitStringOnce(":b", ":="), Optional(Pair("", "b")));
+
+  // Multiple separators overload, but only the second one present.
+  EXPECT_THAT(RSplitStringOnce("a:b:c", "=:"), Optional(Pair("a:b", "c")));
+  EXPECT_THAT(RSplitStringOnce("a:", "=:"), Optional(Pair("a", "")));
+  EXPECT_THAT(RSplitStringOnce(":b", "=:"), Optional(Pair("", "b")));
+
+  // Multiple separators overload, both present. The separator that comes first
+  // in the input string (not separators string) should win.
+  EXPECT_THAT(RSplitStringOnce("a:b=c", ":="), Optional(Pair("a:b", "c")));
+  EXPECT_THAT(RSplitStringOnce("a:b=c", "=:"), Optional(Pair("a:b", "c")));
+  EXPECT_THAT(RSplitStringOnce("a=b:c", ":="), Optional(Pair("a=b", "c")));
+  EXPECT_THAT(RSplitStringOnce("a=b:c", "=:"), Optional(Pair("a=b", "c")));
+}
+
 TEST(StringSplitTest, StringSplitKeepWhitespace) {
   std::vector<std::string> r;
 
@@ -425,29 +581,63 @@
     const char* output1;
     const char* output2;
   } data[] = {
-    { "a",       1, "a",  ""   },
-    { " ",       0, "",   ""   },
-    { " a",      1, "a",  ""   },
-    { " ab ",    1, "ab", ""   },
-    { " ab c",   2, "ab", "c"  },
-    { " ab c ",  2, "ab", "c"  },
-    { " ab cd",  2, "ab", "cd" },
-    { " ab cd ", 2, "ab", "cd" },
-    { " \ta\t",  1, "a",  ""   },
-    { " b\ta\t", 2, "b",  "a"  },
-    { " b\tat",  2, "b",  "at" },
-    { "b\tat",   2, "b",  "at" },
-    { "b\t at",  2, "b",  "at" },
+      {"a", 1, "a", ""},         {" ", 0, "", ""},
+      {" a", 1, "a", ""},        {" ab ", 1, "ab", ""},
+      {" ab c", 2, "ab", "c"},   {" ab c ", 2, "ab", "c"},
+      {" ab cd", 2, "ab", "cd"}, {" ab cd ", 2, "ab", "cd"},
+      {" \ta\t", 1, "a", ""},    {" b\ta\t", 2, "b", "a"},
+      {" b\tat", 2, "b", "at"},  {"b\tat", 2, "b", "at"},
+      {"b\t at", 2, "b", "at"},
   };
-  for (const auto& i : data) {
+  for (const TestData& i : data) {
     std::vector<std::string> results =
         gurl_base::SplitString(i.input, kWhitespaceASCII, gurl_base::KEEP_WHITESPACE,
                           gurl_base::SPLIT_WANT_NONEMPTY);
     ASSERT_EQ(i.expected_result_count, results.size());
-    if (i.expected_result_count > 0)
+    if (i.expected_result_count > 0) {
       ASSERT_EQ(i.output1, results[0]);
-    if (i.expected_result_count > 1)
+    }
+    if (i.expected_result_count > 1) {
       ASSERT_EQ(i.output2, results[1]);
+    }
+  }
+}
+
+TEST(StringSplitTest, NullSeparatorWantNonEmpty) {
+  struct TestData {
+    std::string_view test_case;
+    std::vector<std::string_view> expected;
+  } data[] = {
+      {gurl_base::MakeStringViewWithNulChars("a"), {"a"}},
+      {gurl_base::MakeStringViewWithNulChars("a\0"), {"a"}},
+      {gurl_base::MakeStringViewWithNulChars("a\0a"), {"a", "a"}},
+      {gurl_base::MakeStringViewWithNulChars("\0a\0\0a\0"), {"a", "a"}},
+  };
+
+  for (const auto& i : data) {
+    std::vector<std::string_view> results = gurl_base::SplitStringPiece(
+        i.test_case, gurl_base::MakeStringViewWithNulChars("\0"),
+        gurl_base::KEEP_WHITESPACE, gurl_base::SPLIT_WANT_NONEMPTY);
+    EXPECT_EQ(results, i.expected);
+  }
+}
+
+TEST(StringSplitTest, NullSeparatorWantAll) {
+  struct TestData {
+    std::string_view test_case;
+    std::vector<std::string_view> expected;
+  } data[] = {
+      {gurl_base::MakeStringViewWithNulChars("a"), {"a"}},
+      {gurl_base::MakeStringViewWithNulChars("a\0"), {"a", ""}},
+      {gurl_base::MakeStringViewWithNulChars("a\0a"), {"a", "a"}},
+      {gurl_base::MakeStringViewWithNulChars("\0a\0\0a\0"), {"", "a", "", "a", ""}},
+  };
+
+  for (const TestData& i : data) {
+    std::vector<std::string_view> results = gurl_base::SplitStringPiece(
+        i.test_case, gurl_base::MakeStringViewWithNulChars("\0"),
+        gurl_base::KEEP_WHITESPACE, gurl_base::SPLIT_WANT_ALL);
+    EXPECT_EQ(results, i.expected);
   }
 }
 
diff --git a/base/strings/string_split_win.cc b/base/strings/string_split_win.cc
index e103db5..5311d4f 100644
--- a/base/strings/string_split_win.cc
+++ b/base/strings/string_split_win.cc
@@ -8,7 +8,6 @@
 #include <string_view>
 #include <vector>
 
-#include "base/strings/string_piece.h"
 #include "base/strings/string_split_internal.h"
 
 namespace gurl_base {
diff --git a/base/strings/string_split_win.h b/base/strings/string_split_win.h
index 845de38..180eb1c 100644
--- a/base/strings/string_split_win.h
+++ b/base/strings/string_split_win.h
@@ -10,7 +10,7 @@
 #include <vector>
 
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
+#include "base/compiler_specific.h"
 #include "base/strings/string_split.h"
 
 namespace gurl_base {
@@ -24,7 +24,7 @@
     SplitResult result_type);
 
 [[nodiscard]] BASE_EXPORT std::vector<std::wstring_view> SplitStringPiece(
-    std::wstring_view input,
+    std::wstring_view input LIFETIME_BOUND,
     std::wstring_view separators,
     WhitespaceHandling whitespace,
     SplitResult result_type);
@@ -36,7 +36,7 @@
     SplitResult result_type);
 
 [[nodiscard]] BASE_EXPORT std::vector<std::wstring_view>
-SplitStringPieceUsingSubstr(std::wstring_view input,
+SplitStringPieceUsingSubstr(std::wstring_view input LIFETIME_BOUND,
                             std::wstring_view delimiter,
                             WhitespaceHandling whitespace,
                             SplitResult result_type);
diff --git a/base/strings/string_tokenizer.h b/base/strings/string_tokenizer.h
index 62acf43..aea4a02 100644
--- a/base/strings/string_tokenizer.h
+++ b/base/strings/string_tokenizer.h
@@ -6,10 +6,12 @@
 #define BASE_STRINGS_STRING_TOKENIZER_H_
 
 #include <algorithm>
+#include <optional>
 #include <string>
+#include <string_view>
 
 #include "polyfills/base/check.h"
-#include "base/strings/string_piece.h"
+#include "base/compiler_specific.h"
 #include "base/strings/string_util.h"
 
 namespace gurl_base {
@@ -25,12 +27,12 @@
 // EXAMPLE 1:
 //
 //   char input[] = "this is a test";
-//   CStringTokenizer t(input, input + strlen(input), " ");
-//   while (t.GetNext()) {
-//     printf("%s\n", t.token().c_str());
+//   CStringTokenizer t(input, " ");
+//   while (std::optional<std::string_view> token = t.GetNextTokenView()) {
+//     GURL_LOG(ERROR) << token.value();
 //   }
 //
-// Output:
+// Output sans logging metadata:
 //
 //   this
 //   is
@@ -43,11 +45,11 @@
 //   std::string input = "no-cache=\"foo, bar\", private";
 //   StringTokenizer t(input, ", ");
 //   t.set_quote_chars("\"");
-//   while (t.GetNext()) {
-//     printf("%s\n", t.token().c_str());
+//   while (std::optional<std::string_view> token = t.GetNextTokenView()) {
+//     GURL_LOG(ERROR) << token.value();
 //   }
 //
-// Output:
+// Output sans logging metadata:
 //
 //   no-cache="foo, bar"
 //   private
@@ -59,9 +61,9 @@
 //   std::string input = "text/html; charset=UTF-8; foo=bar";
 //   StringTokenizer t(input, "; =");
 //   t.set_options(StringTokenizer::RETURN_DELIMS);
-//   while (t.GetNext()) {
+//   while (std::optional<std::string_view> token = t.GetNextTokenView()) {
 //     if (t.token_is_delim()) {
-//       switch (*t.token_begin()) {
+//       switch (token.value().front()) {
 //         case ';':
 //           next_is_option = true;
 //           break;
@@ -80,7 +82,7 @@
 //       } else {
 //         label = "mime-type";
 //       }
-//       printf("%s: %s\n", label, t.token().c_str());
+//       GURL_LOG(ERROR) << label << " " << token.value();
 //     }
 //   }
 //
@@ -90,11 +92,11 @@
 //   std::string input = "this, \t is, \t a, \t test";
 //   StringTokenizer t(input, ",",
 //       StringTokenizer::WhitespacePolicy::kSkipOver);
-//   while (t.GetNext()) {
-//     printf("%s\n", t.token().c_str());
+//   while (std::optional<std::string_view> token = t.GetNextTokenView()) {
+//     GURL_LOG(ERROR) << token.value();
 //   }
 //
-// Output:
+// Output sans logging metadata:
 //
 //   this
 //   is
@@ -102,10 +104,17 @@
 //   test
 //
 //
+// TODO(danakj): This class is templated on the container and the iterator type,
+// but it strictly only needs to care about the `CharType`. However many users
+// expect to work with string and string::iterator for historical reasons. When
+// they are all working with `string_view`, then this class can be made to
+// unconditionally use `std::basic_string_view<CharType>` and vend iterators of
+// that type, and we can drop the `str` and `const_iterator` aliases.
 template <class str, class const_iterator>
 class StringTokenizerT {
  public:
-  typedef typename str::value_type char_type;
+  using char_type = typename str::value_type;
+  using owning_str = std::basic_string<char_type>;
 
   // Options that may be pass to set_options()
   enum {
@@ -138,7 +147,7 @@
   // the constructor), but caution must still be exercised.
   StringTokenizerT(
       const str& string,
-      const str& delims,
+      const owning_str& delims,
       WhitespacePolicy whitespace_policy = WhitespacePolicy::kIncludeInTokens) {
     Init(string.begin(), string.end(), delims, whitespace_policy);
   }
@@ -150,7 +159,7 @@
   StringTokenizerT(
       const_iterator string_begin,
       const_iterator string_end,
-      const str& delims,
+      const owning_str& delims,
       WhitespacePolicy whitespace_policy = WhitespacePolicy::kIncludeInTokens) {
     Init(string_begin, string_end, delims, whitespace_policy);
   }
@@ -163,22 +172,31 @@
   // it ignores delimiters that it finds.  It switches out of this mode once it
   // finds another instance of the quote char.  If a backslash is encountered
   // within a quoted string, then the next character is skipped.
-  void set_quote_chars(const str& quotes) { quotes_ = quotes; }
+  void set_quote_chars(const owning_str& quotes) { quotes_ = quotes; }
+
+  // Advance the tokenizer to the next delimiter and return the token value. If
+  // the tokenizer is complete, this returns std::nullopt.
+  std::optional<std::basic_string_view<char_type>> GetNextTokenView() {
+    if (!GetNext()) {
+      return std::nullopt;
+    }
+
+    return token_piece();
+  }
 
   // Call this method to advance the tokenizer to the next delimiter.  This
   // returns false if the tokenizer is complete.  This method must be called
   // before calling any of the token* methods.
   bool GetNext() {
-    if (quotes_.empty() && options_ == 0)
+    if (quotes_.empty() && options_ == 0) {
       return QuickGetNext();
-    else
+    } else {
       return FullGetNext();
+    }
   }
 
   // Start iterating through tokens from the beginning of the string.
-  void Reset() {
-    token_end_ = start_pos_;
-  }
+  void Reset() { token_end_ = start_pos_; }
 
   // Returns true if token is a delimiter.  When the tokenizer is constructed
   // with the RETURN_DELIMS option, this method can be used to check if the
@@ -191,14 +209,14 @@
   const_iterator token_begin() const { return token_begin_; }
   const_iterator token_end() const { return token_end_; }
   str token() const { return str(token_begin_, token_end_); }
-  BasicStringPiece<char_type> token_piece() const {
-    return MakeBasicStringPiece<char_type>(token_begin_, token_end_);
+  std::basic_string_view<char_type> token_piece() const {
+    return std::basic_string_view(token_begin_, token_end_);
   }
 
  private:
   void Init(const_iterator string_begin,
             const_iterator string_end,
-            const str& delims,
+            const owning_str& delims,
             WhitespacePolicy whitespace_policy) {
     start_pos_ = string_begin;
     token_begin_ = string_begin;
@@ -218,8 +236,9 @@
   // Skip over any contiguous whitespace characters according to the whitespace
   // policy.
   void SkipWhitespace() {
-    while (token_end_ != end_ && ShouldSkip(*token_end_))
-      ++token_end_;
+    while (token_end_ != end_ && ShouldSkip(*token_end_)) {
+      UNSAFE_TODO(++token_end_);
+    }
   }
 
   // Implementation of GetNext() for when we have no quote characters. We have
@@ -233,7 +252,7 @@
         token_is_delim_ = true;
         return false;
       }
-      ++token_end_;
+      UNSAFE_TODO(++token_end_);
       if (delims_.find(*token_begin_) == str::npos &&
           !ShouldSkip(*token_begin_)) {
         break;
@@ -242,7 +261,7 @@
     }
     while (token_end_ != end_ && delims_.find(*token_end_) == str::npos &&
            !ShouldSkip(*token_end_)) {
-      ++token_end_;
+      UNSAFE_TODO(++token_end_);
     }
     return true;
   }
@@ -270,12 +289,13 @@
 
         // Slurp all non-delimiter characters into the token.
         while (token_end_ != end_ && AdvanceOne(&state, *token_end_)) {
-          ++token_end_;
+          UNSAFE_TODO(++token_end_);
         }
 
         // If it's non-empty, or empty tokens were requested, return the token.
-        if (token_begin_ != token_end_ || (options_ & RETURN_EMPTY_TOKENS))
+        if (token_begin_ != token_end_ || (options_ & RETURN_EMPTY_TOKENS)) {
           return true;
+        }
       }
 
       GURL_DCHECK(!token_is_delim_);
@@ -296,21 +316,27 @@
       token_is_delim_ = true;
       token_begin_ = token_end_;
 
-      if (token_end_ == end_)
+      if (token_end_ == end_) {
         return false;
+      }
 
       // Look at the delimiter.
-      ++token_end_;
-      if (options_ & RETURN_DELIMS)
+      UNSAFE_TODO(++token_end_);
+      if (options_ & RETURN_DELIMS) {
         return true;
+      }
     }
 
     return false;
   }
 
-  bool IsDelim(char_type c) const { return delims_.find(c) != str::npos; }
+  bool IsDelim(char_type c) const {
+    return delims_.find(c) != owning_str::npos;
+  }
 
-  bool IsQuote(char_type c) const { return quotes_.find(c) != str::npos; }
+  bool IsQuote(char_type c) const {
+    return quotes_.find(c) != owning_str::npos;
+  }
 
   struct AdvanceState {
     bool in_quote;
@@ -331,8 +357,9 @@
         state->in_quote = false;
       }
     } else {
-      if (IsDelim(c) || ShouldSkip(c))
+      if (IsDelim(c) || ShouldSkip(c)) {
         return false;
+      }
       state->in_quote = IsQuote(state->quote_char = c);
     }
     return true;
@@ -342,8 +369,8 @@
   const_iterator token_begin_;
   const_iterator token_end_;
   const_iterator end_;
-  str delims_;
-  str quotes_;
+  owning_str delims_;
+  owning_str quotes_;
   int options_;
   bool token_is_delim_;
   WhitespacePolicy whitespace_policy_;
@@ -351,6 +378,8 @@
 
 typedef StringTokenizerT<std::string, std::string::const_iterator>
     StringTokenizer;
+typedef StringTokenizerT<std::string_view, std::string_view::const_iterator>
+    StringViewTokenizer;
 typedef StringTokenizerT<std::u16string, std::u16string::const_iterator>
     String16Tokenizer;
 typedef StringTokenizerT<std::string, const char*> CStringTokenizer;
diff --git a/base/strings/string_tokenizer_unittest.cc b/base/strings/string_tokenizer_unittest.cc
index 9a5e88e..cc529fe 100644
--- a/base/strings/string_tokenizer_unittest.cc
+++ b/base/strings/string_tokenizer_unittest.cc
@@ -4,9 +4,12 @@
 
 #include "base/strings/string_tokenizer.h"
 
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using std::string;
+using testing::Eq;
+using testing::Optional;
 
 namespace gurl_base {
 
@@ -40,6 +43,30 @@
   EXPECT_TRUE(t.token_is_delim());
 }
 
+TEST(StringTokenizerTest, SimpleUsingTokenView) {
+  string input = "this is a test";
+  StringTokenizer t(input, " ");
+  // The start of string, before returning any tokens, is considered a
+  // delimiter.
+  EXPECT_TRUE(t.token_is_delim());
+
+  EXPECT_THAT(t.GetNextTokenView(), Optional(Eq("this")));
+  EXPECT_FALSE(t.token_is_delim());
+
+  EXPECT_THAT(t.GetNextTokenView(), Optional(Eq("is")));
+  EXPECT_FALSE(t.token_is_delim());
+
+  EXPECT_THAT(t.GetNextTokenView(), Optional(Eq("a")));
+  EXPECT_FALSE(t.token_is_delim());
+
+  EXPECT_THAT(t.GetNextTokenView(), Optional(Eq("test")));
+  EXPECT_FALSE(t.token_is_delim());
+
+  EXPECT_THAT(t.GetNextTokenView(), Eq(std::nullopt));
+  // The end of string, after the last token tokens, is considered a delimiter.
+  EXPECT_TRUE(t.token_is_delim());
+}
+
 TEST(StringTokenizerTest, Reset) {
   string input = "this is a test";
   StringTokenizer t(input, " ");
diff --git a/base/strings/string_util.cc b/base/strings/string_util.cc
index cda921f..dda950f 100644
--- a/base/strings/string_util.cc
+++ b/base/strings/string_util.cc
@@ -14,32 +14,35 @@
 #include <time.h>
 #include <wchar.h>
 
+#include <algorithm>
+#include <array>
 #include <limits>
+#include <optional>
 #include <string_view>
 #include <type_traits>
 #include <vector>
 
 #include "polyfills/base/check_op.h"
+#include "base/compiler_specific.h"
 #include "base/no_destructor.h"
-#include "base/ranges/algorithm.h"
 #include "base/strings/string_util_impl_helpers.h"
 #include "base/strings/string_util_internal.h"
 #include "base/strings/utf_string_conversion_utils.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/third_party/icu/icu_utf.h"
 #include "build/build_config.h"
-#include "absl/types/optional.h"
 
 namespace gurl_base {
 
 bool IsWprintfFormatPortable(const wchar_t* format) {
-  for (const wchar_t* position = format; *position != '\0'; ++position) {
+  for (const wchar_t* position = format; *position != '\0';
+       UNSAFE_TODO(++position)) {
     if (*position == '%') {
       bool in_specification = true;
       bool modifier_l = false;
       while (in_specification) {
         // Eat up characters until reaching a known specifier.
-        if (*++position == '\0') {
+        if (UNSAFE_TODO(*++position) == '\0') {
           // The format string ended in the middle of a specification.  Call
           // it portable because no unportable specifications were found.  The
           // string is equally broken on all platforms.
@@ -56,7 +59,7 @@
           return false;
         }
 
-        if (wcschr(L"diouxXeEfgGaAcspn%", *position)) {
+        if (UNSAFE_TODO(wcschr(L"diouxXeEfgGaAcspn%", *position))) {
           // Portable, keep scanning the rest of the format string.
           in_specification = false;
         }
@@ -67,19 +70,19 @@
   return true;
 }
 
-std::string ToLowerASCII(StringPiece str) {
+std::string ToLowerASCII(std::string_view str) {
   return internal::ToLowerASCIIImpl(str);
 }
 
-std::u16string ToLowerASCII(StringPiece16 str) {
+std::u16string ToLowerASCII(std::u16string_view str) {
   return internal::ToLowerASCIIImpl(str);
 }
 
-std::string ToUpperASCII(StringPiece str) {
+std::string ToUpperASCII(std::string_view str) {
   return internal::ToUpperASCIIImpl(str);
 }
 
-std::u16string ToUpperASCII(StringPiece16 str) {
+std::u16string ToUpperASCII(std::u16string_view str) {
   return internal::ToUpperASCIIImpl(str);
 }
 
@@ -93,65 +96,71 @@
   return *s16;
 }
 
-bool ReplaceChars(StringPiece16 input,
-                  StringPiece16 replace_chars,
-                  StringPiece16 replace_with,
+bool ReplaceChars(std::u16string_view input,
+                  std::u16string_view replace_chars,
+                  std::u16string_view replace_with,
                   std::u16string* output) {
   return internal::ReplaceCharsT(input, replace_chars, replace_with, output);
 }
 
-bool ReplaceChars(StringPiece input,
-                  StringPiece replace_chars,
-                  StringPiece replace_with,
+bool ReplaceChars(std::string_view input,
+                  std::string_view replace_chars,
+                  std::string_view replace_with,
                   std::string* output) {
   return internal::ReplaceCharsT(input, replace_chars, replace_with, output);
 }
 
-bool RemoveChars(StringPiece16 input,
-                 StringPiece16 remove_chars,
+bool RemoveChars(std::u16string_view input,
+                 std::u16string_view remove_chars,
                  std::u16string* output) {
-  return internal::ReplaceCharsT(input, remove_chars, StringPiece16(), output);
+  return internal::ReplaceCharsT(input, remove_chars, std::u16string_view(),
+                                 output);
 }
 
-bool RemoveChars(StringPiece input,
-                 StringPiece remove_chars,
+bool RemoveChars(std::string_view input,
+                 std::string_view remove_chars,
                  std::string* output) {
-  return internal::ReplaceCharsT(input, remove_chars, StringPiece(), output);
+  return internal::ReplaceCharsT(input, remove_chars, std::string_view(),
+                                 output);
 }
 
-bool TrimString(StringPiece16 input,
-                StringPiece16 trim_chars,
+bool TrimString(std::u16string_view input,
+                std::u16string_view trim_chars,
                 std::u16string* output) {
   return internal::TrimStringT(input, trim_chars, TRIM_ALL, output) !=
          TRIM_NONE;
 }
 
-bool TrimString(StringPiece input,
-                StringPiece trim_chars,
+bool TrimString(std::string_view input,
+                std::string_view trim_chars,
                 std::string* output) {
   return internal::TrimStringT(input, trim_chars, TRIM_ALL, output) !=
          TRIM_NONE;
 }
 
-StringPiece16 TrimString(StringPiece16 input,
-                         StringPiece16 trim_chars,
-                         TrimPositions positions) {
+std::u16string_view TrimString(std::u16string_view input,
+                               std::u16string_view trim_chars,
+                               TrimPositions positions) {
   return internal::TrimStringPieceT(input, trim_chars, positions);
 }
 
-StringPiece TrimString(StringPiece input,
-                       StringPiece trim_chars,
-                       TrimPositions positions) {
+std::string_view TrimString(std::string_view input,
+                            std::string_view trim_chars,
+                            TrimPositions positions) {
   return internal::TrimStringPieceT(input, trim_chars, positions);
 }
 
-void TruncateUTF8ToByteSize(const std::string& input,
+void TruncateUTF8ToByteSize(std::string_view input,
                             const size_t byte_size,
                             std::string* output) {
   GURL_DCHECK(output);
+  *output = TruncateUTF8ToByteSize(input, byte_size);
+}
+
+std::string_view TruncateUTF8ToByteSize(std::string_view input,
+                                        size_t byte_size) {
   if (byte_size > input.length()) {
-    *output = input;
-    return;
+    return input;
   }
   GURL_DCHECK_LE(byte_size,
             static_cast<uint32_t>(std::numeric_limits<int32_t>::max()));
@@ -167,8 +176,8 @@
   while (char_index >= 0) {
     int32_t prev = char_index;
     base_icu::UChar32 code_point = 0;
-    CBU8_NEXT(reinterpret_cast<const uint8_t*>(data), char_index,
-              truncation_length, code_point);
+    UNSAFE_TODO(CBU8_NEXT(reinterpret_cast<const uint8_t*>(data), char_index,
+                          truncation_length, code_point));
     if (!IsValidCharacter(code_point)) {
       char_index = prev - 1;
     } else {
@@ -176,61 +185,63 @@
     }
   }
 
-  if (char_index >= 0 )
-    *output = input.substr(0, static_cast<size_t>(char_index));
-  else
-    output->clear();
+  if (char_index >= 0) {
+    return input.substr(0, static_cast<size_t>(char_index));
+  } else {
+    return std::string_view();
+  }
 }
 
-TrimPositions TrimWhitespace(StringPiece16 input,
+TrimPositions TrimWhitespace(std::u16string_view input,
                              TrimPositions positions,
                              std::u16string* output) {
-  return internal::TrimStringT(input, StringPiece16(kWhitespaceUTF16),
+  return internal::TrimStringT(input, std::u16string_view(kWhitespaceUTF16),
                                positions, output);
 }
 
-StringPiece16 TrimWhitespace(StringPiece16 input,
-                             TrimPositions positions) {
-  return internal::TrimStringPieceT(input, StringPiece16(kWhitespaceUTF16),
-                                    positions);
+std::u16string_view TrimWhitespace(std::u16string_view input,
+                                   TrimPositions positions) {
+  return internal::TrimStringPieceT(
+      input, std::u16string_view(kWhitespaceUTF16), positions);
 }
 
-TrimPositions TrimWhitespaceASCII(StringPiece input,
+TrimPositions TrimWhitespaceASCII(std::string_view input,
                                   TrimPositions positions,
                                   std::string* output) {
-  return internal::TrimStringT(input, StringPiece(kWhitespaceASCII), positions,
-                               output);
+  return internal::TrimStringT(input, std::string_view(kWhitespaceASCII),
+                               positions, output);
 }
 
-StringPiece TrimWhitespaceASCII(StringPiece input, TrimPositions positions) {
-  return internal::TrimStringPieceT(input, StringPiece(kWhitespaceASCII),
+std::string_view TrimWhitespaceASCII(std::string_view input,
+                                     TrimPositions positions) {
+  return internal::TrimStringPieceT(input, std::string_view(kWhitespaceASCII),
                                     positions);
 }
 
-std::u16string CollapseWhitespace(StringPiece16 text,
+std::u16string CollapseWhitespace(std::u16string_view text,
                                   bool trim_sequences_with_line_breaks) {
   return internal::CollapseWhitespaceT(text, trim_sequences_with_line_breaks);
 }
 
-std::string CollapseWhitespaceASCII(StringPiece text,
+std::string CollapseWhitespaceASCII(std::string_view text,
                                     bool trim_sequences_with_line_breaks) {
   return internal::CollapseWhitespaceT(text, trim_sequences_with_line_breaks);
 }
 
-bool ContainsOnlyChars(StringPiece input, StringPiece characters) {
-  return input.find_first_not_of(characters) == StringPiece::npos;
+bool ContainsOnlyChars(std::string_view input, std::string_view characters) {
+  return input.find_first_not_of(characters) == std::string_view::npos;
 }
 
-bool ContainsOnlyChars(StringPiece16 input, StringPiece16 characters) {
-  return input.find_first_not_of(characters) == StringPiece16::npos;
+bool ContainsOnlyChars(std::u16string_view input,
+                       std::u16string_view characters) {
+  return input.find_first_not_of(characters) == std::u16string_view::npos;
 }
 
-
-bool IsStringASCII(StringPiece str) {
+bool IsStringASCII(std::string_view str) {
   return internal::DoIsStringASCII(str.data(), str.length());
 }
 
-bool IsStringASCII(StringPiece16 str) {
+bool IsStringASCII(std::u16string_view str) {
   return internal::DoIsStringASCII(str.data(), str.length());
 }
 
@@ -240,85 +251,95 @@
 }
 #endif
 
-bool IsStringUTF8(StringPiece str) {
+bool IsStringUTF8(std::string_view str) {
   return internal::DoIsStringUTF8<IsValidCharacter>(str);
 }
 
-bool IsStringUTF8AllowingNoncharacters(StringPiece str) {
+bool IsStringUTF8AllowingNoncharacters(std::string_view str) {
   return internal::DoIsStringUTF8<IsValidCodepoint>(str);
 }
 
-bool EqualsASCII(StringPiece16 str, StringPiece ascii) {
-  return ranges::equal(ascii, str);
+bool EqualsASCII(std::u16string_view str, std::string_view ascii) {
+  return std::ranges::equal(ascii, str);
 }
 
-bool StartsWith(StringPiece str,
-                StringPiece search_for,
+bool StartsWith(std::string_view str,
+                std::string_view search_for,
                 CompareCase case_sensitivity) {
   return internal::StartsWithT(str, search_for, case_sensitivity);
 }
 
-bool StartsWith(StringPiece16 str,
-                StringPiece16 search_for,
+bool StartsWith(std::u16string_view str,
+                std::u16string_view search_for,
                 CompareCase case_sensitivity) {
   return internal::StartsWithT(str, search_for, case_sensitivity);
 }
 
-bool EndsWith(StringPiece str,
-              StringPiece search_for,
+bool EndsWith(std::string_view str,
+              std::string_view search_for,
               CompareCase case_sensitivity) {
   return internal::EndsWithT(str, search_for, case_sensitivity);
 }
 
-bool EndsWith(StringPiece16 str,
-              StringPiece16 search_for,
+bool EndsWith(std::u16string_view str,
+              std::u16string_view search_for,
               CompareCase case_sensitivity) {
   return internal::EndsWithT(str, search_for, case_sensitivity);
 }
 
+std::optional<std::string_view> RemovePrefix(std::string_view string,
+                                             std::string_view prefix,
+                                             CompareCase case_sensitivity) {
+  if (!StartsWith(string, prefix, case_sensitivity)) {
+    return std::nullopt;
+  }
+  string.remove_prefix(prefix.size());
+  return string;
+}
+
+std::optional<std::u16string_view> RemovePrefix(std::u16string_view string,
+                                                std::u16string_view prefix,
+                                                CompareCase case_sensitivity) {
+  if (!StartsWith(string, prefix, case_sensitivity)) {
+    return std::nullopt;
+  }
+  string.remove_prefix(prefix.size());
+  return string;
+}
+
+std::optional<std::string_view> RemoveSuffix(std::string_view string,
+                                             std::string_view suffix,
+                                             CompareCase case_sensitivity) {
+  if (!EndsWith(string, suffix, case_sensitivity)) {
+    return std::nullopt;
+  }
+  string.remove_suffix(suffix.size());
+  return string;
+}
+
+std::optional<std::u16string_view> RemoveSuffix(std::u16string_view string,
+                                                std::u16string_view suffix,
+                                                CompareCase case_sensitivity) {
+  if (!EndsWith(string, suffix, case_sensitivity)) {
+    return std::nullopt;
+  }
+  string.remove_suffix(suffix.size());
+  return string;
+}
+
 char HexDigitToInt(char c) {
   GURL_DCHECK(IsHexDigit(c));
-  if (c >= '0' && c <= '9')
+  if (c >= '0' && c <= '9') {
     return static_cast<char>(c - '0');
+  }
   return (c >= 'A' && c <= 'F') ? static_cast<char>(c - 'A' + 10)
                                 : static_cast<char>(c - 'a' + 10);
 }
 
-static const char* const kByteStringsUnlocalized[] = {
-  " B",
-  " kB",
-  " MB",
-  " GB",
-  " TB",
-  " PB"
-};
-
-std::u16string FormatBytesUnlocalized(int64_t bytes) {
-  double unit_amount = static_cast<double>(bytes);
-  size_t dimension = 0;
-  const int kKilo = 1024;
-  while (unit_amount >= kKilo &&
-         dimension < std::size(kByteStringsUnlocalized) - 1) {
-    unit_amount /= kKilo;
-    dimension++;
-  }
-
-  char buf[64];
-  if (bytes != 0 && dimension > 0 && unit_amount < 100) {
-    gurl_base::snprintf(buf, std::size(buf), "%.1lf%s", unit_amount,
-                   kByteStringsUnlocalized[dimension]);
-  } else {
-    gurl_base::snprintf(buf, std::size(buf), "%.0lf%s", unit_amount,
-                   kByteStringsUnlocalized[dimension]);
-  }
-
-  return ASCIIToUTF16(buf);
-}
-
 void ReplaceFirstSubstringAfterOffset(std::u16string* str,
                                       size_t start_offset,
-                                      StringPiece16 find_this,
-                                      StringPiece16 replace_with) {
+                                      std::u16string_view find_this,
+                                      std::u16string_view replace_with) {
   internal::DoReplaceMatchesAfterOffset(
       str, start_offset, internal::MakeSubstringMatcher(find_this),
       replace_with, internal::ReplaceType::REPLACE_FIRST);
@@ -326,8 +347,8 @@
 
 void ReplaceFirstSubstringAfterOffset(std::string* str,
                                       size_t start_offset,
-                                      StringPiece find_this,
-                                      StringPiece replace_with) {
+                                      std::string_view find_this,
+                                      std::string_view replace_with) {
   internal::DoReplaceMatchesAfterOffset(
       str, start_offset, internal::MakeSubstringMatcher(find_this),
       replace_with, internal::ReplaceType::REPLACE_FIRST);
@@ -335,8 +356,8 @@
 
 void ReplaceSubstringsAfterOffset(std::u16string* str,
                                   size_t start_offset,
-                                  StringPiece16 find_this,
-                                  StringPiece16 replace_with) {
+                                  std::u16string_view find_this,
+                                  std::u16string_view replace_with) {
   internal::DoReplaceMatchesAfterOffset(
       str, start_offset, internal::MakeSubstringMatcher(find_this),
       replace_with, internal::ReplaceType::REPLACE_ALL);
@@ -344,8 +365,8 @@
 
 void ReplaceSubstringsAfterOffset(std::string* str,
                                   size_t start_offset,
-                                  StringPiece find_this,
-                                  StringPiece replace_with) {
+                                  std::string_view find_this,
+                                  std::string_view replace_with) {
   internal::DoReplaceMatchesAfterOffset(
       str, start_offset, internal::MakeSubstringMatcher(find_this),
       replace_with, internal::ReplaceType::REPLACE_ALL);
@@ -359,86 +380,108 @@
   return internal::WriteIntoT(str, length_with_null);
 }
 
-std::string JoinString(span<const std::string> parts, StringPiece separator) {
+std::string JoinString(span<const std::string> parts,
+                       std::string_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
 std::u16string JoinString(span<const std::u16string> parts,
-                          StringPiece16 separator) {
+                          std::u16string_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
-std::string JoinString(span<const StringPiece> parts, StringPiece separator) {
+std::string JoinString(span<const std::string_view> parts,
+                       std::string_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
-std::u16string JoinString(span<const StringPiece16> parts,
-                          StringPiece16 separator) {
+std::u16string JoinString(span<const std::u16string_view> parts,
+                          std::u16string_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
-std::string JoinString(std::initializer_list<StringPiece> parts,
-                       StringPiece separator) {
+std::string JoinString(std::initializer_list<std::string_view> parts,
+                       std::string_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
-std::u16string JoinString(std::initializer_list<StringPiece16> parts,
-                          StringPiece16 separator) {
+std::u16string JoinString(std::initializer_list<std::u16string_view> parts,
+                          std::u16string_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
-std::u16string ReplaceStringPlaceholders(
-    StringPiece16 format_string,
-    const std::vector<std::u16string>& subst,
-    std::vector<size_t>* offsets) {
-  absl::optional<std::u16string> replacement =
+std::u16string ReplaceStringPlaceholders(std::u16string_view format_string,
+                                         gurl_base::span<const std::u16string> subst,
+                                         std::vector<size_t>* offsets) {
+  std::optional<std::u16string> replacement =
       internal::DoReplaceStringPlaceholders(
           format_string, subst,
           /*placeholder_prefix*/ u'$',
           /*should_escape_multiple_placeholder_prefixes*/ true,
           /*is_strict_mode*/ false, offsets);
 
-  GURL_DCHECK(replacement);
-  return replacement.value();
+  return std::move(replacement).value();
 }
 
-std::string ReplaceStringPlaceholders(StringPiece format_string,
-                                      const std::vector<std::string>& subst,
+std::string ReplaceStringPlaceholders(std::string_view format_string,
+                                      gurl_base::span<const std::string> subst,
                                       std::vector<size_t>* offsets) {
-  absl::optional<std::string> replacement =
+  std::optional<std::string> replacement =
       internal::DoReplaceStringPlaceholders(
           format_string, subst,
           /*placeholder_prefix*/ '$',
           /*should_escape_multiple_placeholder_prefixes*/ true,
           /*is_strict_mode*/ false, offsets);
 
-  GURL_DCHECK(replacement);
-  return replacement.value();
+  return std::move(replacement).value();
 }
 
-std::u16string ReplaceStringPlaceholders(const std::u16string& format_string,
-                                         const std::u16string& a,
+std::u16string ReplaceStringPlaceholders(std::u16string_view format_string,
+                                         std::u16string_view subst,
                                          size_t* offset) {
   std::vector<size_t> offsets;
+  // ReplaceStringPlaceholders() is more efficient when `offsets` is not set.
+  std::vector<size_t>* offsets_pointer = offset ? &offsets : nullptr;
   std::u16string result =
-      ReplaceStringPlaceholders(format_string, {a}, &offsets);
+      internal::DoReplaceStringPlaceholders(
+          format_string, span_from_ref(subst),
+          /*placeholder_prefix*/ u'$',
+          /*should_escape_multiple_placeholder_prefixes*/ true,
+          /*is_strict_mode*/ false, offsets_pointer)
+          .value();
 
-  GURL_DCHECK_EQ(1U, offsets.size());
-  if (offset)
+  if (offset) {
+    GURL_CHECK_EQ(1U, offsets.size());
     *offset = offsets[0];
+  }
   return result;
 }
 
+size_t strlcpy(span<char> dst, std::string_view src) {
+  return internal::lcpyT(dst, src);
+}
+
+size_t u16cstrlcpy(span<char16_t> dst, std::u16string_view src) {
+  return internal::lcpyT(dst, src);
+}
+
+size_t wcslcpy(span<wchar_t> dst, std::wstring_view src) {
+  return internal::lcpyT(dst, src);
+}
+
 size_t strlcpy(char* dst, const char* src, size_t dst_size) {
-  return internal::lcpyT(dst, src, dst_size);
+  return internal::lcpyT(
+      UNSAFE_TODO(gurl_base::span(dst, dst_size), std::string_view(src)));
 }
 
 size_t u16cstrlcpy(char16_t* dst, const char16_t* src, size_t dst_size) {
-  return internal::lcpyT(dst, src, dst_size);
+  return internal::lcpyT(UNSAFE_TODO(gurl_base::span(dst, dst_size)),
+                         std::u16string_view(src));
 }
 
 size_t wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size) {
-  return internal::lcpyT(dst, src, dst_size);
+  return internal::lcpyT(UNSAFE_TODO(gurl_base::span(dst, dst_size)),
+                         std::wstring_view(src));
 }
 
 }  // namespace base
diff --git a/base/strings/string_util.h b/base/strings/string_util.h
index 8f8bb72..06def58 100644
--- a/base/strings/string_util.h
+++ b/base/strings/string_util.h
@@ -4,6 +4,11 @@
 //
 // This file defines utility functions for working with strings.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
+#pragma allow_unsafe_libc_calls
+#endif
+
 #ifndef BASE_STRINGS_STRING_UTIL_H_
 #define BASE_STRINGS_STRING_UTIL_H_
 
@@ -11,19 +16,21 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <concepts>
 #include <initializer_list>
 #include <memory>
+#include <optional>
 #include <string>
 #include <string_view>
-#include <type_traits>
 #include <vector>
 
 #include "polyfills/base/base_export.h"
 #include "polyfills/base/check_op.h"
 #include "base/compiler_specific.h"
 #include "base/containers/span.h"
-#include "base/strings/string_piece.h"  // For implicit conversions.
+// For implicit conversions.
 #include "base/strings/string_util_internal.h"
+#include "base/types/to_address.h"
 #include "build/build_config.h"
 
 namespace gurl_base {
@@ -36,15 +43,17 @@
 // Wrapper for vsnprintf that always null-terminates and always returns the
 // number of characters that would be in an untruncated formatted
 // string, even when truncation occurs.
-int vsnprintf(char* buffer, size_t size, const char* format, va_list arguments)
-    PRINTF_FORMAT(3, 0);
+// TODO(tsepez): should be UNSAFE_BUFFER_USAGE.
+PRINTF_FORMAT(3, 0)
+int vsnprintf(char* buffer, size_t size, const char* format, va_list arguments);
 
 // Some of these implementations need to be inlined.
 
 // We separate the declaration from the implementation of this inline
 // function just so the PRINTF_FORMAT works.
-inline int snprintf(char* buffer, size_t size, const char* format, ...)
-    PRINTF_FORMAT(3, 4);
+// TODO(tsepez): should be UNSAFE_BUFFER_USAGE.
+PRINTF_FORMAT(3, 4)
+inline int snprintf(char* buffer, size_t size, const char* format, ...);
 inline int snprintf(char* buffer, size_t size, const char* format, ...) {
   va_list arguments;
   va_start(arguments, format);
@@ -54,11 +63,22 @@
 }
 
 // BSD-style safe and consistent string copy functions.
+
+// Copies `src` to `dst`, truncating `dst` if it does not fit, and ensuring that
+// `dst` is NUL-terminated if it's not an empty span. Returns the length of
+// `src` in characters. If the return value is `>= dst.size()`, then the output
+// was truncated. NOTE: All sizes are in number of characters, NOT in bytes.
+BASE_EXPORT size_t strlcpy(span<char> dst, std::string_view src);
+BASE_EXPORT size_t u16cstrlcpy(span<char16_t> dst, std::u16string_view src);
+BASE_EXPORT size_t wcslcpy(span<wchar_t> dst, std::wstring_view src);
+
 // Copies |src| to |dst|, where |dst_size| is the total allocated size of |dst|.
 // Copies at most |dst_size|-1 characters, and always NULL terminates |dst|, as
 // long as |dst_size| is not 0.  Returns the length of |src| in characters.
 // If the return value is >= dst_size, then the output was truncated.
 // NOTE: All sizes are in number of characters, NOT in bytes.
+//
+// TODO: crbug.com/40284755 - Make these UNSAFE_BUFFER_USAGE.
 BASE_EXPORT size_t strlcpy(char* dst, const char* src, size_t dst_size);
 BASE_EXPORT size_t u16cstrlcpy(char16_t* dst,
                                const char16_t* src,
@@ -88,69 +108,46 @@
 // This function is intended to be called from gurl_base::vswprintf.
 BASE_EXPORT bool IsWprintfFormatPortable(const wchar_t* format);
 
-// Simplified implementation of C++20's std::basic_string_view(It, End).
-// Reference: https://wg21.link/string.view.cons
-template <typename CharT, typename Iter>
-constexpr BasicStringPiece<CharT> MakeBasicStringPiece(Iter begin, Iter end) {
-  GURL_DCHECK_GE(end - begin, 0);
-  return {std::to_address(begin), static_cast<size_t>(end - begin)};
-}
-
-// Explicit instantiations of MakeBasicStringPiece for the BasicStringPiece
-// aliases defined in base/strings/string_piece.h
-template <typename Iter>
-constexpr StringPiece MakeStringPiece(Iter begin, Iter end) {
-  return MakeBasicStringPiece<char>(begin, end);
-}
-
-template <typename Iter>
-constexpr StringPiece16 MakeStringPiece16(Iter begin, Iter end) {
-  return MakeBasicStringPiece<char16_t>(begin, end);
-}
-
-template <typename Iter>
-constexpr std::wstring_view MakeWStringView(Iter begin, Iter end) {
-  return MakeBasicStringPiece<wchar_t>(begin, end);
-}
-
 // ASCII-specific tolower.  The standard library's tolower is locale sensitive,
 // so we don't want to use it here.
-template <typename CharT,
-          typename = std::enable_if_t<std::is_integral_v<CharT>>>
+template <typename CharT>
+  requires(std::integral<CharT>)
 constexpr CharT ToLowerASCII(CharT c) {
   return internal::ToLowerASCII(c);
 }
 
 // ASCII-specific toupper.  The standard library's toupper is locale sensitive,
 // so we don't want to use it here.
-template <typename CharT,
-          typename = std::enable_if_t<std::is_integral_v<CharT>>>
+template <typename CharT>
+  requires(std::integral<CharT>)
 CharT ToUpperASCII(CharT c) {
   return (c >= 'a' && c <= 'z') ? static_cast<CharT>(c + 'A' - 'a') : c;
 }
 
 // Converts the given string to its ASCII-lowercase equivalent. Non-ASCII
-// bytes (or UTF-16 code units in `StringPiece16`) are permitted but will be
-// unmodified.
-BASE_EXPORT std::string ToLowerASCII(StringPiece str);
-BASE_EXPORT std::u16string ToLowerASCII(StringPiece16 str);
+// bytes (or UTF-16 code units in `std::u16string_view`) are permitted but will
+// be unmodified.
+BASE_EXPORT std::string ToLowerASCII(std::string_view str);
+BASE_EXPORT std::u16string ToLowerASCII(std::u16string_view str);
 
 // Converts the given string to its ASCII-uppercase equivalent. Non-ASCII
-// bytes (or UTF-16 code units in `StringPiece16`) are permitted but will be
-// unmodified.
-BASE_EXPORT std::string ToUpperASCII(StringPiece str);
-BASE_EXPORT std::u16string ToUpperASCII(StringPiece16 str);
+// bytes (or UTF-16 code units in `std::u16string_view`) are permitted but will
+// be unmodified.
+BASE_EXPORT std::string ToUpperASCII(std::string_view str);
+BASE_EXPORT std::u16string ToUpperASCII(std::u16string_view str);
 
 // Functor for ASCII case-insensitive comparisons for STL algorithms like
-// std::search. Non-ASCII bytes (or UTF-16 code units in `StringPiece16`) are
-// permitted but will be compared as-is.
+// std::search. Non-ASCII bytes (or UTF-16 code units in `std::u16string_view`)
+// are permitted but will be compared as-is.
 //
 // Note that a full Unicode version of this functor is not possible to write
 // because case mappings might change the number of characters, depend on
 // context (combining accents), and require handling UTF-16. If you need
 // proper Unicode support, use gurl_base::i18n::ToLower/FoldCase and then just
 // use a normal operator== on the result.
-template<typename Char> struct CaseInsensitiveCompareASCII {
+template <typename Char>
+  requires(std::integral<Char>)
+struct CaseInsensitiveCompareASCII {
  public:
   bool operator()(Char x, Char y) const {
     return ToLowerASCII(x) == ToLowerASCII(y);
@@ -166,32 +163,35 @@
 // or gurl_base::i18n::FoldCase and then just call the normal string operators on the
 // result.
 //
-// Non-ASCII bytes (or UTF-16 code units in `StringPiece16`) are permitted but
-// will be compared unmodified.
-BASE_EXPORT constexpr int CompareCaseInsensitiveASCII(StringPiece a,
-                                                      StringPiece b) {
+// Non-ASCII bytes (or UTF-16 code units in `std::u16string_view`) are permitted
+// but will be compared unmodified.
+BASE_EXPORT constexpr int CompareCaseInsensitiveASCII(std::string_view a,
+                                                      std::string_view b) {
   return internal::CompareCaseInsensitiveASCIIT(a, b);
 }
-BASE_EXPORT constexpr int CompareCaseInsensitiveASCII(StringPiece16 a,
-                                                      StringPiece16 b) {
+BASE_EXPORT constexpr int CompareCaseInsensitiveASCII(std::u16string_view a,
+                                                      std::u16string_view b) {
   return internal::CompareCaseInsensitiveASCIIT(a, b);
 }
 
 // Equality for ASCII case-insensitive comparisons. Non-ASCII bytes (or UTF-16
-// code units in `StringPiece16`) are permitted but will be compared unmodified.
-// To compare all Unicode code points case-insensitively, use
+// code units in `std::u16string_view`) are permitted but will be compared
+// unmodified. To compare all Unicode code points case-insensitively, use
 // gurl_base::i18n::ToLower or gurl_base::i18n::FoldCase and then compare with either ==
 // or !=.
-inline bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b) {
+inline bool EqualsCaseInsensitiveASCII(std::string_view a, std::string_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
-inline bool EqualsCaseInsensitiveASCII(StringPiece16 a, StringPiece16 b) {
+inline bool EqualsCaseInsensitiveASCII(std::u16string_view a,
+                                       std::u16string_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
-inline bool EqualsCaseInsensitiveASCII(StringPiece16 a, StringPiece b) {
+inline bool EqualsCaseInsensitiveASCII(std::u16string_view a,
+                                       std::string_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
-inline bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece16 b) {
+inline bool EqualsCaseInsensitiveASCII(std::string_view a,
+                                       std::u16string_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
 
@@ -214,15 +214,16 @@
 // Contains the set of characters representing whitespace in the corresponding
 // encoding. Null-terminated. The ASCII versions are the whitespaces as defined
 // by HTML5, and don't include control characters.
-BASE_EXPORT extern const wchar_t kWhitespaceWide[];  // Includes Unicode.
+BASE_EXPORT extern const wchar_t kWhitespaceWide[];    // Includes Unicode.
 BASE_EXPORT extern const char16_t kWhitespaceUTF16[];  // Includes Unicode.
 BASE_EXPORT extern const char16_t
     kWhitespaceNoCrLfUTF16[];  // Unicode w/o CR/LF.
 BASE_EXPORT extern const char kWhitespaceASCII[];
 BASE_EXPORT extern const char16_t kWhitespaceASCIIAs16[];  // No unicode.
-                                                           //
+
 // https://infra.spec.whatwg.org/#ascii-whitespace
-BASE_EXPORT extern const char kInfraAsciiWhitespace[];
+// Note that this array is not null-terminated.
+inline constexpr char kInfraAsciiWhitespace[] = {0x09, 0x0A, 0x0C, 0x0D, 0x20};
 
 // Null-terminated string representing the UTF-8 byte order mark.
 BASE_EXPORT extern const char kUtf8ByteOrderMark[];
@@ -230,11 +231,11 @@
 // Removes characters in |remove_chars| from anywhere in |input|.  Returns true
 // if any characters were removed.  |remove_chars| must be null-terminated.
 // NOTE: Safe to use the same variable for both |input| and |output|.
-BASE_EXPORT bool RemoveChars(StringPiece16 input,
-                             StringPiece16 remove_chars,
+BASE_EXPORT bool RemoveChars(std::u16string_view input,
+                             std::u16string_view remove_chars,
                              std::u16string* output);
-BASE_EXPORT bool RemoveChars(StringPiece input,
-                             StringPiece remove_chars,
+BASE_EXPORT bool RemoveChars(std::string_view input,
+                             std::string_view remove_chars,
                              std::string* output);
 
 // Replaces characters in |replace_chars| from anywhere in |input| with
@@ -242,20 +243,20 @@
 // the |replace_with| string.  Returns true if any characters were replaced.
 // |replace_chars| must be null-terminated.
 // NOTE: Safe to use the same variable for both |input| and |output|.
-BASE_EXPORT bool ReplaceChars(StringPiece16 input,
-                              StringPiece16 replace_chars,
-                              StringPiece16 replace_with,
+BASE_EXPORT bool ReplaceChars(std::u16string_view input,
+                              std::u16string_view replace_chars,
+                              std::u16string_view replace_with,
                               std::u16string* output);
-BASE_EXPORT bool ReplaceChars(StringPiece input,
-                              StringPiece replace_chars,
-                              StringPiece replace_with,
+BASE_EXPORT bool ReplaceChars(std::string_view input,
+                              std::string_view replace_chars,
+                              std::string_view replace_with,
                               std::string* output);
 
 enum TrimPositions {
-  TRIM_NONE     = 0,
-  TRIM_LEADING  = 1 << 0,
+  TRIM_NONE = 0,
+  TRIM_LEADING = 1 << 0,
   TRIM_TRAILING = 1 << 1,
-  TRIM_ALL      = TRIM_LEADING | TRIM_TRAILING,
+  TRIM_ALL = TRIM_LEADING | TRIM_TRAILING,
 };
 
 // Removes characters in |trim_chars| from the beginning and end of |input|.
@@ -264,45 +265,47 @@
 //
 // It is safe to use the same variable for both |input| and |output| (this is
 // the normal usage to trim in-place).
-BASE_EXPORT bool TrimString(StringPiece16 input,
-                            StringPiece16 trim_chars,
+BASE_EXPORT bool TrimString(std::u16string_view input,
+                            std::u16string_view trim_chars,
                             std::u16string* output);
-BASE_EXPORT bool TrimString(StringPiece input,
-                            StringPiece trim_chars,
+BASE_EXPORT bool TrimString(std::string_view input,
+                            std::string_view trim_chars,
                             std::string* output);
 
-// StringPiece versions of the above. The returned pieces refer to the original
-// buffer.
-BASE_EXPORT StringPiece16 TrimString(StringPiece16 input,
-                                     StringPiece16 trim_chars,
-                                     TrimPositions positions);
-BASE_EXPORT StringPiece TrimString(StringPiece input,
-                                   StringPiece trim_chars,
-                                   TrimPositions positions);
+// std::string_view versions of the above. The returned pieces refer to the
+// original buffer.
+BASE_EXPORT std::u16string_view TrimString(std::u16string_view input,
+                                           std::u16string_view trim_chars,
+                                           TrimPositions positions);
+BASE_EXPORT std::string_view TrimString(std::string_view input,
+                                        std::string_view trim_chars,
+                                        TrimPositions positions);
 
 // Truncates a string to the nearest UTF-8 character that will leave
 // the string less than or equal to the specified byte size.
-BASE_EXPORT void TruncateUTF8ToByteSize(const std::string& input,
+BASE_EXPORT void TruncateUTF8ToByteSize(std::string_view input,
                                         const size_t byte_size,
                                         std::string* output);
+BASE_EXPORT std::string_view TruncateUTF8ToByteSize(std::string_view input,
+                                                    size_t byte_size);
 
 // Trims any whitespace from either end of the input string.
 //
-// The StringPiece versions return a substring referencing the input buffer.
-// The ASCII versions look only for ASCII whitespace.
+// The std::string_view versions return a substring referencing the input
+// buffer. The ASCII versions look only for ASCII whitespace.
 //
 // The std::string versions return where whitespace was found.
 // NOTE: Safe to use the same variable for both input and output.
-BASE_EXPORT TrimPositions TrimWhitespace(StringPiece16 input,
+BASE_EXPORT TrimPositions TrimWhitespace(std::u16string_view input,
                                          TrimPositions positions,
                                          std::u16string* output);
-BASE_EXPORT StringPiece16 TrimWhitespace(StringPiece16 input,
-                                         TrimPositions positions);
-BASE_EXPORT TrimPositions TrimWhitespaceASCII(StringPiece input,
+BASE_EXPORT std::u16string_view TrimWhitespace(std::u16string_view input,
+                                               TrimPositions positions);
+BASE_EXPORT TrimPositions TrimWhitespaceASCII(std::string_view input,
                                               TrimPositions positions,
                                               std::string* output);
-BASE_EXPORT StringPiece TrimWhitespaceASCII(StringPiece input,
-                                            TrimPositions positions);
+BASE_EXPORT std::string_view TrimWhitespaceASCII(std::string_view input,
+                                                 TrimPositions positions);
 
 // Searches for CR or LF characters.  Removes all contiguous whitespace
 // strings that contain them.  This is useful when trying to deal with text
@@ -313,28 +316,29 @@
 //     sequences containing a CR or LF are trimmed.
 // (3) All other whitespace sequences are converted to single spaces.
 BASE_EXPORT std::u16string CollapseWhitespace(
-    StringPiece16 text,
+    std::u16string_view text,
     bool trim_sequences_with_line_breaks);
 BASE_EXPORT std::string CollapseWhitespaceASCII(
-    StringPiece text,
+    std::string_view text,
     bool trim_sequences_with_line_breaks);
 
 // Returns true if |input| is empty or contains only characters found in
 // |characters|.
-BASE_EXPORT bool ContainsOnlyChars(StringPiece input, StringPiece characters);
-BASE_EXPORT bool ContainsOnlyChars(StringPiece16 input,
-                                   StringPiece16 characters);
+BASE_EXPORT bool ContainsOnlyChars(std::string_view input,
+                                   std::string_view characters);
+BASE_EXPORT bool ContainsOnlyChars(std::u16string_view input,
+                                   std::u16string_view characters);
 
 // Returns true if |str| is structurally valid UTF-8 and also doesn't
 // contain any non-character code point (e.g. U+10FFFE). Prohibiting
 // non-characters increases the likelihood of detecting non-UTF-8 in
 // real-world text, for callers which do not need to accept
 // non-characters in strings.
-BASE_EXPORT bool IsStringUTF8(StringPiece str);
+BASE_EXPORT bool IsStringUTF8(std::string_view str);
 
 // Returns true if |str| contains valid UTF-8, allowing non-character
 // code points.
-BASE_EXPORT bool IsStringUTF8AllowingNoncharacters(StringPiece str);
+BASE_EXPORT bool IsStringUTF8AllowingNoncharacters(std::string_view str);
 
 // Returns true if |str| contains only valid ASCII character values.
 // Note 1: IsStringASCII executes in time determined solely by the
@@ -342,8 +346,8 @@
 // timing attacks for all strings of equal length.
 // Note 2: IsStringASCII assumes the input is likely all ASCII, and
 // does not leave early if it is not the case.
-BASE_EXPORT bool IsStringASCII(StringPiece str);
-BASE_EXPORT bool IsStringASCII(StringPiece16 str);
+BASE_EXPORT bool IsStringASCII(std::string_view str);
+BASE_EXPORT bool IsStringASCII(std::u16string_view str);
 
 #if defined(WCHAR_T_IS_32_BIT)
 BASE_EXPORT bool IsStringASCII(std::wstring_view str);
@@ -352,7 +356,7 @@
 // Performs a case-sensitive string compare of the given 16-bit string against
 // the given 8-bit ASCII string (typically a constant). The behavior is
 // undefined if the |ascii| string is not ASCII.
-BASE_EXPORT bool EqualsASCII(StringPiece16 str, StringPiece ascii);
+BASE_EXPORT bool EqualsASCII(std::u16string_view str, std::string_view ascii);
 
 // Indicates case sensitivity of comparisons. Only ASCII case insensitivity
 // is supported. Full Unicode case-insensitive conversions would need to go in
@@ -368,60 +372,95 @@
 };
 
 BASE_EXPORT bool StartsWith(
-    StringPiece str,
-    StringPiece search_for,
+    std::string_view str,
+    std::string_view search_for,
     CompareCase case_sensitivity = CompareCase::SENSITIVE);
 BASE_EXPORT bool StartsWith(
-    StringPiece16 str,
-    StringPiece16 search_for,
+    std::u16string_view str,
+    std::u16string_view search_for,
     CompareCase case_sensitivity = CompareCase::SENSITIVE);
 BASE_EXPORT bool EndsWith(
-    StringPiece str,
-    StringPiece search_for,
+    std::string_view str,
+    std::string_view search_for,
     CompareCase case_sensitivity = CompareCase::SENSITIVE);
 BASE_EXPORT bool EndsWith(
-    StringPiece16 str,
-    StringPiece16 search_for,
+    std::u16string_view str,
+    std::u16string_view search_for,
+    CompareCase case_sensitivity = CompareCase::SENSITIVE);
+
+// If `string` begins with `prefix`, return a view into the portion
+// of `string` following `prefix`. Otherwise, return nullopt. The
+// `case_sensitivity` argument is the same as would be passed to
+// StartsWith() above.
+BASE_EXPORT std::optional<std::string_view> RemovePrefix(
+    std::string_view string,
+    std::string_view prefix,
+    CompareCase case_sensitivity = CompareCase::SENSITIVE);
+BASE_EXPORT std::optional<std::u16string_view> RemovePrefix(
+    std::u16string_view string,
+    std::u16string_view prefix,
+    CompareCase case_sensitivity = CompareCase::SENSITIVE);
+
+// If `string` ends with `suffix`, return a view into the portion
+// of `string` preceding `suffix`. Otherwise, return nullopt. The
+// `case_sensitivity` argument is the same as would be passed to
+// EndsWith() above.
+BASE_EXPORT std::optional<std::string_view> RemoveSuffix(
+    std::string_view string,
+    std::string_view suffix,
+    CompareCase case_sensitivity = CompareCase::SENSITIVE);
+BASE_EXPORT std::optional<std::u16string_view> RemoveSuffix(
+    std::u16string_view string,
+    std::u16string_view suffix,
     CompareCase case_sensitivity = CompareCase::SENSITIVE);
 
 // Determines the type of ASCII character, independent of locale (the C
 // library versions will change based on locale).
 template <typename Char>
-inline bool IsAsciiWhitespace(Char c) {
-  // kWhitespaceASCII is a null-terminated string.
-  for (const char* cur = kWhitespaceASCII; *cur; ++cur) {
-    if (*cur == c)
+  requires(std::integral<Char>)
+constexpr bool IsAsciiWhitespace(Char c) {
+  // SAFETY: kWhitespaceASCII is a NUL-terminated string.
+  for (const char* cur = kWhitespaceASCII; *cur; UNSAFE_BUFFERS(++cur)) {
+    if (*cur == c) {
       return true;
+    }
   }
   return false;
 }
 template <typename Char>
-inline bool IsAsciiAlpha(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsAsciiAlpha(Char c) {
   return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
 }
 template <typename Char>
-inline bool IsAsciiUpper(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsAsciiUpper(Char c) {
   return c >= 'A' && c <= 'Z';
 }
 template <typename Char>
-inline bool IsAsciiLower(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsAsciiLower(Char c) {
   return c >= 'a' && c <= 'z';
 }
 template <typename Char>
-inline bool IsAsciiDigit(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsAsciiDigit(Char c) {
   return c >= '0' && c <= '9';
 }
 template <typename Char>
-inline bool IsAsciiAlphaNumeric(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsAsciiAlphaNumeric(Char c) {
   return IsAsciiAlpha(c) || IsAsciiDigit(c);
 }
 template <typename Char>
-inline bool IsAsciiPrintable(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsAsciiPrintable(Char c) {
   return c >= ' ' && c <= '~';
 }
 
 template <typename Char>
-inline bool IsAsciiControl(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsAsciiControl(Char c) {
   if constexpr (std::is_signed_v<Char>) {
     if (c < 0) {
       return false;
@@ -431,21 +470,23 @@
 }
 
 template <typename Char>
-inline bool IsUnicodeControl(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsUnicodeControl(Char c) {
   return IsAsciiControl(c) ||
          // C1 control characters: http://unicode.org/charts/PDF/U0080.pdf
          (c >= 0x80 && c <= 0x9F);
 }
 
 template <typename Char>
-inline bool IsAsciiPunctuation(Char c) {
+  requires(std::integral<Char>)
+constexpr bool IsAsciiPunctuation(Char c) {
   return c > 0x20 && c < 0x7f && !IsAsciiAlphaNumeric(c);
 }
 
 template <typename Char>
-inline bool IsHexDigit(Char c) {
-  return (c >= '0' && c <= '9') ||
-         (c >= 'A' && c <= 'F') ||
+  requires(std::integral<Char>)
+constexpr bool IsHexDigit(Char c) {
+  return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') ||
          (c >= 'a' && c <= 'f');
 }
 
@@ -465,22 +506,24 @@
 // should call IsAsciiWhitespace(), and if they are from a UTF-8 string they may
 // be individual units of a multi-unit code point.  Convert to 16- or 32-bit
 // values known to hold the full code point before calling this.
-template <typename Char, typename = std::enable_if_t<(sizeof(Char) > 1)>>
-inline bool IsUnicodeWhitespace(Char c) {
-  // kWhitespaceWide is a null-terminated string.
-  for (const auto* cur = kWhitespaceWide; *cur; ++cur) {
+template <typename Char>
+  requires(sizeof(Char) > 1)
+constexpr bool IsUnicodeWhitespace(Char c) {
+  // SAFETY: kWhitespaceWide is a NUL-terminated string.
+  for (const auto* cur = kWhitespaceWide; *cur; UNSAFE_BUFFERS(++cur)) {
     if (static_cast<typename std::make_unsigned_t<wchar_t>>(*cur) ==
-        static_cast<typename std::make_unsigned_t<Char>>(c))
+        static_cast<typename std::make_unsigned_t<Char>>(c)) {
       return true;
+    }
   }
   return false;
 }
 
-// DANGEROUS: Assumes ASCII or not base on the size of `Char`.  You should
+// DANGEROUS: Assumes ASCII or not based on the size of `Char`.  You should
 // probably be explicitly calling IsUnicodeWhitespace() or IsAsciiWhitespace()
 // instead!
 template <typename Char>
-inline bool IsWhitespace(Char c) {
+constexpr bool IsWhitespace(Char c) {
   if constexpr (sizeof(Char) > 1) {
     return IsUnicodeWhitespace(c);
   } else {
@@ -488,23 +531,18 @@
   }
 }
 
-// Return a byte string in human-readable format with a unit suffix. Not
-// appropriate for use in any UI; use of FormatBytes and friends in ui/base is
-// highly recommended instead. TODO(avi): Figure out how to get callers to use
-// FormatBytes instead; remove this.
-BASE_EXPORT std::u16string FormatBytesUnlocalized(int64_t bytes);
-
 // Starting at |start_offset| (usually 0), replace the first instance of
 // |find_this| with |replace_with|.
-BASE_EXPORT void ReplaceFirstSubstringAfterOffset(std::u16string* str,
-                                                  size_t start_offset,
-                                                  StringPiece16 find_this,
-                                                  StringPiece16 replace_with);
+BASE_EXPORT void ReplaceFirstSubstringAfterOffset(
+    std::u16string* str,
+    size_t start_offset,
+    std::u16string_view find_this,
+    std::u16string_view replace_with);
 BASE_EXPORT void ReplaceFirstSubstringAfterOffset(
     std::string* str,
     size_t start_offset,
-    StringPiece find_this,
-    StringPiece replace_with);
+    std::string_view find_this,
+    std::string_view replace_with);
 
 // Starting at |start_offset| (usually 0), look through |str| and replace all
 // instances of |find_this| with |replace_with|.
@@ -514,13 +552,12 @@
 //   std::replace(str.begin(), str.end(), 'a', 'b');
 BASE_EXPORT void ReplaceSubstringsAfterOffset(std::u16string* str,
                                               size_t start_offset,
-                                              StringPiece16 find_this,
-                                              StringPiece16 replace_with);
-BASE_EXPORT void ReplaceSubstringsAfterOffset(
-    std::string* str,
-    size_t start_offset,
-    StringPiece find_this,
-    StringPiece replace_with);
+                                              std::u16string_view find_this,
+                                              std::u16string_view replace_with);
+BASE_EXPORT void ReplaceSubstringsAfterOffset(std::string* str,
+                                              size_t start_offset,
+                                              std::string_view find_this,
+                                              std::string_view replace_with);
 
 // Reserves enough memory in |str| to accommodate |length_with_null| characters,
 // sets the size of |str| to |length_with_null - 1| characters, and returns a
@@ -548,47 +585,56 @@
 // string_split.h.
 //
 // If possible, callers should build a vector of StringPieces and use the
-// StringPiece variant, so that they do not create unnecessary copies of
+// std::string_view variant, so that they do not create unnecessary copies of
 // strings. For example, instead of using SplitString, modifying the vector,
 // then using JoinString, use SplitStringPiece followed by JoinString so that no
 // copies of those strings are created until the final join operation.
 //
 // Use StrCat (in base/strings/strcat.h) if you don't need a separator.
 BASE_EXPORT std::string JoinString(span<const std::string> parts,
-                                   StringPiece separator);
+                                   std::string_view separator);
 BASE_EXPORT std::u16string JoinString(span<const std::u16string> parts,
-                                      StringPiece16 separator);
-BASE_EXPORT std::string JoinString(span<const StringPiece> parts,
-                                   StringPiece separator);
-BASE_EXPORT std::u16string JoinString(span<const StringPiece16> parts,
-                                      StringPiece16 separator);
+                                      std::u16string_view separator);
+BASE_EXPORT std::string JoinString(span<const std::string_view> parts,
+                                   std::string_view separator);
+BASE_EXPORT std::u16string JoinString(span<const std::u16string_view> parts,
+                                      std::u16string_view separator);
 // Explicit initializer_list overloads are required to break ambiguity when used
 // with a literal initializer list (otherwise the compiler would not be able to
-// decide between the string and StringPiece overloads).
-BASE_EXPORT std::string JoinString(std::initializer_list<StringPiece> parts,
-                                   StringPiece separator);
+// decide between the string and std::string_view overloads).
+BASE_EXPORT std::string JoinString(
+    std::initializer_list<std::string_view> parts,
+    std::string_view separator);
 BASE_EXPORT std::u16string JoinString(
-    std::initializer_list<StringPiece16> parts,
-    StringPiece16 separator);
+    std::initializer_list<std::u16string_view> parts,
+    std::u16string_view separator);
 
 // Replace $1-$2-$3..$9 in the format string with values from |subst|.
 // Additionally, any number of consecutive '$' characters is replaced by that
 // number less one. Eg $$->$, $$$->$$, etc. The offsets parameter here can be
 // NULL. This only allows you to use up to nine replacements.
+//
+// Calling ReplaceStringPlaceholders(u"$1", {ReturnU16string()}, nullptr) will
+// unexpectedly give you the single-u16string overload below, the same as if you
+// had written ReplaceStringPlaceholders(u"$1", ReturnU16String(), nullptr).
+// This is surprising but mostly harmless. Call the gurl_base::span constructor
+// explicitly if you need to force this overload, ie.
+// ReplaceStringPlaceholders(
+//     u"$1", gurl_base::span<const std::u16string>({ReturnU16string()}), nullptr).
 BASE_EXPORT std::u16string ReplaceStringPlaceholders(
-    StringPiece16 format_string,
-    const std::vector<std::u16string>& subst,
+    std::u16string_view format_string,
+    gurl_base::span<const std::u16string> subst,
     std::vector<size_t>* offsets);
 
 BASE_EXPORT std::string ReplaceStringPlaceholders(
-    StringPiece format_string,
-    const std::vector<std::string>& subst,
+    std::string_view format_string,
+    gurl_base::span<const std::string> subst,
     std::vector<size_t>* offsets);
 
 // Single-string shortcut for ReplaceStringHolders. |offset| may be NULL.
 BASE_EXPORT std::u16string ReplaceStringPlaceholders(
-    const std::u16string& format_string,
-    const std::u16string& a,
+    std::u16string_view format_string,
+    std::u16string_view subst,
     size_t* offset);
 
 }  // namespace base
diff --git a/base/strings/string_util_constants.cc b/base/strings/string_util_constants.cc
index 12a3c5e..fece0af 100644
--- a/base/strings/string_util_constants.cc
+++ b/base/strings/string_util_constants.cc
@@ -49,8 +49,6 @@
 const char kWhitespaceASCII[] = {WHITESPACE_ASCII, 0};
 const char16_t kWhitespaceASCIIAs16[] = {WHITESPACE_ASCII, 0};
 
-const char kInfraAsciiWhitespace[] = {0x09, 0x0A, 0x0C, 0x0D, 0x20, 0};
-
 const char kUtf8ByteOrderMark[] = "\xEF\xBB\xBF";
 
 }  // namespace base
diff --git a/base/strings/string_util_impl_helpers.h b/base/strings/string_util_impl_helpers.h
index 948463b..276cab3 100644
--- a/base/strings/string_util_impl_helpers.h
+++ b/base/strings/string_util_impl_helpers.h
@@ -6,15 +6,18 @@
 #define BASE_STRINGS_STRING_UTIL_IMPL_HELPERS_H_
 
 #include <algorithm>
+#include <array>
+#include <numeric>
+#include <optional>
+#include <string_view>
+#include <type_traits>
 
 #include "polyfills/base/check.h"
 #include "polyfills/base/check_op.h"
+#include "base/compiler_specific.h"
 #include "polyfills/base/logging.h"
 #include "polyfills/base/notreached.h"
-#include "base/ranges/algorithm.h"
-#include "base/strings/string_piece.h"
 #include "base/third_party/icu/icu_utf.h"
-#include "absl/types/optional.h"
 
 namespace gurl_base::internal {
 
@@ -48,8 +51,9 @@
 std::basic_string<CharT> ToLowerASCIIImpl(T str) {
   std::basic_string<CharT> ret;
   ret.reserve(str.size());
-  for (size_t i = 0; i < str.size(); i++)
+  for (size_t i = 0; i < str.size(); i++) {
     ret.push_back(ToLowerASCII(str[i]));
+  }
   return ret;
 }
 
@@ -57,8 +61,9 @@
 std::basic_string<CharT> ToUpperASCIIImpl(T str) {
   std::basic_string<CharT> ret;
   ret.reserve(str.size());
-  for (size_t i = 0; i < str.size(); i++)
+  for (size_t i = 0; i < str.size(); i++) {
     ret.push_back(ToUpperASCII(str[i]));
+  }
   return ret;
 }
 
@@ -68,8 +73,8 @@
                           TrimPositions positions,
                           std::basic_string<CharT>* output) {
   // Find the edges of leading/trailing whitespace as desired. Need to use
-  // a StringPiece version of input to be able to call find* on it with the
-  // StringPiece version of trim_chars (normally the trim_chars will be a
+  // a std::string_view version of input to be able to call find* on it with the
+  // std::string_view version of trim_chars (normally the trim_chars will be a
   // constant so avoid making a copy).
   const size_t last_char = input.length() - 1;
   const size_t first_good_char =
@@ -89,7 +94,7 @@
   }
 
   // Trim.
-  output->assign(input.data() + first_good_char,
+  output->assign(UNSAFE_TODO(input.data() + first_good_char),
                  last_good_char - first_good_char + 1);
 
   // Return where we trimmed from.
@@ -155,105 +160,108 @@
 bool DoIsStringASCII(const Char* characters, size_t length) {
   // Bitmasks to detect non ASCII characters for character sizes of 8, 16 and 32
   // bits.
-  constexpr MachineWord NonASCIIMasks[] = {
-      0, MachineWord(0x8080808080808080ULL), MachineWord(0xFF80FF80FF80FF80ULL),
-      0, MachineWord(0xFFFFFF80FFFFFF80ULL),
-  };
+  constexpr auto NonASCIIMasks = std::to_array<MachineWord>({
+      0,
+      MachineWord(0x8080808080808080ULL),
+      MachineWord(0xFF80FF80FF80FF80ULL),
+      0,
+      MachineWord(0xFFFFFF80FFFFFF80ULL),
+  });
 
-  if (!length)
+  if (!length) {
     return true;
+  }
   constexpr MachineWord non_ascii_bit_mask = NonASCIIMasks[sizeof(Char)];
   static_assert(non_ascii_bit_mask, "Error: Invalid Mask");
   MachineWord all_char_bits = 0;
-  const Char* end = characters + length;
+  const Char* end = UNSAFE_TODO(characters + length);
 
   // Prologue: align the input.
-  while (!IsMachineWordAligned(characters) && characters < end)
-    all_char_bits |= static_cast<MachineWord>(*characters++);
-  if (all_char_bits & non_ascii_bit_mask)
+  while (!IsMachineWordAligned(characters) && characters < end) {
+    all_char_bits |= UNSAFE_TODO(static_cast<MachineWord>(*characters++));
+  }
+  if (all_char_bits & non_ascii_bit_mask) {
     return false;
+  }
 
   // Compare the values of CPU word size.
   constexpr size_t chars_per_word = sizeof(MachineWord) / sizeof(Char);
   constexpr int batch_count = 16;
-  while (characters <= end - batch_count * chars_per_word) {
+  while (characters <= UNSAFE_TODO(end - batch_count * chars_per_word)) {
     all_char_bits = 0;
     for (int i = 0; i < batch_count; ++i) {
       all_char_bits |= *(reinterpret_cast<const MachineWord*>(characters));
-      characters += chars_per_word;
+      UNSAFE_TODO(characters += chars_per_word);
     }
-    if (all_char_bits & non_ascii_bit_mask)
+    if (all_char_bits & non_ascii_bit_mask) {
       return false;
+    }
   }
 
   // Process the remaining words.
   all_char_bits = 0;
-  while (characters <= end - chars_per_word) {
+  while (characters <= UNSAFE_TODO(end - chars_per_word)) {
     all_char_bits |= *(reinterpret_cast<const MachineWord*>(characters));
-    characters += chars_per_word;
+    UNSAFE_TODO(characters += chars_per_word);
   }
 
   // Process the remaining bytes.
-  while (characters < end)
-    all_char_bits |= static_cast<MachineWord>(*characters++);
+  while (characters < end) {
+    all_char_bits |= UNSAFE_TODO(static_cast<MachineWord>(*characters++));
+  }
 
   return !(all_char_bits & non_ascii_bit_mask);
 }
 
 template <bool (*Validator)(base_icu::UChar32)>
-inline bool DoIsStringUTF8(StringPiece str) {
+inline bool DoIsStringUTF8(std::string_view str) {
   const uint8_t* src = reinterpret_cast<const uint8_t*>(str.data());
   size_t src_len = str.length();
   size_t char_index = 0;
 
   while (char_index < src_len) {
     base_icu::UChar32 code_point;
-    CBU8_NEXT(src, char_index, src_len, code_point);
-    if (!Validator(code_point))
+    UNSAFE_TODO(CBU8_NEXT(src, char_index, src_len, code_point));
+    if (!Validator(code_point)) {
       return false;
+    }
   }
   return true;
 }
 
+// Lookup table for fast ASCII case-insensitive comparison.
+inline constexpr std::array<unsigned char, 256> kToLower = []() {
+  std::array<unsigned char, 256> table;
+  std::iota(table.begin(), table.end(), 0);
+  std::iota(table.begin() + size_t{'A'}, table.begin() + size_t{'Z'} + 1, 'a');
+  return table;
+}();
+
+inline constexpr auto lower = [](auto c) constexpr {
+  return kToLower[static_cast<unsigned char>(c)];
+};
+
 template <typename T, typename CharT = typename T::value_type>
-bool StartsWithT(T str, T search_for, CompareCase case_sensitivity) {
-  if (search_for.size() > str.size())
-    return false;
-
-  BasicStringPiece<CharT> source = str.substr(0, search_for.size());
-
-  switch (case_sensitivity) {
-    case CompareCase::SENSITIVE:
-      return source == search_for;
-
-    case CompareCase::INSENSITIVE_ASCII:
-      return std::equal(search_for.begin(), search_for.end(), source.begin(),
-                        CaseInsensitiveCompareASCII<CharT>());
-  }
+constexpr bool StartsWithT(T str, T search_for, CompareCase case_sensitivity) {
+  return case_sensitivity == CompareCase::SENSITIVE
+             ? str.starts_with(search_for)
+             : std::ranges::equal(str.substr(0, search_for.size()), search_for,
+                                  {}, lower, lower);
 }
 
 template <typename T, typename CharT = typename T::value_type>
-bool EndsWithT(T str, T search_for, CompareCase case_sensitivity) {
-  if (search_for.size() > str.size())
-    return false;
-
-  BasicStringPiece<CharT> source =
-      str.substr(str.size() - search_for.size(), search_for.size());
-
-  switch (case_sensitivity) {
-    case CompareCase::SENSITIVE:
-      return source == search_for;
-
-    case CompareCase::INSENSITIVE_ASCII:
-      return std::equal(source.begin(), source.end(), search_for.begin(),
-                        CaseInsensitiveCompareASCII<CharT>());
-  }
+constexpr bool EndsWithT(T str, T search_for, CompareCase case_sensitivity) {
+  return case_sensitivity == CompareCase::SENSITIVE
+             ? str.ends_with(search_for)
+             : (search_for.size() <= str.size() &&
+                std::ranges::equal(str.substr(str.size() - search_for.size()),
+                                   search_for, {}, lower, lower));
 }
 
 // A Matcher for DoReplaceMatchesAfterOffset() that matches substrings.
 template <class CharT>
 struct SubstringMatcher {
-  BasicStringPiece<CharT> find_this;
+  std::basic_string_view<CharT> find_this;
 
   size_t Find(const std::basic_string<CharT>& input, size_t pos) {
     return input.find(find_this.data(), pos, find_this.length());
@@ -270,7 +278,7 @@
 // A Matcher for DoReplaceMatchesAfterOffset() that matches single characters.
 template <class CharT>
 struct CharacterMatcher {
-  BasicStringPiece<CharT> find_any_of_these;
+  std::basic_string_view<CharT> find_any_of_these;
 
   size_t Find(const std::basic_string<CharT>& input, size_t pos) {
     return input.find_first_of(find_any_of_these.data(), pos,
@@ -301,13 +309,15 @@
   using CharTraits = std::char_traits<CharT>;
 
   const size_t find_length = matcher.MatchSize();
-  if (!find_length)
+  if (!find_length) {
     return false;
+  }
 
   // If the find string doesn't appear, there's nothing to do.
   size_t first_match = matcher.Find(*str, initial_offset);
-  if (first_match == std::basic_string<CharT>::npos)
+  if (first_match == std::basic_string<CharT>::npos) {
     return false;
+  }
 
   // If we're only replacing one instance, there's no need to do anything
   // complicated.
@@ -323,7 +333,8 @@
     auto* buffer = &((*str)[0]);
     for (size_t offset = first_match; offset != std::basic_string<CharT>::npos;
          offset = matcher.Find(*str, offset + replace_length)) {
-      CharTraits::copy(buffer + offset, replace_with.data(), replace_length);
+      CharTraits::copy(UNSAFE_TODO(buffer + offset), replace_with.data(),
+                       replace_length);
     }
     return true;
   }
@@ -373,8 +384,9 @@
 
         // A mid-loop test/break enables skipping the final Find() call; the
         // number of matches is known, so don't search past the last one.
-        if (!--num_matches)
+        if (!--num_matches) {
           break;
+        }
       }
 
       // Handle substring after the final match.
@@ -390,8 +402,9 @@
 
     // Big |expansion| factors (relative to |str_length|) require padding up to
     // |shift_dst|.
-    if (shift_dst > str_length)
+    if (shift_dst > str_length) {
       str->resize(shift_dst);
+    }
 
     str->replace(shift_dst, str_length - shift_src, *str, shift_src,
                  str_length - shift_src);
@@ -413,7 +426,7 @@
   size_t read_offset = first_match + expansion;
   do {
     if (replace_length) {
-      CharTraits::copy(buffer + write_offset, replace_with.data(),
+      CharTraits::copy(UNSAFE_TODO(buffer + write_offset), replace_with.data(),
                        replace_length);
       write_offset += replace_length;
     }
@@ -425,7 +438,8 @@
 
     size_t length = match - read_offset;
     if (length) {
-      CharTraits::move(buffer + write_offset, buffer + read_offset, length);
+      CharTraits::move(UNSAFE_TODO(buffer + write_offset),
+                       UNSAFE_TODO(buffer + read_offset), length);
       write_offset += length;
       read_offset += length;
     }
@@ -443,8 +457,9 @@
                    std::basic_string<CharT>* output) {
   // Commonly, this is called with output and input being the same string; in
   // that case, skip the copy.
-  if (input.data() != output->data() || input.size() != output->size())
+  if (input.data() != output->data() || input.size() != output->size()) {
     output->assign(input.data(), input.size());
+  }
 
   return DoReplaceMatchesAfterOffset(output, 0,
                                      MakeCharacterMatcher(find_any_of_these),
@@ -457,34 +472,36 @@
   GURL_DCHECK_GE(length_with_null, 1u);
   str->reserve(length_with_null);
   str->resize(length_with_null - 1);
-  return &((*str)[0]);
+  return str->data();
 }
 
 // Generic version for all JoinString overloads. |list_type| must be a sequence
 // (gurl_base::span or std::initializer_list) of strings/StringPieces (std::string,
-// std::u16string, StringPiece or StringPiece16). |CharT| is either char or
-// char16_t.
+// std::u16string, std::string_view or std::u16string_view). |CharT| is either
+// char or char16_t.
 template <typename list_type,
           typename T,
           typename CharT = typename T::value_type>
 static std::basic_string<CharT> JoinStringT(list_type parts, T sep) {
-  if (std::empty(parts))
+  if (std::empty(parts)) {
     return std::basic_string<CharT>();
+  }
 
   // Pre-allocate the eventual size of the string. Start with the size of all of
   // the separators (note that this *assumes* parts.size() > 0).
   size_t total_size = (parts.size() - 1) * sep.size();
-  for (const auto& part : parts)
+  for (const auto& part : parts) {
     total_size += part.size();
+  }
   std::basic_string<CharT> result;
   result.reserve(total_size);
 
   auto iter = parts.begin();
-  GURL_DCHECK(iter != parts.end());
+  GURL_CHECK(iter != parts.end());
   result.append(*iter);
-  ++iter;
+  UNSAFE_TODO(++iter);
 
-  for (; iter != parts.end(); ++iter) {
+  for (; iter != parts.end(); UNSAFE_TODO(++iter)) {
     result.append(sep);
     result.append(*iter);
   }
@@ -495,6 +512,14 @@
   return result;
 }
 
+// StringViewLike will match both std::basic_string_view<Char> and
+// std::basic_string<Char>. It ensures that the type satisfies the requirements
+// to be passed to std::basic_string::append().
+template <typename T, typename CharT, typename TBaseT = std::remove_cvref_t<T>>
+concept StringOrStringView =
+    std::same_as<TBaseT, std::basic_string<CharT>> ||
+    std::same_as<TBaseT, std::basic_string_view<CharT>>;
+
 // Replaces placeholders in `format_string` with values from `subst`.
 // * `placeholder_prefix`: Allows using a specific character as the placeholder
 // prefix. `gurl_base::ReplaceStringPlaceholders` uses '$'.
@@ -507,29 +532,37 @@
 //   instance, with `%` as the `placeholder_prefix`: %%->%, %%%%->%%, etc.
 // * `is_strict_mode`:
 //   * If this parameter is `true`, error handling is stricter. The function
-//   returns `absl::nullopt` if:
+//   returns `std::nullopt` if:
 //     * a placeholder %N is encountered where N > substitutions.size().
 //     * a literal `%` is not escaped with a `%`.
-template <typename T, typename CharT = typename T::value_type>
-absl::optional<std::basic_string<CharT>> DoReplaceStringPlaceholders(
+template <typename T, typename Range, typename CharT = typename T::value_type>
+  requires(std::ranges::random_access_range<Range> &&
+           std::ranges::sized_range<Range> &&
+           requires(Range&& range, size_t index) {
+             { range.at(index) } -> StringOrStringView<CharT>;
+           })
+std::optional<std::basic_string<CharT>> DoReplaceStringPlaceholders(
     T format_string,
-    const std::vector<std::basic_string<CharT>>& subst,
+    Range&& subst,
     const CharT placeholder_prefix,
     const bool should_escape_multiple_placeholder_prefixes,
     const bool is_strict_mode,
     std::vector<size_t>* offsets) {
-  size_t substitutions = subst.size();
+  const size_t substitutions = subst.size();
   GURL_DCHECK_LT(substitutions, 10U);
 
   size_t sub_length = 0;
   for (const auto& cur : subst) {
-    sub_length += cur.length();
+    sub_length += cur.size();
   }
 
   std::basic_string<CharT> formatted;
   formatted.reserve(format_string.length() + sub_length);
 
   std::vector<ReplacementOffset> r_offsets;
+  if (offsets) {
+    r_offsets.reserve(substitutions);
+  }
   for (auto i = format_string.begin(); i != format_string.end(); ++i) {
     if (placeholder_prefix == *i) {
       if (i + 1 != format_string.end()) {
@@ -547,7 +580,7 @@
               GURL_DLOG(ERROR) << "Invalid placeholder after placeholder prefix: "
                           << std::basic_string<CharT>(1, placeholder_prefix)
                           << std::basic_string<CharT>(1, *i);
-              return absl::nullopt;
+              return std::nullopt;
             }
 
             continue;
@@ -555,21 +588,21 @@
           const size_t index = static_cast<size_t>(*i - '1');
           if (offsets) {
             ReplacementOffset r_offset(index, formatted.size());
-            r_offsets.insert(
-                ranges::upper_bound(r_offsets, r_offset, &CompareParameter),
-                r_offset);
+            r_offsets.insert(std::ranges::upper_bound(r_offsets, r_offset,
+                                                      &CompareParameter),
+                             r_offset);
           }
           if (index < substitutions) {
             formatted.append(subst.at(index));
           } else if (is_strict_mode) {
             GURL_DLOG(ERROR) << "index out of range: " << index << ": "
                         << substitutions;
-            return absl::nullopt;
+            return std::nullopt;
           }
         }
       } else if (is_strict_mode) {
         GURL_DLOG(ERROR) << "unexpected placeholder prefix at end of string";
-        return absl::nullopt;
+        return std::nullopt;
       }
     } else {
       formatted.push_back(*i);
@@ -588,20 +621,23 @@
 //   ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c
 
 template <typename CHAR>
-size_t lcpyT(CHAR* dst, const CHAR* src, size_t dst_size) {
-  for (size_t i = 0; i < dst_size; ++i) {
-    if ((dst[i] = src[i]) == 0)  // We hit and copied the terminating NULL.
-      return i;
+size_t lcpyT(span<CHAR> dst, std::basic_string_view<CHAR> src) {
+  size_t i = 0;
+
+  const size_t dst_size = dst.size();
+  for (; i + 1u < dst_size; ++i) {
+    if (i == src.size()) {
+      break;
+    }
+    dst[i] = src[i];
   }
 
-  // We were left off at dst_size.  We over copied 1 byte.  Null terminate.
-  if (dst_size != 0)
-    dst[dst_size - 1] = 0;
+  // Write the terminating NUL.
+  if (!dst.empty()) {
+    dst[i] = 0;
+  }
 
-  // Count the rest of the |src|, and return it's length in characters.
-  while (src[dst_size])
-    ++dst_size;
-  return dst_size;
+  return src.size();
 }
 
 }  // namespace gurl_base::internal
diff --git a/base/strings/string_util_internal.h b/base/strings/string_util_internal.h
index c4802d1..a4e3fd7 100644
--- a/base/strings/string_util_internal.h
+++ b/base/strings/string_util_internal.h
@@ -5,54 +5,58 @@
 #ifndef BASE_STRINGS_STRING_UTIL_INTERNAL_H_
 #define BASE_STRINGS_STRING_UTIL_INTERNAL_H_
 
+#include <algorithm>
+#include <concepts>
+#include <string_view>
 #include <type_traits>
 
-#include "base/ranges/algorithm.h"
-#include "base/strings/string_piece.h"
-
 namespace gurl_base::internal {
 
 // ASCII-specific tolower.  The standard library's tolower is locale sensitive,
 // so we don't want to use it here.
-template <typename CharT,
-          typename = std::enable_if_t<std::is_integral_v<CharT>>>
+template <typename CharT>
+  requires(std::integral<CharT>)
 constexpr CharT ToLowerASCII(CharT c) {
   return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
 }
 
 template <typename T>
+  requires(std::integral<typename T::value_type>)
 constexpr int CompareCaseInsensitiveASCIIT(T a, T b) {
   // Find the first characters that aren't equal and compare them.  If the end
   // of one of the strings is found before a nonequal character, the lengths
   // of the strings are compared. Compare using the unsigned type so the sort
   // order is independent of the signedness of `char`.
-  static_assert(std::is_integral_v<typename T::value_type>);
   using UCharT = std::make_unsigned_t<typename T::value_type>;
   size_t i = 0;
   while (i < a.length() && i < b.length()) {
     UCharT lower_a = static_cast<UCharT>(ToLowerASCII(a[i]));
     UCharT lower_b = static_cast<UCharT>(ToLowerASCII(b[i]));
-    if (lower_a < lower_b)
+    if (lower_a < lower_b) {
       return -1;
-    if (lower_a > lower_b)
+    }
+    if (lower_a > lower_b) {
       return 1;
+    }
     i++;
   }
 
   // End of one string hit before finding a different character. Expect the
   // common case to be "strings equal" at this point so check that first.
-  if (a.length() == b.length())
+  if (a.length() == b.length()) {
     return 0;
+  }
 
-  if (a.length() < b.length())
+  if (a.length() < b.length()) {
     return -1;
+  }
   return 1;
 }
 
 template <typename CharT, typename CharU>
-inline bool EqualsCaseInsensitiveASCIIT(BasicStringPiece<CharT> a,
-                                        BasicStringPiece<CharU> b) {
-  return ranges::equal(a, b, [](auto lhs, auto rhs) {
+inline bool EqualsCaseInsensitiveASCIIT(std::basic_string_view<CharT> a,
+                                        std::basic_string_view<CharU> b) {
+  return std::ranges::equal(a, b, [](auto lhs, auto rhs) {
     return ToLowerASCII(lhs) == ToLowerASCII(rhs);
   });
 }
diff --git a/base/strings/string_util_perftest.cc b/base/strings/string_util_perftest.cc
index e2df9f3..f09c5a3 100644
--- a/base/strings/string_util_perftest.cc
+++ b/base/strings/string_util_perftest.cc
@@ -15,12 +15,14 @@
 template <typename String>
 void MeasureIsStringASCII(size_t str_length, size_t non_ascii_pos) {
   String str(str_length, 'A');
-  if (non_ascii_pos < str_length)
+  if (non_ascii_pos < str_length) {
     str[non_ascii_pos] = '\xAF';
+  }
 
   TimeTicks t0 = TimeTicks::Now();
-  for (size_t i = 0; i < 10000000; ++i)
+  for (size_t i = 0; i < 10000000; ++i) {
     IsStringASCII(str);
+  }
   TimeDelta time = TimeTicks::Now() - t0;
   printf(
       "char-size:\t%zu\tlength:\t%zu\tnon-ascii-pos:\t%zu\ttime-ms:\t%" PRIu64
diff --git a/base/strings/string_util_posix.h b/base/strings/string_util_posix.h
index 49351f9..07ab7cf 100644
--- a/base/strings/string_util_posix.h
+++ b/base/strings/string_util_posix.h
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
+#pragma allow_unsafe_libc_calls
+#endif
+
 #ifndef BASE_STRINGS_STRING_UTIL_POSIX_H_
 #define BASE_STRINGS_STRING_UTIL_POSIX_H_
 
@@ -12,7 +17,6 @@
 #include <wchar.h>
 
 #include "polyfills/base/check.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 
@@ -22,13 +26,22 @@
   return ::strdup(str);
 }
 
-inline int vsnprintf(char* buffer, size_t size,
-                     const char* format, va_list arguments) {
+inline int vsnprintf(char* buffer,
+                     size_t size,
+                     const char* format,
+                     va_list arguments) {
   return ::vsnprintf(buffer, size, format, arguments);
 }
 
-inline int vswprintf(wchar_t* buffer, size_t size,
-                     const wchar_t* format, va_list arguments) {
+// TODO(crbug.com/40284755): implement spanified version, or just remove
+// this entirely as it has ~no non-test uses.
+// inline int vswprintf(gurl_base::span<wchar_t> buffer,
+//                      const wchar_t* format,
+//                      va_list arguments);
+inline int vswprintf(wchar_t* buffer,
+                     size_t size,
+                     const wchar_t* format,
+                     va_list arguments) {
   GURL_DCHECK(IsWprintfFormatPortable(format));
   return ::vswprintf(buffer, size, format, arguments);
 }
diff --git a/base/strings/string_util_unittest.cc b/base/strings/string_util_unittest.cc
index 12c4c8c..26dec72 100644
--- a/base/strings/string_util_unittest.cc
+++ b/base/strings/string_util_unittest.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "base/strings/string_util.h"
 
 #include <math.h>
@@ -10,12 +15,14 @@
 #include <stdint.h>
 
 #include <algorithm>
+#include <array>
+#include <ranges>
 #include <string>
 #include <string_view>
 #include <type_traits>
 
 #include "base/bits.h"
-#include "base/strings/string_piece.h"
+#include "base/strings/utf_ostream_operators.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -62,16 +69,7 @@
     {"\t\rTest String\n", TRIM_ALL, "Test String", TRIM_ALL},
 };
 
-// Helper used to test TruncateUTF8ToByteSize.
-bool Truncated(const std::string& input,
-               const size_t byte_size,
-               std::string* output) {
-    size_t prev = input.length();
-    TruncateUTF8ToByteSize(input, byte_size, output);
-    return prev != output->length();
-}
-
-using TestFunction = bool (*)(StringPiece str);
+using TestFunction = bool (*)(std::string_view str);
 
 // Helper used to test IsStringUTF8[AllowingNoncharacters].
 void TestStructurallyValidUtf8(TestFunction fn) {
@@ -194,162 +192,140 @@
 }
 
 TEST(StringUtilTest, TruncateUTF8ToByteSize) {
+  // Just a few tests of the version which takes an output parameter. It's
+  // implemented in terms `TruncateUTF8ToByteSize()` which returns a string
+  // view.
   std::string output;
+  TruncateUTF8ToByteSize("test", 3, &output);
+  EXPECT_EQ("tes", output);
+  TruncateUTF8ToByteSize("test", 0, &output);
+  EXPECT_EQ("", output);
+  TruncateUTF8ToByteSize("", 3, &output);
+  EXPECT_EQ("", output);
 
   // Empty strings and invalid byte_size arguments
-  EXPECT_FALSE(Truncated(std::string(), 0, &output));
-  EXPECT_EQ(output, "");
-  EXPECT_TRUE(Truncated("\xe1\x80\xbf", 0, &output));
-  EXPECT_EQ(output, "");
-  EXPECT_FALSE(Truncated("\xe1\x80\xbf", static_cast<size_t>(-1), &output));
-  EXPECT_FALSE(Truncated("\xe1\x80\xbf", 4, &output));
+  EXPECT_EQ("", TruncateUTF8ToByteSize(std::string(), 0));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xe1\x80\xbf", 0));
+  EXPECT_EQ("\xe1\x80\xbf",
+            TruncateUTF8ToByteSize("\xe1\x80\xbf", static_cast<size_t>(-1)));
+  EXPECT_EQ("\xe1\x80\xbf", TruncateUTF8ToByteSize("\xe1\x80\xbf", 4));
 
   // Testing the truncation of valid UTF8 correctly
-  EXPECT_TRUE(Truncated("abc", 2, &output));
-  EXPECT_EQ(output, "ab");
-  EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 2, &output));
-  EXPECT_EQ(output.compare("\xc2\x81"), 0);
-  EXPECT_TRUE(Truncated("\xc2\x81\xc2\x81", 3, &output));
-  EXPECT_EQ(output.compare("\xc2\x81"), 0);
-  EXPECT_FALSE(Truncated("\xc2\x81\xc2\x81", 4, &output));
-  EXPECT_EQ(output.compare("\xc2\x81\xc2\x81"), 0);
+  EXPECT_EQ("ab", TruncateUTF8ToByteSize("abc", 2));
+  EXPECT_EQ("\xc2\x81", TruncateUTF8ToByteSize("\xc2\x81\xc2\x81", 2));
+  EXPECT_EQ("\xc2\x81", TruncateUTF8ToByteSize("\xc2\x81\xc2\x81", 3));
+  EXPECT_EQ("\xc2\x81\xc2\x81", TruncateUTF8ToByteSize("\xc2\x81\xc2\x81", 4));
 
   {
     const char array[] = "\x00\x00\xc2\x81\xc2\x81";
     const std::string array_string(array, std::size(array));
-    EXPECT_TRUE(Truncated(array_string, 4, &output));
-    EXPECT_EQ(output.compare(std::string("\x00\x00\xc2\x81", 4)), 0);
+    EXPECT_EQ(std::string_view("\x00\x00\xc2\x81", 4),
+              TruncateUTF8ToByteSize(array_string, 4));
   }
 
   {
     const char array[] = "\x00\xc2\x81\xc2\x81";
     const std::string array_string(array, std::size(array));
-    EXPECT_TRUE(Truncated(array_string, 4, &output));
-    EXPECT_EQ(output.compare(std::string("\x00\xc2\x81", 3)), 0);
+    EXPECT_EQ(std::string_view("\x00\xc2\x81", 3),
+              TruncateUTF8ToByteSize(array_string, 4));
   }
 
   // Testing invalid UTF8
-  EXPECT_TRUE(Truncated("\xed\xa0\x80\xed\xbf\xbf", 6, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xed\xa0\x8f", 3, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xed\xbf\xbf", 3, &output));
-  EXPECT_EQ(output.compare(""), 0);
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xed\xa0\x80\xed\xbf\xbf", 6));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xed\xa0\x8f", 3));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xed\xbf\xbf", 3));
 
   // Testing invalid UTF8 mixed with valid UTF8
-  EXPECT_FALSE(Truncated("\xe1\x80\xbf", 3, &output));
-  EXPECT_EQ(output.compare("\xe1\x80\xbf"), 0);
-  EXPECT_FALSE(Truncated("\xf1\x80\xa0\xbf", 4, &output));
-  EXPECT_EQ(output.compare("\xf1\x80\xa0\xbf"), 0);
-  EXPECT_FALSE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf",
-              10, &output));
-  EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf"), 0);
-  EXPECT_TRUE(Truncated("a\xc2\x81\xe1\x80\xbf\xf1""a""\x80\xa0",
-              10, &output));
-  EXPECT_EQ(output.compare("a\xc2\x81\xe1\x80\xbf\xf1""a"), 0);
-  EXPECT_FALSE(Truncated("\xef\xbb\xbf" "abc", 6, &output));
-  EXPECT_EQ(output.compare("\xef\xbb\xbf" "abc"), 0);
+  EXPECT_EQ("\xe1\x80\xbf", TruncateUTF8ToByteSize("\xe1\x80\xbf", 3));
+  EXPECT_EQ("\xf1\x80\xa0\xbf", TruncateUTF8ToByteSize("\xf1\x80\xa0\xbf", 4));
+  EXPECT_EQ(
+      "a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf",
+      TruncateUTF8ToByteSize("a\xc2\x81\xe1\x80\xbf\xf1\x80\xa0\xbf", 10));
+  EXPECT_EQ(
+      "a\xc2\x81\xe1\x80\xbf\xf1"
+      "a",
+      TruncateUTF8ToByteSize("a\xc2\x81\xe1\x80\xbf\xf1"
+                             "a"
+                             "\x80\xa0",
+                             10));
+  EXPECT_EQ(
+      "\xef\xbb\xbf"
+      "abc",
+      TruncateUTF8ToByteSize("\xef\xbb\xbf"
+                             "abc",
+                             6));
 
   // Overlong sequences
-  EXPECT_TRUE(Truncated("\xc0\x80", 2, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xc1\x80\xc1\x81", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xe0\x80\x80", 3, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xe0\x82\x80", 3, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xe0\x9f\xbf", 3, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xf0\x80\x80\x8D", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xf0\x80\x82\x91", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xf0\x80\xa0\x80", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xf0\x8f\xbb\xbf", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xf8\x80\x80\x80\xbf", 5, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xfc\x80\x80\x80\xa0\xa5", 6, &output));
-  EXPECT_EQ(output.compare(""), 0);
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xc0\x80", 2));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xc1\x80\xc1\x81", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xe0\x80\x80", 3));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xe0\x82\x80", 3));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xe0\x9f\xbf", 3));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf0\x80\x80\x8D", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf0\x80\x82\x91", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf0\x80\xa0\x80", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf0\x8f\xbb\xbf", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf8\x80\x80\x80\xbf", 5));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xfc\x80\x80\x80\xa0\xa5", 6));
 
   // Beyond U+10FFFF (the upper limit of Unicode codespace)
-  EXPECT_TRUE(Truncated("\xf4\x90\x80\x80", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xf8\xa0\xbf\x80\xbf", 5, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xfc\x9c\xbf\x80\xbf\x80", 6, &output));
-  EXPECT_EQ(output.compare(""), 0);
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf4\x90\x80\x80", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf8\xa0\xbf\x80\xbf", 5));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xfc\x9c\xbf\x80\xbf\x80", 6));
 
   // BOMs in UTF-16(BE|LE) and UTF-32(BE|LE)
-  EXPECT_TRUE(Truncated("\xfe\xff", 2, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xff\xfe", 2, &output));
-  EXPECT_EQ(output.compare(""), 0);
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xfe\xff", 2));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xff\xfe", 2));
 
   {
     const char array[] = "\x00\x00\xfe\xff";
     const std::string array_string(array, std::size(array));
-    EXPECT_TRUE(Truncated(array_string, 4, &output));
-    EXPECT_EQ(output.compare(std::string("\x00\x00", 2)), 0);
+    EXPECT_EQ(std::string("\x00\x00", 2),
+              TruncateUTF8ToByteSize(array_string, 4));
   }
 
   // Variants on the previous test
   {
     const char array[] = "\xff\xfe\x00\x00";
     const std::string array_string(array, 4);
-    EXPECT_FALSE(Truncated(array_string, 4, &output));
-    EXPECT_EQ(output.compare(std::string("\xff\xfe\x00\x00", 4)), 0);
+    EXPECT_EQ(std::string_view("\xff\xfe\x00\x00", 4),
+              TruncateUTF8ToByteSize(array_string, 4));
   }
   {
     const char array[] = "\xff\x00\x00\xfe";
     const std::string array_string(array, std::size(array));
-    EXPECT_TRUE(Truncated(array_string, 4, &output));
-    EXPECT_EQ(output.compare(std::string("\xff\x00\x00", 3)), 0);
+    EXPECT_EQ(std::string_view("\xff\x00\x00", 3),
+              TruncateUTF8ToByteSize(array_string, 4));
   }
 
   // Non-characters : U+xxFFF[EF] where xx is 0x00 through 0x10 and <FDD0,FDEF>
-  EXPECT_TRUE(Truncated("\xef\xbf\xbe", 3, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xf0\x8f\xbf\xbe", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xf3\xbf\xbf\xbf", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xef\xb7\x90", 3, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_TRUE(Truncated("\xef\xb7\xaf", 3, &output));
-  EXPECT_EQ(output.compare(""), 0);
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xef\xbf\xbe", 3));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf0\x8f\xbf\xbe", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xf3\xbf\xbf\xbf", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xef\xb7\x90", 3));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xef\xb7\xaf", 3));
 
   // Strings in legacy encodings that are valid in UTF-8, but
   // are invalid as UTF-8 in real data.
-  EXPECT_TRUE(Truncated("caf\xe9", 4, &output));
-  EXPECT_EQ(output.compare("caf"), 0);
-  EXPECT_TRUE(Truncated("\xb0\xa1\xb0\xa2", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
-  EXPECT_FALSE(Truncated("\xa7\x41\xa6\x6e", 4, &output));
-  EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0);
-  EXPECT_TRUE(Truncated("\xa7\x41\xa6\x6e\xd9\xee\xe4\xee", 7,
-              &output));
-  EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0);
-
-  // Testing using the same string as input and output.
-  EXPECT_FALSE(Truncated(output, 4, &output));
-  EXPECT_EQ(output.compare("\xa7\x41\xa6\x6e"), 0);
-  EXPECT_TRUE(Truncated(output, 3, &output));
-  EXPECT_EQ(output.compare("\xa7\x41"), 0);
+  EXPECT_EQ("caf", TruncateUTF8ToByteSize("caf\xe9", 4));
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xb0\xa1\xb0\xa2", 4));
+  EXPECT_EQ("\xa7\x41\xa6\x6e", TruncateUTF8ToByteSize("\xa7\x41\xa6\x6e", 4));
+  EXPECT_EQ("\xa7\x41\xa6\x6e",
+            TruncateUTF8ToByteSize("\xa7\x41\xa6\x6e\xd9\xee\xe4\xee", 7));
 
   // "abc" with U+201[CD] in windows-125[0-8]
-  EXPECT_TRUE(Truncated("\x93" "abc\x94", 5, &output));
-  EXPECT_EQ(output.compare("\x93" "abc"), 0);
+  EXPECT_EQ(
+      "\x93"
+      "abc",
+      TruncateUTF8ToByteSize("\x93"
+                             "abc\x94",
+                             5));
 
   // U+0639 U+064E U+0644 U+064E in ISO-8859-6
-  EXPECT_TRUE(Truncated("\xd9\xee\xe4\xee", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xd9\xee\xe4\xee", 4));
 
   // U+03B3 U+03B5 U+03B9 U+03AC in ISO-8859-7
-  EXPECT_TRUE(Truncated("\xe3\xe5\xe9\xdC", 4, &output));
-  EXPECT_EQ(output.compare(""), 0);
+  EXPECT_EQ("", TruncateUTF8ToByteSize("\xe3\xe5\xe9\xdC", 4));
 }
 
 #if defined(WCHAR_T_IS_16_BIT)
@@ -373,7 +349,7 @@
   static_assert(std::is_same_v<const wchar_t*, decltype(as_wcstr(ro_str))>, "");
   EXPECT_EQ(static_cast<const void*>(ro_str.data()), as_wcstr(ro_str));
 
-  StringPiece16 piece = ro_buffer;
+  std::u16string_view piece = ro_buffer;
   static_assert(std::is_same_v<const wchar_t*, decltype(as_wcstr(piece))>, "");
   EXPECT_EQ(static_cast<const void*>(piece.data()), as_wcstr(piece));
 }
@@ -410,9 +386,8 @@
 TEST(StringUtilTest, TrimWhitespace) {
   std::u16string output;  // Allow contents to carry over to next testcase
   for (const auto& value : trim_cases) {
-    EXPECT_EQ(value.return_value,
-              TrimWhitespace(WideToUTF16(value.input), value.positions,
-                             &output));
+    EXPECT_EQ(value.return_value, TrimWhitespace(WideToUTF16(value.input),
+                                                 value.positions, &output));
     EXPECT_EQ(WideToUTF16(value.output), output);
   }
 
@@ -439,25 +414,25 @@
   const bool trim;
   const wchar_t* output;
 } collapse_cases[] = {
-  {L" Google Video ", false, L"Google Video"},
-  {L"Google Video", false, L"Google Video"},
-  {L"", false, L""},
-  {L"  ", false, L""},
-  {L"\t\rTest String\n", false, L"Test String"},
-  {L"\x2002Test String\x00A0\x3000", false, L"Test String"},
-  {L"    Test     \n  \t String    ", false, L"Test String"},
-  {L"\x2002Test\x1680 \x2028 \tString\x00A0\x3000", false, L"Test String"},
-  {L"   Test String", false, L"Test String"},
-  {L"Test String    ", false, L"Test String"},
-  {L"Test String", false, L"Test String"},
-  {L"", true, L""},
-  {L"\n", true, L""},
-  {L"  \r  ", true, L""},
-  {L"\nFoo", true, L"Foo"},
-  {L"\r  Foo  ", true, L"Foo"},
-  {L" Foo bar ", true, L"Foo bar"},
-  {L"  \tFoo  bar  \n", true, L"Foo bar"},
-  {L" a \r b\n c \r\n d \t\re \t f \n ", true, L"abcde f"},
+    {L" Google Video ", false, L"Google Video"},
+    {L"Google Video", false, L"Google Video"},
+    {L"", false, L""},
+    {L"  ", false, L""},
+    {L"\t\rTest String\n", false, L"Test String"},
+    {L"\x2002Test String\x00A0\x3000", false, L"Test String"},
+    {L"    Test     \n  \t String    ", false, L"Test String"},
+    {L"\x2002Test\x1680 \x2028 \tString\x00A0\x3000", false, L"Test String"},
+    {L"   Test String", false, L"Test String"},
+    {L"Test String    ", false, L"Test String"},
+    {L"Test String", false, L"Test String"},
+    {L"", true, L""},
+    {L"\n", true, L""},
+    {L"  \r  ", true, L""},
+    {L"\nFoo", true, L"Foo"},
+    {L"\r  Foo  ", true, L"Foo"},
+    {L" Foo bar ", true, L"Foo bar"},
+    {L"  \tFoo  bar  \n", true, L"Foo bar"},
+    {L" a \r b\n c \r\n d \t\re \t f \n ", true, L"abcde f"},
 };
 
 TEST(StringUtilTest, CollapseWhitespace) {
@@ -517,12 +492,13 @@
 }
 
 TEST(StringUtilTest, IsStringASCII) {
-  static char char_ascii[] =
-      "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
-  static char16_t char16_ascii[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
-                                    '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', '0',
-                                    '1', '2', '3', '4', '5', '6', '7', '8', '9',
-                                    '0', 'A', 'B', 'C', 'D', 'E', 'F', 0};
+  static std::array<char, 49> char_ascii{
+      "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"};
+  static auto char16_ascii = std::to_array<char16_t>({
+      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A',
+      'B', 'C', 'D', 'E', 'F', '0', '1', '2', '3', '4', '5', '6',
+      '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 0,
+  });
   static std::wstring wchar_ascii(
       L"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF");
 
@@ -535,10 +511,12 @@
     for (size_t offset = 0; offset < 8; ++offset) {
       for (size_t len = 0, max_len = string_length - offset; len < max_len;
            ++len) {
-        EXPECT_TRUE(IsStringASCII(StringPiece(char_ascii + offset, len)));
+        EXPECT_TRUE(IsStringASCII(std::string_view(
+            gurl_base::span(char_ascii).subspan(offset).data(), len)));
         for (size_t char_pos = offset; char_pos < len; ++char_pos) {
           char_ascii[char_pos] |= '\x80';
-          EXPECT_FALSE(IsStringASCII(StringPiece(char_ascii + offset, len)));
+          EXPECT_FALSE(IsStringASCII(std::string_view(
+              gurl_base::span(char_ascii).subspan(offset).data(), len)));
           char_ascii[char_pos] &= ~'\x80';
         }
       }
@@ -550,16 +528,17 @@
     for (size_t offset = 0; offset < 4; ++offset) {
       for (size_t len = 0, max_len = string_length - offset; len < max_len;
            ++len) {
-        EXPECT_TRUE(IsStringASCII(StringPiece16(char16_ascii + offset, len)));
+        EXPECT_TRUE(IsStringASCII(std::u16string_view(
+            gurl_base::span(char16_ascii).subspan(offset).data(), len)));
         for (size_t char_pos = offset; char_pos < len; ++char_pos) {
           char16_ascii[char_pos] |= 0x80;
-          EXPECT_FALSE(
-              IsStringASCII(StringPiece16(char16_ascii + offset, len)));
+          EXPECT_FALSE(IsStringASCII(std::u16string_view(
+              gurl_base::span(char16_ascii).subspan(offset).data(), len)));
           char16_ascii[char_pos] &= ~0x80;
           // Also test when the upper half is non-zero.
           char16_ascii[char_pos] |= 0x100;
-          EXPECT_FALSE(
-              IsStringASCII(StringPiece16(char16_ascii + offset, len)));
+          EXPECT_FALSE(IsStringASCII(std::u16string_view(
+              gurl_base::span(char16_ascii).subspan(offset).data(), len)));
           char16_ascii[char_pos] &= ~0x100;
         }
       }
@@ -588,17 +567,11 @@
 }
 
 TEST(StringUtilTest, ConvertASCII) {
-  static const char* const char_cases[] = {
-    "Google Video",
-    "Hello, world\n",
-    "0123ABCDwxyz \a\b\t\r\n!+,.~"
-  };
+  static const auto char_cases = std::to_array<const char*>(
+      {"Google Video", "Hello, world\n", "0123ABCDwxyz \a\b\t\r\n!+,.~"});
 
-  static const wchar_t* const wchar_cases[] = {
-    L"Google Video",
-    L"Hello, world\n",
-    L"0123ABCDwxyz \a\b\t\r\n!+,.~"
-  };
+  static const auto wchar_cases = std::to_array<const wchar_t*>(
+      {L"Google Video", L"Hello, world\n", L"0123ABCDwxyz \a\b\t\r\n!+,.~"});
 
   for (size_t i = 0; i < std::size(char_cases); ++i) {
     EXPECT_TRUE(IsStringASCII(char_cases[i]));
@@ -666,46 +639,13 @@
   EXPECT_EQ(u'\x00e4', ToUpperASCII(u'\x00e4'));
 }
 
-TEST(StringUtilTest, FormatBytesUnlocalized) {
-  static const struct {
-    int64_t bytes;
-    const char* expected;
-  } cases[] = {
-      // Expected behavior: we show one post-decimal digit when we have
-      // under two pre-decimal digits, except in cases where it makes no
-      // sense (zero or bytes).
-      // Since we switch units once we cross the 1000 mark, this keeps
-      // the display of file sizes or bytes consistently around three
-      // digits.
-      {0, "0 B"},
-      {512, "512 B"},
-      {1024 * 1024, "1.0 MB"},
-      {1024 * 1024 * 1024, "1.0 GB"},
-      {10LL * 1024 * 1024 * 1024, "10.0 GB"},
-      {99LL * 1024 * 1024 * 1024, "99.0 GB"},
-      {105LL * 1024 * 1024 * 1024, "105 GB"},
-      {105LL * 1024 * 1024 * 1024 + 500LL * 1024 * 1024, "105 GB"},
-      {~(bits::LeftmostBit<int64_t>()), "8192 PB"},
-
-      {99 * 1024 + 103, "99.1 kB"},
-      {1024 * 1024 + 103, "1.0 MB"},
-      {1024 * 1024 + 205 * 1024, "1.2 MB"},
-      {1024 * 1024 * 1024 + (927 * 1024 * 1024), "1.9 GB"},
-      {10LL * 1024 * 1024 * 1024, "10.0 GB"},
-      {100LL * 1024 * 1024 * 1024, "100 GB"},
-  };
-
-  for (const auto& i : cases) {
-    EXPECT_EQ(ASCIIToUTF16(i.expected), FormatBytesUnlocalized(i.bytes));
-  }
-}
 TEST(StringUtilTest, ReplaceSubstringsAfterOffset) {
   static const struct {
-    StringPiece str;
+    std::string_view str;
     size_t start_offset;
-    StringPiece find_this;
-    StringPiece replace_with;
-    StringPiece expected;
+    std::string_view find_this;
+    std::string_view replace_with;
+    std::string_view expected;
   } cases[] = {
       {"aaa", 0, "", "b", "aaa"},
       {"aaa", 1, "", "b", "aaa"},
@@ -776,17 +716,17 @@
     const char* replace_with;
     const char* expected;
   } cases[] = {
-    {"aaa", 0, "a", "b", "baa"},
-    {"abb", 0, "ab", "a", "ab"},
-    {"Removing some substrings inging", 0, "ing", "",
-      "Remov some substrings inging"},
-    {"Not found", 0, "x", "0", "Not found"},
-    {"Not found again", 5, "x", "0", "Not found again"},
-    {" Making it much longer ", 0, " ", "Four score and seven years ago",
-     "Four score and seven years agoMaking it much longer "},
-    {"Invalid offset", 9999, "t", "foobar", "Invalid offset"},
-    {"Replace me only me once", 4, "me ", "", "Replace only me once"},
-    {"abababab", 2, "ab", "c", "abcabab"},
+      {"aaa", 0, "a", "b", "baa"},
+      {"abb", 0, "ab", "a", "ab"},
+      {"Removing some substrings inging", 0, "ing", "",
+       "Remov some substrings inging"},
+      {"Not found", 0, "x", "0", "Not found"},
+      {"Not found again", 5, "x", "0", "Not found again"},
+      {" Making it much longer ", 0, " ", "Four score and seven years ago",
+       "Four score and seven years agoMaking it much longer "},
+      {"Invalid offset", 9999, "t", "foobar", "Invalid offset"},
+      {"Replace me only me once", 4, "me ", "", "Replace only me once"},
+      {"abababab", 2, "ab", "c", "abcabab"},
   };
 
   for (const auto& i : cases) {
@@ -830,7 +770,7 @@
   std::vector<std::string> parts;
   EXPECT_EQ(std::string(), JoinString(parts, separator));
 
-  parts.push_back(std::string());
+  parts.emplace_back();
   EXPECT_EQ(std::string(), JoinString(parts, separator));
   parts.clear();
 
@@ -841,7 +781,7 @@
   parts.push_back("c");
   EXPECT_EQ("a, b, c", JoinString(parts, separator));
 
-  parts.push_back(std::string());
+  parts.emplace_back();
   EXPECT_EQ("a, b, c, ", JoinString(parts, separator));
   parts.push_back(" ");
   EXPECT_EQ("a|b|c|| ", JoinString(parts, "|"));
@@ -852,7 +792,7 @@
   std::vector<std::u16string> parts;
   EXPECT_EQ(std::u16string(), JoinString(parts, separator));
 
-  parts.push_back(std::u16string());
+  parts.emplace_back();
   EXPECT_EQ(std::u16string(), JoinString(parts, separator));
   parts.clear();
 
@@ -871,11 +811,11 @@
 
 TEST(StringUtilTest, JoinStringPiece) {
   std::string separator(", ");
-  std::vector<StringPiece> parts;
+  std::vector<std::string_view> parts;
   EXPECT_EQ(std::string(), JoinString(parts, separator));
 
   // Test empty first part (https://crbug.com/698073).
-  parts.push_back(StringPiece());
+  parts.emplace_back();
   EXPECT_EQ(std::string(), JoinString(parts, separator));
   parts.clear();
 
@@ -886,7 +826,7 @@
   parts.push_back("c");
   EXPECT_EQ("a, b, c", JoinString(parts, separator));
 
-  parts.push_back(StringPiece());
+  parts.emplace_back();
   EXPECT_EQ("a, b, c, ", JoinString(parts, separator));
   parts.push_back(" ");
   EXPECT_EQ("a|b|c|| ", JoinString(parts, "|"));
@@ -894,11 +834,11 @@
 
 TEST(StringUtilTest, JoinStringPiece16) {
   std::u16string separator = u", ";
-  std::vector<StringPiece16> parts;
+  std::vector<std::u16string_view> parts;
   EXPECT_EQ(std::u16string(), JoinString(parts, separator));
 
   // Test empty first part (https://crbug.com/698073).
-  parts.push_back(StringPiece16());
+  parts.emplace_back();
   EXPECT_EQ(std::u16string(), JoinString(parts, separator));
   parts.clear();
 
@@ -912,7 +852,7 @@
   parts.push_back(kC);
   EXPECT_EQ(u"a, b, c", JoinString(parts, separator));
 
-  parts.push_back(StringPiece16());
+  parts.emplace_back();
   EXPECT_EQ(u"a, b, c, ", JoinString(parts, separator));
   const std::u16string kSpace = u" ";
   parts.push_back(kSpace);
@@ -924,13 +864,15 @@
   EXPECT_EQ(std::string(), JoinString({}, separator));
 
   // Test empty first part (https://crbug.com/698073).
-  EXPECT_EQ(std::string(), JoinString({StringPiece()}, separator));
+  EXPECT_EQ(std::string(), JoinString({std::string_view()}, separator));
 
   // With const char*s.
   EXPECT_EQ("a", JoinString({"a"}, separator));
   EXPECT_EQ("a, b, c", JoinString({"a", "b", "c"}, separator));
-  EXPECT_EQ("a, b, c, ", JoinString({"a", "b", "c", StringPiece()}, separator));
-  EXPECT_EQ("a|b|c|| ", JoinString({"a", "b", "c", StringPiece(), " "}, "|"));
+  EXPECT_EQ("a, b, c, ",
+            JoinString({"a", "b", "c", std::string_view()}, separator));
+  EXPECT_EQ("a|b|c|| ",
+            JoinString({"a", "b", "c", std::string_view(), " "}, "|"));
 
   // With std::strings.
   const std::string kA = "a";
@@ -938,8 +880,8 @@
   EXPECT_EQ("a, b", JoinString({kA, kB}, separator));
 
   // With StringPieces.
-  const StringPiece kPieceA = kA;
-  const StringPiece kPieceB = kB;
+  const std::string_view kPieceA = kA;
+  const std::string_view kPieceB = kB;
   EXPECT_EQ("a, b", JoinString({kPieceA, kPieceB}, separator));
 }
 
@@ -948,7 +890,7 @@
   EXPECT_EQ(std::u16string(), JoinString({}, separator));
 
   // Test empty first part (https://crbug.com/698073).
-  EXPECT_EQ(std::u16string(), JoinString({StringPiece16()}, separator));
+  EXPECT_EQ(std::u16string(), JoinString({std::u16string_view()}, separator));
 
   // With string16s.
   const std::u16string kA = u"a";
@@ -958,35 +900,36 @@
   const std::u16string kC = u"c";
   EXPECT_EQ(u"a, b, c", JoinString({kA, kB, kC}, separator));
 
-  EXPECT_EQ(u"a, b, c, ", JoinString({kA, kB, kC, StringPiece16()}, separator));
+  EXPECT_EQ(u"a, b, c, ",
+            JoinString({kA, kB, kC, std::u16string_view()}, separator));
   const std::u16string kSpace = u" ";
   EXPECT_EQ(u"a|b|c|| ",
-            JoinString({kA, kB, kC, StringPiece16(), kSpace}, u"|"));
+            JoinString({kA, kB, kC, std::u16string_view(), kSpace}, u"|"));
 
   // With StringPiece16s.
-  const StringPiece16 kPieceA = kA;
-  const StringPiece16 kPieceB = kB;
+  const std::u16string_view kPieceA = kA;
+  const std::u16string_view kPieceB = kB;
   EXPECT_EQ(u"a, b", JoinString({kPieceA, kPieceB}, separator));
 }
 
 TEST(StringUtilTest, StartsWith) {
-  EXPECT_TRUE(StartsWith("javascript:url", "javascript",
-                         gurl_base::CompareCase::SENSITIVE));
-  EXPECT_FALSE(StartsWith("JavaScript:url", "javascript",
-                          gurl_base::CompareCase::SENSITIVE));
+  EXPECT_TRUE(
+      StartsWith("javascript:url", "javascript", gurl_base::CompareCase::SENSITIVE));
+  EXPECT_FALSE(
+      StartsWith("JavaScript:url", "javascript", gurl_base::CompareCase::SENSITIVE));
   EXPECT_TRUE(StartsWith("javascript:url", "javascript",
                          gurl_base::CompareCase::INSENSITIVE_ASCII));
   EXPECT_TRUE(StartsWith("JavaScript:url", "javascript",
                          gurl_base::CompareCase::INSENSITIVE_ASCII));
   EXPECT_FALSE(StartsWith("java", "javascript", gurl_base::CompareCase::SENSITIVE));
-  EXPECT_FALSE(StartsWith("java", "javascript",
-                          gurl_base::CompareCase::INSENSITIVE_ASCII));
+  EXPECT_FALSE(
+      StartsWith("java", "javascript", gurl_base::CompareCase::INSENSITIVE_ASCII));
   EXPECT_FALSE(StartsWith(std::string(), "javascript",
                           gurl_base::CompareCase::INSENSITIVE_ASCII));
-  EXPECT_FALSE(StartsWith(std::string(), "javascript",
-                          gurl_base::CompareCase::SENSITIVE));
-  EXPECT_TRUE(StartsWith("java", std::string(),
-                         gurl_base::CompareCase::INSENSITIVE_ASCII));
+  EXPECT_FALSE(
+      StartsWith(std::string(), "javascript", gurl_base::CompareCase::SENSITIVE));
+  EXPECT_TRUE(
+      StartsWith("java", std::string(), gurl_base::CompareCase::INSENSITIVE_ASCII));
   EXPECT_TRUE(StartsWith("java", std::string(), gurl_base::CompareCase::SENSITIVE));
 
   EXPECT_TRUE(StartsWith(u"javascript:url", u"javascript",
@@ -1044,6 +987,225 @@
                        gurl_base::CompareCase::SENSITIVE));
 }
 
+TEST(StringUtilTest, RemovePrefix) {
+  {
+    std::optional<std::string_view> result;
+
+    result = RemovePrefix("", "");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "");
+    result = RemovePrefix("", "", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "");
+
+    EXPECT_FALSE(RemovePrefix("", "xyz"));
+    EXPECT_FALSE(RemovePrefix("", "xyZ", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemovePrefix("xyz", "");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "xyz");
+    result = RemovePrefix("Xyz", "", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "Xyz");
+
+    EXPECT_FALSE(RemovePrefix("abc", "xyz"));
+    EXPECT_FALSE(RemovePrefix("abc", "xyz", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemovePrefix("xyz", "xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "");
+    result = RemovePrefix("Xyz", "xyZ", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "");
+    EXPECT_EQ(result.value(), "");
+
+    EXPECT_FALSE(RemovePrefix("Xyz", "xyZ"));
+
+    result = RemovePrefix("xyz123", "xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "123");
+    result = RemovePrefix("Xyz123", "xyz", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "123");
+  }
+  {
+    std::optional<std::u16string_view> result;
+
+    result = RemovePrefix(u"", u"");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"");
+    result = RemovePrefix(u"", u"", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"");
+
+    EXPECT_FALSE(RemovePrefix(u"", u"xyz"));
+    EXPECT_FALSE(RemovePrefix(u"", u"xyZ", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemovePrefix(u"xyz", u"");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"xyz");
+    result = RemovePrefix(u"Xyz", u"", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"Xyz");
+
+    EXPECT_FALSE(RemovePrefix(u"abc", u"xyz"));
+    EXPECT_FALSE(RemovePrefix(u"abc", u"xyz", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemovePrefix(u"xyz", u"xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"");
+    result = RemovePrefix(u"Xyz", u"xyZ", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"");
+    EXPECT_EQ(result.value(), u"");
+
+    EXPECT_FALSE(RemovePrefix(u"Xyz", u"xyZ"));
+
+    result = RemovePrefix(u"xyz123", u"xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"123");
+    result = RemovePrefix(u"Xyz123", u"xyz", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"123");
+  }
+#if BUILDFLAG(IS_WIN)
+  {
+    std::optional<std::wstring_view> result;
+
+    result = RemovePrefix(L"", L"");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"");
+    result = RemovePrefix(L"", L"", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"");
+
+    EXPECT_FALSE(RemovePrefix(L"", L"xyz"));
+    EXPECT_FALSE(RemovePrefix(L"", L"xyZ", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemovePrefix(L"xyz", L"");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"xyz");
+    result = RemovePrefix(L"Xyz", L"", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"Xyz");
+
+    EXPECT_FALSE(RemovePrefix(L"abc", L"xyz"));
+    EXPECT_FALSE(RemovePrefix(L"abc", L"xyz", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemovePrefix(L"xyz", L"xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"");
+    result = RemovePrefix(L"Xyz", L"xyZ", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"");
+
+    EXPECT_FALSE(RemovePrefix(L"Xyz", L"xyZ"));
+
+    result = RemovePrefix(L"xyz123", L"xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"123");
+    result = RemovePrefix(L"Xyz123", L"xyz", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"123");
+
+    // Non-ASCII
+    result = RemovePrefix(L"你好世界", L"你好");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"世界");
+    EXPECT_FALSE(RemovePrefix(L"你好世界", L"世界"));
+
+    // Case-insensitivity is ASCII-only.
+    result = RemovePrefix(L"ÄBC", L"Äbc", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), L"");
+    EXPECT_FALSE(RemovePrefix(L"ÄBC", L"äbc", CompareCase::INSENSITIVE_ASCII));
+  }
+#endif
+}
+
+TEST(StringUtilTest, RemoveSuffix) {
+  {
+    std::optional<std::string_view> result;
+
+    result = RemoveSuffix("", "");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "");
+    result = RemoveSuffix("", "", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "");
+
+    EXPECT_FALSE(RemoveSuffix("", "xyz"));
+    EXPECT_FALSE(RemoveSuffix("", "xyZ", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemoveSuffix("xyz", "");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "xyz");
+    result = RemoveSuffix("Xyz", "", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "Xyz");
+
+    EXPECT_FALSE(RemoveSuffix("abc", "xyz"));
+    EXPECT_FALSE(RemoveSuffix("abc", "xyz", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemoveSuffix("xyz", "xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "");
+    result = RemoveSuffix("Xyz", "xyZ", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "");
+    EXPECT_EQ(result.value(), "");
+
+    EXPECT_FALSE(RemoveSuffix("Xyz", "xyZ"));
+
+    result = RemoveSuffix("123xyz", "xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "123");
+    result = RemoveSuffix("123Xyz", "xyZ", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), "123");
+  }
+  {
+    std::optional<std::u16string_view> result;
+
+    result = RemoveSuffix(u"", u"");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"");
+    result = RemoveSuffix(u"", u"", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"");
+
+    EXPECT_FALSE(RemoveSuffix(u"", u"xyz"));
+    EXPECT_FALSE(RemoveSuffix(u"", u"xyZ", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemoveSuffix(u"xyz", u"");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"xyz");
+    result = RemoveSuffix(u"Xyz", u"", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"Xyz");
+
+    EXPECT_FALSE(RemoveSuffix(u"abc", u"xyz"));
+    EXPECT_FALSE(RemoveSuffix(u"abc", u"xyz", CompareCase::INSENSITIVE_ASCII));
+
+    result = RemoveSuffix(u"xyz", u"xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"");
+    result = RemoveSuffix(u"Xyz", u"xyZ", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"");
+    EXPECT_EQ(result.value(), u"");
+
+    EXPECT_FALSE(RemoveSuffix(u"Xyz", u"xyZ"));
+
+    result = RemoveSuffix(u"123xyz", u"xyz");
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"123");
+    result = RemoveSuffix(u"123Xyz", u"xyZ", CompareCase::INSENSITIVE_ASCII);
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), u"123");
+  }
+}
+
 TEST(StringUtilTest, GetStringFWithOffsets) {
   std::vector<std::u16string> subst;
   subst.push_back(u"1");
@@ -1159,6 +1321,95 @@
   EXPECT_EQ(u"+++1a+", formatted);
 }
 
+TEST(StringUtilTest, ReplaceStringPlaceholdersArray) {
+  const auto subst = std::to_array<std::u16string>({u"world", u"hello"});
+
+  std::u16string formatted =
+      ReplaceStringPlaceholders(u"$2, $1!", subst, nullptr);
+  EXPECT_EQ(u"hello, world!", formatted);
+}
+
+std::u16string ReturnU16String() {
+  return u"U16String";
+}
+
+TEST(StringUtilTest, ReplaceStringPlaceholdersCastToSpan) {
+  std::u16string formatted = ReplaceStringPlaceholders(
+      u"$1", gurl_base::span<const std::u16string>({ReturnU16String()}), nullptr);
+  EXPECT_EQ(u"U16String", formatted);
+}
+
+// Return a string of type T long enough to defeat the short string
+// optimization.
+template <typename T>
+constexpr T LongString() {
+  constexpr char kLongString[] =
+      "This string is long enough to prevent the short string optimization";
+  // std::u16string can't be constructed from a char string, but it can be
+  // constructed from a char iterator pair.
+  return T(std::ranges::begin(kLongString), std::ranges::end(kLongString));
+}
+
+// Checks that a string implementation leaves the string empty when a
+// sufficiently long string is moved from. The "MovesFromString" tests will not
+// work correctly for implementations where this is not the case.
+template <typename T>
+constexpr bool MovedFromStringIsEmpty() {
+  T original = LongString<T>();
+  T destination = std::move(original);
+  // We are verifying behavior of the string implementation that is not required
+  // by the C++ standard.
+  // NOLINTNEXTLINE(bugprone-use-after-move)
+  return original.empty();
+}
+
+TEST(StringUtilTest, ReplaceStringPlaceholdersMovesFromString) {
+  ASSERT_TRUE(MovedFromStringIsEmpty<std::u16string>());
+  std::u16string long_string = LongString<std::u16string>();
+  std::vector<size_t> offsets;
+  // `offsets` is passed just to force the gurl_base::span overload.
+  std::u16string formatted =
+      ReplaceStringPlaceholders(u"=$1", {std::move(long_string)}, &offsets);
+  EXPECT_EQ(u"=" + LongString<std::u16string>(), formatted);
+  // This test relies on behavior that is not required by the C++ standard.
+  // NOLINTNEXTLINE(bugprone-use-after-move)
+  EXPECT_TRUE(long_string.empty());
+}
+
+// The single-argument form of ReplaceStringPlaceholders() should not require
+// `$1` to be present in the string unless `offset` is set.
+TEST(StringUtilTest, ReplaceStringPlaceholdersNoPlaceholder) {
+  const std::u16string replacement = u"unused";
+
+  std::u16string formatted =
+      ReplaceStringPlaceholders(u"no dollar", replacement, nullptr);
+  EXPECT_EQ(u"no dollar", formatted);
+}
+
+TEST(StringUtilTest, ReplaceStringPlaceholdersSingleOffset) {
+  size_t offset = 0;
+
+  std::u16string formatted =
+      ReplaceStringPlaceholders(u"a $1", u"car", &offset);
+
+  EXPECT_EQ(u"a car", formatted);
+  EXPECT_EQ(2u, offset);
+}
+
+TEST(StringUtilTest, ReplaceStringPlaceholdersInitializerList) {
+  // Combine lots of different types to make sure they all work.
+  const std::u16string be = u"be";
+  constexpr std::u16string_view kNot = u"not";
+  char16_t mutable_be[] = u"be";
+  constexpr const char16_t* const kIs = u"is";
+  std::u16string question = u"question";
+
+  std::u16string formatted = ReplaceStringPlaceholders(
+      u"to $1 or $2 to $3 that $4 the $5",
+      {be, std::u16string(kNot), mutable_be, kIs, question}, nullptr);
+  EXPECT_EQ(u"to be or not to be that is the question", formatted);
+}
+
 TEST(StringUtilTest, StdStringReplaceStringPlaceholders) {
   std::vector<std::string> subst;
   subst.push_back("9a");
@@ -1171,9 +1422,8 @@
   subst.push_back("2h");
   subst.push_back("1i");
 
-  std::string formatted =
-      ReplaceStringPlaceholders(
-          "$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i", subst, nullptr);
+  std::string formatted = ReplaceStringPlaceholders(
+      "$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i", subst, nullptr);
 
   EXPECT_EQ("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii", formatted);
 }
@@ -1194,6 +1444,40 @@
   EXPECT_EQ(expected_offsets, offsets);
 }
 
+TEST(StringUtilTest, StdStringReplaceStringPlaceholdersArray) {
+  const auto subst = std::to_array<std::string>({"world", "hello"});
+
+  std::string formatted = ReplaceStringPlaceholders("$2, $1!", subst, nullptr);
+  EXPECT_EQ("hello, world!", formatted);
+}
+
+TEST(StringUtilTest, StdStringReplaceStringPlaceholdersInitializerList) {
+  // Combine lots of different types to make sure they all work.
+  const std::string be = "be";
+  constexpr std::string_view kNot = "not";
+  char mutable_be[] = "be";
+  constexpr const char* const kIs = "is";
+  std::string question = "question";
+
+  std::string formatted = ReplaceStringPlaceholders(
+      "to $1 or $2 to $3 that $4 the $5",
+      {be, std::string(kNot), mutable_be, kIs, question}, nullptr);
+  EXPECT_EQ("to be or not to be that is the question", formatted);
+}
+
+TEST(StringUtilTest, StdStringReplaceStringPlaceholdersMovesFromString) {
+  ASSERT_TRUE(MovedFromStringIsEmpty<std::string>());
+  std::string long_string = LongString<std::string>();
+  std::vector<size_t> offsets;
+  // `offsets` is passed just to force the gurl_base::span overload.
+  std::string formatted =
+      ReplaceStringPlaceholders("=$1", {std::move(long_string)}, &offsets);
+  EXPECT_EQ("=" + LongString<std::string>(), formatted);
+  // This test relies  on behavior that is not required by the C++ standard.
+  // NOLINTNEXTLINE(bugprone-use-after-move)
+  EXPECT_TRUE(long_string.empty());
+}
+
 TEST(StringUtilTest, ReplaceStringPlaceholdersConsecutiveDollarSigns) {
   std::vector<std::string> subst;
   subst.push_back("a");
@@ -1215,6 +1499,16 @@
     EXPECT_EQ(0, memcmp(u16dst, u"abcdefg", sizeof(u16dst[0]) * 8));
     EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg", std::size(wdst)));
     EXPECT_EQ(0, memcmp(wdst, L"abcdefg", sizeof(wdst[0]) * 8));
+
+    EXPECT_EQ(7U, strlcpy(dst, "abcdefg"));
+    EXPECT_EQ(gurl_base::span(dst).first(8u),
+              gurl_base::span_with_nul_from_cstring("abcdefg"));
+    EXPECT_EQ(7U, u16cstrlcpy(u16dst, u"abcdefg"));
+    EXPECT_EQ(gurl_base::span(u16dst).first(8u),
+              gurl_base::span_with_nul_from_cstring(u"abcdefg"));
+    EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg"));
+    EXPECT_EQ(gurl_base::span(wdst).first(8u),
+              gurl_base::span_with_nul_from_cstring(L"abcdefg"));
   }
 
   // Test dst_size == 0, nothing should be written to |dst| and we should
@@ -1232,6 +1526,16 @@
     EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg", 0));
     EXPECT_EQ(static_cast<wchar_t>(1), wdst[0]);
     EXPECT_EQ(static_cast<wchar_t>(2), wdst[1]);
+
+    EXPECT_EQ(7U, strlcpy(gurl_base::span(dst).first(0u), "abcdefg"));
+    EXPECT_EQ(1, dst[0]);
+    EXPECT_EQ(2, dst[1]);
+    EXPECT_EQ(7U, u16cstrlcpy(gurl_base::span(u16dst).first(0u), u"abcdefg"));
+    EXPECT_EQ(char16_t{1}, u16dst[0]);
+    EXPECT_EQ(char16_t{2}, u16dst[1]);
+    EXPECT_EQ(7U, wcslcpy(gurl_base::span(wdst).first(0u), L"abcdefg"));
+    EXPECT_EQ(static_cast<wchar_t>(1), wdst[0]);
+    EXPECT_EQ(static_cast<wchar_t>(2), wdst[1]);
   }
 
   // Test the case were we _just_ competely fit including the null.
@@ -1245,6 +1549,13 @@
     EXPECT_EQ(0, memcmp(u16dst, u"abcdefg", sizeof(u16dst)));
     EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg", std::size(wdst)));
     EXPECT_EQ(0, memcmp(wdst, L"abcdefg", sizeof(wdst)));
+
+    EXPECT_EQ(7U, strlcpy(dst, "abcdefg"));
+    EXPECT_EQ(gurl_base::span(dst), gurl_base::span_with_nul_from_cstring("abcdefg"));
+    EXPECT_EQ(7U, u16cstrlcpy(u16dst, u"abcdefg"));
+    EXPECT_EQ(gurl_base::span(u16dst), gurl_base::span_with_nul_from_cstring(u"abcdefg"));
+    EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg"));
+    EXPECT_EQ(gurl_base::span(wdst), gurl_base::span_with_nul_from_cstring(L"abcdefg"));
   }
 
   // Test the case were we we are one smaller, so we can't fit the null.
@@ -1258,6 +1569,13 @@
     EXPECT_EQ(0, memcmp(u16dst, u"abcdef", sizeof(u16dst[0]) * 7));
     EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg", std::size(wdst)));
     EXPECT_EQ(0, memcmp(wdst, L"abcdef", sizeof(wdst[0]) * 7));
+
+    EXPECT_EQ(7U, strlcpy(dst, "abcdefg"));
+    EXPECT_EQ(gurl_base::span(dst), gurl_base::span_with_nul_from_cstring("abcdef"));
+    EXPECT_EQ(7U, u16cstrlcpy(u16dst, u"abcdefg"));
+    EXPECT_EQ(gurl_base::span(u16dst), gurl_base::span_with_nul_from_cstring(u"abcdef"));
+    EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg"));
+    EXPECT_EQ(gurl_base::span(wdst), gurl_base::span_with_nul_from_cstring(L"abcdef"));
   }
 
   // Test the case were we are just too small.
@@ -1271,6 +1589,13 @@
     EXPECT_EQ(0, memcmp(u16dst, u"ab", sizeof(u16dst)));
     EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg", std::size(wdst)));
     EXPECT_EQ(0, memcmp(wdst, L"ab", sizeof(wdst)));
+
+    EXPECT_EQ(7U, strlcpy(dst, "abcdefg"));
+    EXPECT_EQ(gurl_base::span(dst), gurl_base::span_with_nul_from_cstring("ab"));
+    EXPECT_EQ(7U, u16cstrlcpy(u16dst, u"abcdefg"));
+    EXPECT_EQ(gurl_base::span(u16dst), gurl_base::span_with_nul_from_cstring(u"ab"));
+    EXPECT_EQ(7U, wcslcpy(wdst, L"abcdefg"));
+    EXPECT_EQ(gurl_base::span(wdst), gurl_base::span_with_nul_from_cstring(L"ab"));
   }
 }
 
@@ -1278,72 +1603,30 @@
   static const struct {
     const wchar_t* input;
     bool portable;
-  } cases[] = {
-    { L"%ls", true },
-    { L"%s", false },
-    { L"%S", false },
-    { L"%lS", false },
-    { L"Hello, %s", false },
-    { L"%lc", true },
-    { L"%c", false },
-    { L"%C", false },
-    { L"%lC", false },
-    { L"%ls %s", false },
-    { L"%s %ls", false },
-    { L"%s %ls %s", false },
-    { L"%f", true },
-    { L"%f %F", false },
-    { L"%d %D", false },
-    { L"%o %O", false },
-    { L"%u %U", false },
-    { L"%f %d %o %u", true },
-    { L"%-8d (%02.1f%)", true },
-    { L"% 10s", false },
-    { L"% 10ls", true }
-  };
-  for (const auto& i : cases)
+  } cases[] = {{L"%ls", true},
+               {L"%s", false},
+               {L"%S", false},
+               {L"%lS", false},
+               {L"Hello, %s", false},
+               {L"%lc", true},
+               {L"%c", false},
+               {L"%C", false},
+               {L"%lC", false},
+               {L"%ls %s", false},
+               {L"%s %ls", false},
+               {L"%s %ls %s", false},
+               {L"%f", true},
+               {L"%f %F", false},
+               {L"%d %D", false},
+               {L"%o %O", false},
+               {L"%u %U", false},
+               {L"%f %d %o %u", true},
+               {L"%-8d (%02.1f%)", true},
+               {L"% 10s", false},
+               {L"% 10ls", true}};
+  for (const auto& i : cases) {
     EXPECT_EQ(i.portable, IsWprintfFormatPortable(i.input));
-}
-
-TEST(StringUtilTest, MakeBasicStringPieceTest) {
-  constexpr char kFoo[] = "Foo";
-  static_assert(MakeStringPiece(kFoo, kFoo + 3) == kFoo, "");
-  static_assert(MakeStringPiece(kFoo, kFoo + 3).data() == kFoo, "");
-  static_assert(MakeStringPiece(kFoo, kFoo + 3).size() == 3, "");
-  static_assert(MakeStringPiece(kFoo + 3, kFoo + 3).empty(), "");
-  static_assert(MakeStringPiece(kFoo + 4, kFoo + 4).empty(), "");
-
-  std::string foo = kFoo;
-  EXPECT_EQ(MakeStringPiece(foo.begin(), foo.end()), foo);
-  EXPECT_EQ(MakeStringPiece(foo.begin(), foo.end()).data(), foo.data());
-  EXPECT_EQ(MakeStringPiece(foo.begin(), foo.end()).size(), foo.size());
-  EXPECT_TRUE(MakeStringPiece(foo.end(), foo.end()).empty());
-
-  constexpr char16_t kBar[] = u"Bar";
-  static_assert(MakeStringPiece16(kBar, kBar + 3) == kBar, "");
-  static_assert(MakeStringPiece16(kBar, kBar + 3).data() == kBar, "");
-  static_assert(MakeStringPiece16(kBar, kBar + 3).size() == 3, "");
-  static_assert(MakeStringPiece16(kBar + 3, kBar + 3).empty(), "");
-  static_assert(MakeStringPiece16(kBar + 4, kBar + 4).empty(), "");
-
-  std::u16string bar = kBar;
-  EXPECT_EQ(MakeStringPiece16(bar.begin(), bar.end()), bar);
-  EXPECT_EQ(MakeStringPiece16(bar.begin(), bar.end()).data(), bar.data());
-  EXPECT_EQ(MakeStringPiece16(bar.begin(), bar.end()).size(), bar.size());
-  EXPECT_TRUE(MakeStringPiece16(bar.end(), bar.end()).empty());
-
-  constexpr wchar_t kBaz[] = L"Baz";
-  static_assert(MakeWStringView(kBaz, kBaz + 3) == kBaz, "");
-  static_assert(MakeWStringView(kBaz, kBaz + 3).data() == kBaz, "");
-  static_assert(MakeWStringView(kBaz, kBaz + 3).size() == 3, "");
-  static_assert(MakeWStringView(kBaz + 3, kBaz + 3).empty(), "");
-  static_assert(MakeWStringView(kBaz + 4, kBaz + 4).empty(), "");
-
-  std::wstring baz = kBaz;
-  EXPECT_EQ(MakeWStringView(baz.begin(), baz.end()), baz);
-  EXPECT_EQ(MakeWStringView(baz.begin(), baz.end()).data(), baz.data());
-  EXPECT_EQ(MakeWStringView(baz.begin(), baz.end()).size(), baz.size());
-  EXPECT_TRUE(MakeWStringView(baz.end(), baz.end()).empty());
+  }
 }
 
 TEST(StringUtilTest, RemoveChars) {
diff --git a/base/strings/string_util_win.cc b/base/strings/string_util_win.cc
index 0caa8d3..2891ddc 100644
--- a/base/strings/string_util_win.cc
+++ b/base/strings/string_util_win.cc
@@ -4,11 +4,11 @@
 
 #include "base/strings/string_util_win.h"
 
+#include <algorithm>
+#include <optional>
 #include <string_view>
 
-#include "base/ranges/algorithm.h"
 #include "base/strings/string_util_impl_helpers.h"
-#include "absl/types/optional.h"
 
 namespace gurl_base {
 
@@ -74,11 +74,11 @@
 }
 
 bool ContainsOnlyChars(std::wstring_view input, std::wstring_view characters) {
-  return input.find_first_not_of(characters) == StringPiece::npos;
+  return input.find_first_not_of(characters) == std::string_view::npos;
 }
 
-bool EqualsASCII(std::wstring_view str, StringPiece ascii) {
-  return ranges::equal(ascii, str);
+bool EqualsASCII(std::wstring_view str, std::string_view ascii) {
+  return std::ranges::equal(ascii, str);
 }
 
 bool StartsWith(std::wstring_view str,
@@ -93,6 +93,16 @@
   return internal::EndsWithT(str, search_for, case_sensitivity);
 }
 
+std::optional<std::wstring_view> RemovePrefix(std::wstring_view string,
+                                              std::wstring_view prefix,
+                                              CompareCase case_sensitivity) {
+  if (!StartsWith(string, prefix, case_sensitivity)) {
+    return std::nullopt;
+  }
+  string.remove_prefix(prefix.size());
+  return string;
+}
+
 void ReplaceFirstSubstringAfterOffset(std::wstring* str,
                                       size_t start_offset,
                                       std::wstring_view find_this,
@@ -131,9 +141,9 @@
 }
 
 std::wstring ReplaceStringPlaceholders(std::wstring_view format_string,
-                                       const std::vector<std::wstring>& subst,
+                                       gurl_base::span<const std::wstring> subst,
                                        std::vector<size_t>* offsets) {
-  absl::optional<std::wstring> replacement =
+  std::optional<std::wstring> replacement =
       internal::DoReplaceStringPlaceholders(
           format_string, subst,
           /*placeholder_prefix*/ L'$',
diff --git a/base/strings/string_util_win.h b/base/strings/string_util_win.h
index ed3dbbc..ecf5f99 100644
--- a/base/strings/string_util_win.h
+++ b/base/strings/string_util_win.h
@@ -16,8 +16,8 @@
 #include <vector>
 
 #include "polyfills/base/check.h"
+#include "base/compiler_specific.h"
 #include "base/containers/span.h"
-#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 
 namespace gurl_base {
@@ -28,21 +28,32 @@
   return _strdup(str);
 }
 
-inline int vsnprintf(char* buffer, size_t size,
-                     const char* format, va_list arguments) {
-  int length = vsnprintf_s(buffer, size, size - 1, format, arguments);
-  if (length < 0)
+inline int vsnprintf(char* buffer,
+                     size_t size,
+                     const char* format,
+                     va_list arguments) {
+  int length =
+      UNSAFE_TODO(vsnprintf_s(buffer, size, size - 1, format, arguments));
+  if (length < 0) {
     return _vscprintf(format, arguments);
+  }
   return length;
 }
 
-inline int vswprintf(wchar_t* buffer, size_t size,
-                     const wchar_t* format, va_list arguments) {
+// TODO(crbug.com/40284755): implement spanified version.
+// inline int vswprintf(gurl_base::span<wchar_t> buffer,
+//                      const wchar_t* format,
+//                      va_list arguments);
+inline int vswprintf(wchar_t* buffer,
+                     size_t size,
+                     const wchar_t* format,
+                     va_list arguments) {
   GURL_DCHECK(IsWprintfFormatPortable(format));
 
   int length = _vsnwprintf_s(buffer, size, size - 1, format, arguments);
-  if (length < 0)
+  if (length < 0) {
     return _vscwprintf(format, arguments);
+  }
   return length;
 }
 
@@ -68,7 +79,7 @@
   return reinterpret_cast<const wchar_t*>(str);
 }
 
-inline const wchar_t* as_wcstr(StringPiece16 str) {
+inline const wchar_t* as_wcstr(std::u16string_view str) {
   return reinterpret_cast<const wchar_t*>(str.data());
 }
 
@@ -91,16 +102,16 @@
 }
 
 // Utility functions to convert between std::wstring_view and
-// gurl_base::StringPiece16.
-inline std::wstring_view AsWStringView(StringPiece16 str) {
+// std::u16string_view.
+inline std::wstring_view AsWStringView(std::u16string_view str) {
   return std::wstring_view(as_wcstr(str.data()), str.size());
 }
 
-inline StringPiece16 AsStringPiece16(std::wstring_view str) {
-  return StringPiece16(as_u16cstr(str.data()), str.size());
+inline std::u16string_view AsStringPiece16(std::wstring_view str) {
+  return std::u16string_view(as_u16cstr(str.data()), str.size());
 }
 
-inline std::wstring AsWString(StringPiece16 str) {
+inline std::wstring AsWString(std::u16string_view str) {
   return std::wstring(as_wcstr(str.data()), str.size());
 }
 
@@ -123,10 +134,12 @@
                                        std::wstring_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
-inline bool EqualsCaseInsensitiveASCII(std::wstring_view a, StringPiece b) {
+inline bool EqualsCaseInsensitiveASCII(std::wstring_view a,
+                                       std::string_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
-inline bool EqualsCaseInsensitiveASCII(StringPiece a, std::wstring_view b) {
+inline bool EqualsCaseInsensitiveASCII(std::string_view a,
+                                       std::wstring_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
 
@@ -161,7 +174,7 @@
 BASE_EXPORT bool ContainsOnlyChars(std::wstring_view input,
                                    std::wstring_view characters);
 
-BASE_EXPORT bool EqualsASCII(StringPiece16 str, StringPiece ascii);
+BASE_EXPORT bool EqualsASCII(std::u16string_view str, std::string_view ascii);
 
 BASE_EXPORT bool StartsWith(
     std::wstring_view str,
@@ -173,6 +186,11 @@
     std::wstring_view search_for,
     CompareCase case_sensitivity = CompareCase::SENSITIVE);
 
+BASE_EXPORT std::optional<std::wstring_view> RemovePrefix(
+    std::wstring_view string,
+    std::wstring_view prefix,
+    CompareCase case_sensitivity = CompareCase::SENSITIVE);
+
 BASE_EXPORT void ReplaceFirstSubstringAfterOffset(
     std::wstring* str,
     size_t start_offset,
@@ -198,7 +216,7 @@
 
 BASE_EXPORT std::wstring ReplaceStringPlaceholders(
     std::wstring_view format_string,
-    const std::vector<std::wstring>& subst,
+    gurl_base::span<const std::wstring> subst,
     std::vector<size_t>* offsets);
 
 }  // namespace base
diff --git a/base/strings/string_view_rust.h b/base/strings/string_view_rust.h
new file mode 100644
index 0000000..663f283
--- /dev/null
+++ b/base/strings/string_view_rust.h
@@ -0,0 +1,40 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRING_VIEW_RUST_H_
+#define BASE_STRINGS_STRING_VIEW_RUST_H_
+
+#include <stdint.h>
+
+#include <string_view>
+
+#include "build/build_config.h"
+#include "third_party/rust/cxx/v1/cxx.h"
+
+namespace gurl_base {
+
+// Create a Rust str from a std::string_view. This will call std::abort
+// if there is any invalid UTF8. If you're concerned about this, then
+// instead use StringViewToRustSlice and convert the data to a string on
+// the Rust side (or pass in a std::string).
+inline rust::Str StringViewToRustStrUTF8(std::string_view string_piece) {
+  return rust::Str(string_piece.data(), string_piece.size());
+}
+
+// Create a Rust slice from a std::string_view. No UTF8 check is performed.
+inline rust::Slice<const uint8_t> StringViewToRustSlice(
+    std::string_view string_piece) {
+  return rust::Slice<const uint8_t>(
+      reinterpret_cast<const uint8_t*>(string_piece.data()),
+      string_piece.length() * sizeof(std::string_view::value_type));
+}
+
+// Create a std::string_view from a Rust str.
+inline std::string_view RustStrToStringView(rust::Str str) {
+  return std::string_view(str.data(), str.size());
+}
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_STRING_VIEW_RUST_H_
diff --git a/base/strings/string_view_util.h b/base/strings/string_view_util.h
new file mode 100644
index 0000000..24a61e7
--- /dev/null
+++ b/base/strings/string_view_util.h
@@ -0,0 +1,48 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_STRING_VIEW_UTIL_H_
+#define BASE_STRINGS_STRING_VIEW_UTIL_H_
+
+#include <string_view>
+
+#include "base/compiler_specific.h"
+#include "base/containers/span.h"
+
+namespace gurl_base {
+
+// Helper function for creating a std::string_view from a string literal that
+// preserves internal NUL characters.
+template <class CharT, size_t N>
+std::basic_string_view<CharT> MakeStringViewWithNulChars(
+    const CharT (&lit LIFETIME_BOUND)[N])
+    ENABLE_IF_ATTR(lit[N - 1u] == CharT{0},
+                   "requires string literal as input") {
+  return std::basic_string_view<CharT>(lit, N - 1u);
+}
+
+// Converts a span over byte-like elements to `std::string_view`.
+//
+// std:: has no direct equivalent for this; however, it eases span adoption in
+// Chromium, which uses `string`s and `string_view`s in many cases that
+// rightfully should be containers of `uint8_t`.
+//
+// TODO(C++23): Replace with direct use of the `std::string_view` range
+// constructor.
+constexpr auto as_string_view(span<const char> s) {
+  return std::string_view(s.begin(), s.end());
+}
+constexpr auto as_string_view(span<const unsigned char> s) {
+  return as_string_view(as_chars(s));
+}
+constexpr auto as_string_view(span<const char16_t> s) {
+  return std::u16string_view(s.begin(), s.end());
+}
+constexpr auto as_string_view(span<const wchar_t> s) {
+  return std::wstring_view(s.begin(), s.end());
+}
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_STRING_VIEW_UTIL_H_
diff --git a/base/strings/string_view_util_unittest.cc b/base/strings/string_view_util_unittest.cc
new file mode 100644
index 0000000..f53cc79
--- /dev/null
+++ b/base/strings/string_view_util_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_view_util.h"
+
+#include <string_view>
+#include <type_traits>
+
+#include "base/containers/span.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gurl_base {
+
+// Tests that MakeStringViewWithNulChars preserves internal NUL characters.
+TEST(StringViewUtilTest, MakeStringViewWithNulChars) {
+  {
+    const char kTestString[] = "abd\0def";
+    auto s = MakeStringViewWithNulChars(kTestString);
+    EXPECT_EQ(s.size(), 7u);
+    EXPECT_EQ(gurl_base::span(s), gurl_base::span_from_cstring(kTestString));
+  }
+  {
+    const wchar_t kTestString[] = L"abd\0def";
+    auto s = MakeStringViewWithNulChars(kTestString);
+    EXPECT_EQ(s.size(), 7u);
+    ASSERT_TRUE(gurl_base::span(s) == gurl_base::span_from_cstring(kTestString));
+  }
+  {
+    const char16_t kTestString[] = u"abd\0def";
+    auto s = MakeStringViewWithNulChars(kTestString);
+    EXPECT_EQ(s.size(), 7u);
+    EXPECT_TRUE(gurl_base::span(s) == gurl_base::span_from_cstring(kTestString));
+  }
+  {
+    const char32_t kTestString[] = U"abd\0def";
+    auto s = MakeStringViewWithNulChars(kTestString);
+    EXPECT_EQ(s.size(), 7u);
+    EXPECT_TRUE(gurl_base::span(s) == gurl_base::span_from_cstring(kTestString));
+  }
+}
+
+TEST(SpanTest, AsStringView) {
+  {
+    constexpr uint8_t kArray[] = {'h', 'e', 'l', 'l', 'o'};
+    // Fixed size span.
+    auto s = as_string_view(kArray);
+    static_assert(std::is_same_v<decltype(s), std::string_view>);
+    EXPECT_EQ(s.data(), reinterpret_cast<const char*>(&kArray[0u]));
+    EXPECT_EQ(s.size(), std::size(kArray));
+
+    // Dynamic size span.
+    auto s2 = as_string_view(span<const uint8_t>(kArray));
+    static_assert(std::is_same_v<decltype(s2), std::string_view>);
+    EXPECT_EQ(s2.data(), reinterpret_cast<const char*>(&kArray[0u]));
+    EXPECT_EQ(s2.size(), std::size(kArray));
+  }
+  {
+    constexpr char kArray[] = {'h', 'e', 'l', 'l', 'o'};
+    // Fixed size span.
+    auto s = as_string_view(kArray);
+    static_assert(std::is_same_v<decltype(s), std::string_view>);
+    EXPECT_EQ(s.data(), &kArray[0u]);
+    EXPECT_EQ(s.size(), std::size(kArray));
+
+    // Dynamic size span.
+    auto s2 = as_string_view(span<const char>(kArray));
+    static_assert(std::is_same_v<decltype(s2), std::string_view>);
+    EXPECT_EQ(s2.data(), &kArray[0u]);
+    EXPECT_EQ(s2.size(), std::size(kArray));
+  }
+}
+
+}  // namespace base
diff --git a/base/strings/stringize_macros_unittest.cc b/base/strings/stringize_macros_unittest.cc
index c3c3479..f3f1546 100644
--- a/base/strings/stringize_macros_unittest.cc
+++ b/base/strings/stringize_macros_unittest.cc
@@ -12,15 +12,12 @@
 #define PREPROCESSOR_UTIL_UNITTEST_C "foo"
 
 TEST(StringizeTest, Ansi) {
-  EXPECT_STREQ(
-      "PREPROCESSOR_UTIL_UNITTEST_A",
-      STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_A));
-  EXPECT_STREQ(
-      "PREPROCESSOR_UTIL_UNITTEST_B(y)",
-      STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_B(y)));
-  EXPECT_STREQ(
-      "PREPROCESSOR_UTIL_UNITTEST_C",
-      STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_C));
+  EXPECT_STREQ("PREPROCESSOR_UTIL_UNITTEST_A",
+               STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_A));
+  EXPECT_STREQ("PREPROCESSOR_UTIL_UNITTEST_B(y)",
+               STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_B(y)));
+  EXPECT_STREQ("PREPROCESSOR_UTIL_UNITTEST_C",
+               STRINGIZE_NO_EXPANSION(PREPROCESSOR_UTIL_UNITTEST_C));
 
   EXPECT_STREQ("FOO", STRINGIZE(PREPROCESSOR_UTIL_UNITTEST_A));
   EXPECT_STREQ("myobj->FunctionCall(y)",
diff --git a/base/strings/stringprintf.cc b/base/strings/stringprintf.cc
index 6720744..84cead8 100644
--- a/base/strings/stringprintf.cc
+++ b/base/strings/stringprintf.cc
@@ -11,33 +11,18 @@
 
 #include "polyfills/base/logging.h"
 #include "base/scoped_clear_last_error.h"
+#include "base/strings/span_printf.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
 
 namespace gurl_base {
 
-std::string StringPrintf(const char* format, ...) {
-  va_list ap;
-  va_start(ap, format);
-  std::string result;
-  StringAppendV(&result, format, ap);
-  va_end(ap);
-  return result;
-}
-
 std::string StringPrintV(const char* format, va_list ap) {
   std::string result;
   StringAppendV(&result, format, ap);
   return result;
 }
 
-void StringAppendF(std::string* dst, const char* format, ...) {
-  va_list ap;
-  va_start(ap, format);
-  StringAppendV(dst, format, ap);
-  va_end(ap);
-}
-
 void StringAppendV(std::string* dst, const char* format, va_list ap) {
   // First try with a small fixed size buffer.
   // This buffer size should be kept in sync with StringUtilTest.GrowBoundary
@@ -48,7 +33,7 @@
   va_copy(ap_copy, ap);
 
   gurl_base::ScopedClearLastError last_error;
-  int result = vsnprintf(stack_buf, std::size(stack_buf), format, ap_copy);
+  int result = VSpanPrintf(stack_buf, format, ap_copy);
   va_end(ap_copy);
 
   if (result >= 0 && static_cast<size_t>(result) < std::size(stack_buf)) {
@@ -91,7 +76,7 @@
     // NOTE: You can only use a va_list once.  Since we're in a while loop, we
     // need to make a new copy each time so we don't use up the original.
     va_copy(ap_copy, ap);
-    result = vsnprintf(&mem_buf[0], mem_length, format, ap_copy);
+    result = VSpanPrintf(mem_buf, format, ap_copy);
     va_end(ap_copy);
 
     if ((result >= 0) && (static_cast<size_t>(result) < mem_length)) {
diff --git a/base/strings/stringprintf.h b/base/strings/stringprintf.h
index 879dac8..504875f 100644
--- a/base/strings/stringprintf.h
+++ b/base/strings/stringprintf.h
@@ -8,71 +8,41 @@
 #include <stdarg.h>  // va_list
 
 #include <string>
-#include <string_view>
 
 #include "polyfills/base/base_export.h"
 #include "base/compiler_specific.h"
+#include "absl/strings/str_format.h"
 
 namespace gurl_base {
 
-// Returns a C++ string given `printf()`-like input. The format string should be
-// a compile-time constant (like with `std::format()`).
-// TODO(crbug.com/1371963): Implement in terms of `std::format()`,
-// `absl::StrFormat()`, or similar.
-[[nodiscard]] BASE_EXPORT std::string StringPrintf(const char* format, ...)
-    PRINTF_FORMAT(1, 2);
-
 // Returns a C++ string given `printf()`-like input. The format string must be a
-// run-time value (like with `std::vformat()`), or this will not compile.
-// Because this does not check arguments at compile-time, prefer
-// `StringPrintf()` whenever possible.
+// compile-time constant (like with `std::format()`), or this will not compile.
+// TODO(crbug.com/40241565): Replace calls to this with direct calls to
+// `absl::StrFormat()` and remove.
 template <typename... Args>
-[[nodiscard]] std::string StringPrintfNonConstexpr(std::string_view format,
-                                                   const Args&... args) {
-  // TODO(crbug.com/1371963): Implement in terms of `std::vformat()`,
-  // `absl::FormatUntyped()`, or similar.
-  return StringPrintf(format.data(), args...);
+[[nodiscard]] std::string StringPrintf(const absl::FormatSpec<Args...>& format,
+                                       const Args&... args) {
+  return absl::StrFormat(format, args...);
 }
 
-// If possible, guide users to use `StringPrintf()` instead of
-// `StringPrintfNonConstexpr()` when the format string is constexpr.
-//
-// It would be nice to do this with `std::enable_if`, but I don't know of a way;
-// whether a string constant's value is available at compile time is not
-// something easily obtained from the type system, and trying to pass various
-// forms of string constant to non-type template parameters produces a variety
-// of compile errors.
-#if HAS_ATTRIBUTE(enable_if)
-// Disable calling with a constexpr `std::string_view`.
-template <typename... Args>
-[[nodiscard]] std::string StringPrintfNonConstexpr(std::string_view format,
-                                                   const Args&... args)
-    __attribute__((enable_if(
-        [](std::string_view s) { return s.empty() || s[0] == s[0]; }(format),
-        "Use StringPrintf() for constexpr format strings"))) = delete;
-// Disable calling with a constexpr `char[]` or `char*`.
-template <typename... Args>
-[[nodiscard]] std::string StringPrintfNonConstexpr(const char* format,
-                                                   const Args&... args)
-    __attribute__((
-        enable_if([](const char* s) { return !!s; }(format),
-                  "Use StringPrintf() for constexpr format strings"))) = delete;
-#endif
-
 // Returns a C++ string given `vprintf()`-like input.
-[[nodiscard]] BASE_EXPORT std::string StringPrintV(const char* format,
-                                                   va_list ap)
-    PRINTF_FORMAT(1, 0);
+[[nodiscard]] PRINTF_FORMAT(1, 0) BASE_EXPORT std::string
+    StringPrintV(const char* format, va_list ap);
 
 // Like `StringPrintf()`, but appends result to a supplied string.
-// TODO(crbug.com/1371963): Implement in terms of `std::format_to()`,
-// `absl::StrAppendFormat()`, or similar.
-BASE_EXPORT void StringAppendF(std::string* dst, const char* format, ...)
-    PRINTF_FORMAT(2, 3);
+// TODO(crbug.com/40241565): Replace calls to this with direct calls to
+// `absl::StrAppendFormat()` and remove.
+template <typename... Args>
+void StringAppendF(std::string* dst,
+                   const absl::FormatSpec<Args...>& format,
+                   const Args&... args) {
+  absl::StrAppendFormat(dst, format, args...);
+}
 
 // Like `StringPrintV()`, but appends result to a supplied string.
-BASE_EXPORT void StringAppendV(std::string* dst, const char* format, va_list ap)
-    PRINTF_FORMAT(2, 0);
+PRINTF_FORMAT(2, 0)
+BASE_EXPORT
+void StringAppendV(std::string* dst, const char* format, va_list ap);
 
 }  // namespace base
 
diff --git a/base/strings/stringprintf_unittest.cc b/base/strings/stringprintf_unittest.cc
index 981c45c..6e5c93d 100644
--- a/base/strings/stringprintf_unittest.cc
+++ b/base/strings/stringprintf_unittest.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "base/strings/stringprintf.h"
 
 #include <errno.h>
@@ -78,8 +83,9 @@
 // Test very large sprintfs that will cause the buffer to grow.
 TEST(StringPrintfTest, Grow) {
   char src[1026];
-  for (auto& i : src)
+  for (auto& i : src) {
     i = 'A';
+  }
   src[1025] = 0;
 
   const char fmt[] = "%sB%sB%sB%sB%sB%sB%s";
@@ -111,8 +117,9 @@
   // And need extra one for NULL-terminator.
   const int kBufLen = kStringUtilBufLen + 1 + 1;
   char src[kBufLen];
-  for (int i = 0; i < kBufLen - 1; ++i)
+  for (int i = 0; i < kBufLen - 1; ++i) {
     src[i] = 'a';
+  }
   src[kBufLen - 1] = 0;
 
   EXPECT_EQ(src, StringPrintf("%s", src));
diff --git a/base/strings/sys_string_conversions.h b/base/strings/sys_string_conversions.h
index 38f89f8..581df64 100644
--- a/base/strings/sys_string_conversions.h
+++ b/base/strings/sys_string_conversions.h
@@ -12,9 +12,9 @@
 #include <stdint.h>
 
 #include <string>
+#include <string_view>
 
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
 #include "build/build_config.h"
 
 #if BUILDFLAG(IS_APPLE)
@@ -32,14 +32,15 @@
 // Converts between wide and UTF-8 representations of a string. On error, the
 // result is system-dependent.
 [[nodiscard]] BASE_EXPORT std::string SysWideToUTF8(const std::wstring& wide);
-[[nodiscard]] BASE_EXPORT std::wstring SysUTF8ToWide(StringPiece utf8);
+[[nodiscard]] BASE_EXPORT std::wstring SysUTF8ToWide(std::string_view utf8);
 
 // Converts between wide and the system multi-byte representations of a string.
 // DANGER: This will lose information and can change (on Windows, this can
 // change between reboots).
 [[nodiscard]] BASE_EXPORT std::string SysWideToNativeMB(
     const std::wstring& wide);
-[[nodiscard]] BASE_EXPORT std::wstring SysNativeMBToWide(StringPiece native_mb);
+[[nodiscard]] BASE_EXPORT std::wstring SysNativeMBToWide(
+    std::string_view native_mb);
 
 // Windows-specific ------------------------------------------------------------
 
@@ -48,7 +49,7 @@
 // Converts between 8-bit and wide strings, using the given code page. The
 // code page identifier is one accepted by the Windows function
 // MultiByteToWideChar().
-[[nodiscard]] BASE_EXPORT std::wstring SysMultiByteToWide(StringPiece mb,
+[[nodiscard]] BASE_EXPORT std::wstring SysMultiByteToWide(std::string_view mb,
                                                           uint32_t code_page);
 [[nodiscard]] BASE_EXPORT std::string SysWideToMultiByte(
     const std::wstring& wide,
@@ -64,9 +65,9 @@
 
 // Converts a string to a CFStringRef. Returns null on failure.
 [[nodiscard]] BASE_EXPORT apple::ScopedCFTypeRef<CFStringRef>
-SysUTF8ToCFStringRef(StringPiece utf8);
+SysUTF8ToCFStringRef(std::string_view utf8);
 [[nodiscard]] BASE_EXPORT apple::ScopedCFTypeRef<CFStringRef>
-SysUTF16ToCFStringRef(StringPiece16 utf16);
+SysUTF16ToCFStringRef(std::u16string_view utf16);
 
 // Converts a CFStringRef to a string. Returns an empty string on failure. It is
 // not valid to call these with a null `ref`.
@@ -76,8 +77,9 @@
 #ifdef __OBJC__
 
 // Converts a string to an autoreleased NSString. Returns nil on failure.
-[[nodiscard]] BASE_EXPORT NSString* SysUTF8ToNSString(StringPiece utf8);
-[[nodiscard]] BASE_EXPORT NSString* SysUTF16ToNSString(StringPiece16 utf16);
+[[nodiscard]] BASE_EXPORT NSString* SysUTF8ToNSString(std::string_view utf8);
+[[nodiscard]] BASE_EXPORT NSString* SysUTF16ToNSString(
+    std::u16string_view utf16);
 
 // Converts an NSString to a string. Returns an empty string on failure or if
 // `ref` is nil.
diff --git a/base/strings/sys_string_conversions_posix.cc b/base/strings/sys_string_conversions_posix.cc
index 3aab336..0493fff 100644
--- a/base/strings/sys_string_conversions_posix.cc
+++ b/base/strings/sys_string_conversions_posix.cc
@@ -8,7 +8,9 @@
 #include <string.h>
 #include <wchar.h>
 
-#include "base/strings/string_piece.h"
+#include <string_view>
+
+#include "base/compiler_specific.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 
@@ -19,7 +21,7 @@
   // than our ICU, but this will do for now.
   return WideToUTF8(wide);
 }
-std::wstring SysUTF8ToWide(StringPiece utf8) {
+std::wstring SysUTF8ToWide(std::string_view utf8) {
   // In theory this should be using the system-provided conversion rather
   // than our ICU, but this will do for now.
   std::wstring out;
@@ -35,19 +37,18 @@
   return WideToUTF8(wide);
 }
 
-std::wstring SysNativeMBToWide(StringPiece native_mb) {
+std::wstring SysNativeMBToWide(std::string_view native_mb) {
   return SysUTF8ToWide(native_mb);
 }
 
 #else
 
 std::string SysWideToNativeMB(const std::wstring& wide) {
-  mbstate_t ps;
+  mbstate_t ps = {};
 
   // Calculate the number of multi-byte characters.  We walk through the string
   // without writing the output, counting the number of multi-byte characters.
   size_t num_out_chars = 0;
-  memset(&ps, 0, sizeof(ps));
   for (auto src : wide) {
     // Use a temp buffer since calling wcrtomb with an output of NULL does not
     // calculate the output length.
@@ -68,15 +69,16 @@
     }
   }
 
-  if (num_out_chars == 0)
+  if (num_out_chars == 0) {
     return std::string();
+  }
 
   std::string out;
   out.resize(num_out_chars);
 
   // We walk the input string again, with |i| tracking the index of the
   // wide input, and |j| tracking the multi-byte output.
-  memset(&ps, 0, sizeof(ps));
+  ps = {};
   for (size_t i = 0, j = 0; i < wide.size(); ++i) {
     const wchar_t src = wide[i];
     // We don't want wcrtomb to do its funkiness for embedded NULLs.
@@ -98,15 +100,14 @@
   return out;
 }
 
-std::wstring SysNativeMBToWide(StringPiece native_mb) {
-  mbstate_t ps;
+std::wstring SysNativeMBToWide(std::string_view native_mb) {
+  mbstate_t ps = {};
 
   // Calculate the number of wide characters.  We walk through the string
   // without writing the output, counting the number of wide characters.
   size_t num_out_chars = 0;
-  memset(&ps, 0, sizeof(ps));
-  for (size_t i = 0; i < native_mb.size(); ) {
-    const char* src = native_mb.data() + i;
+  for (size_t i = 0; i < native_mb.size();) {
+    const char* src = UNSAFE_TODO(native_mb.data() + i);
     size_t res = mbrtowc(nullptr, src, native_mb.size() - i, &ps);
     switch (res) {
       // Handle any errors and return an empty string.
@@ -124,17 +125,18 @@
     }
   }
 
-  if (num_out_chars == 0)
+  if (num_out_chars == 0) {
     return std::wstring();
+  }
 
   std::wstring out;
   out.resize(num_out_chars);
 
-  memset(&ps, 0, sizeof(ps));  // Clear the shift state.
+  ps = {};  // Clear the shift state.
   // We walk the input string again, with |i| tracking the index of the
   // multi-byte input, and |j| tracking the wide output.
   for (size_t i = 0, j = 0; i < native_mb.size(); ++j) {
-    const char* src = native_mb.data() + i;
+    const char* src = UNSAFE_TODO(native_mb.data() + i);
     wchar_t* dst = &out[j];
     size_t res = mbrtowc(dst, src, native_mb.size() - i, &ps);
     switch (res) {
diff --git a/base/strings/sys_string_conversions_unittest.cc b/base/strings/sys_string_conversions_unittest.cc
index d4552ea..4b957fc 100644
--- a/base/strings/sys_string_conversions_unittest.cc
+++ b/base/strings/sys_string_conversions_unittest.cc
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/strings/sys_string_conversions.h"
+
 #include <stddef.h>
 
 #include <string>
 
-#include "base/strings/string_piece.h"
-#include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_locale.h"
 #include "build/build_config.h"
@@ -171,7 +171,6 @@
 #endif
 };
 
-
 TEST(SysStrings, SysNativeMBAndWide) {
 #if !defined(SYSTEM_NATIVE_UTF8)
   ScopedLocale locale("en_US.UTF-8");
diff --git a/base/strings/sys_string_conversions_win.cc b/base/strings/sys_string_conversions_win.cc
index 50b7c76..c67a068 100644
--- a/base/strings/sys_string_conversions_win.cc
+++ b/base/strings/sys_string_conversions_win.cc
@@ -8,7 +8,7 @@
 
 #include <stdint.h>
 
-#include "base/strings/string_piece.h"
+#include <string_view>
 
 namespace gurl_base {
 
@@ -18,7 +18,7 @@
 }
 
 // Do not assert in this function since it is used by the asssertion code!
-std::wstring SysUTF8ToWide(StringPiece utf8) {
+std::wstring SysUTF8ToWide(std::string_view utf8) {
   return SysMultiByteToWide(utf8, CP_UTF8);
 }
 
@@ -26,21 +26,23 @@
   return SysWideToMultiByte(wide, CP_ACP);
 }
 
-std::wstring SysNativeMBToWide(StringPiece native_mb) {
+std::wstring SysNativeMBToWide(std::string_view native_mb) {
   return SysMultiByteToWide(native_mb, CP_ACP);
 }
 
 // Do not assert in this function since it is used by the asssertion code!
-std::wstring SysMultiByteToWide(StringPiece mb, uint32_t code_page) {
-  if (mb.empty())
+std::wstring SysMultiByteToWide(std::string_view mb, uint32_t code_page) {
+  if (mb.empty()) {
     return std::wstring();
+  }
 
   int mb_length = static_cast<int>(mb.length());
   // Compute the length of the buffer.
-  int charcount = MultiByteToWideChar(code_page, 0,
-                                      mb.data(), mb_length, NULL, 0);
-  if (charcount == 0)
+  int charcount =
+      MultiByteToWideChar(code_page, 0, mb.data(), mb_length, NULL, 0);
+  if (charcount == 0) {
     return std::wstring();
+  }
 
   std::wstring wide;
   wide.resize(static_cast<size_t>(charcount));
@@ -52,19 +54,21 @@
 // Do not assert in this function since it is used by the asssertion code!
 std::string SysWideToMultiByte(const std::wstring& wide, uint32_t code_page) {
   int wide_length = static_cast<int>(wide.length());
-  if (wide_length == 0)
+  if (wide_length == 0) {
     return std::string();
+  }
 
   // Compute the length of the buffer we'll need.
   int charcount = WideCharToMultiByte(code_page, 0, wide.data(), wide_length,
                                       NULL, 0, NULL, NULL);
-  if (charcount == 0)
+  if (charcount == 0) {
     return std::string();
+  }
 
   std::string mb;
   mb.resize(static_cast<size_t>(charcount));
-  WideCharToMultiByte(code_page, 0, wide.data(), wide_length,
-                      &mb[0], charcount, NULL, NULL);
+  WideCharToMultiByte(code_page, 0, wide.data(), wide_length, &mb[0], charcount,
+                      NULL, NULL);
 
   return mb;
 }
diff --git a/base/strings/to_string.cc b/base/strings/to_string.cc
new file mode 100644
index 0000000..cb2d4a5
--- /dev/null
+++ b/base/strings/to_string.cc
@@ -0,0 +1,26 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/to_string.h"
+
+#include <string>
+#include <string_view>
+
+#include "base/strings/utf_string_conversions.h"
+
+namespace gurl_base {
+
+std::string ToString(std::string_view sv) {
+  return std::string(sv);
+}
+
+std::string ToString(std::u16string_view sv) {
+  return UTF16ToUTF8(sv);
+}
+
+std::string ToString(std::wstring_view sv) {
+  return WideToUTF8(sv);
+}
+
+}  // namespace base
diff --git a/base/strings/to_string.h b/base/strings/to_string.h
index b873cd4..7df6ba7 100644
--- a/base/strings/to_string.h
+++ b/base/strings/to_string.h
@@ -5,81 +5,95 @@
 #ifndef BASE_STRINGS_TO_STRING_H_
 #define BASE_STRINGS_TO_STRING_H_
 
-#include <ios>
+#include <concepts>
 #include <memory>
 #include <sstream>
 #include <string>
+#include <string_view>
 #include <tuple>
 #include <type_traits>
 #include <utility>
 
-#include "base/template_util.h"
+#include "polyfills/base/base_export.h"
+#include "base/containers/span.h"
+#include "base/strings/string_view_util.h"
 #include "base/types/supports_ostream_operator.h"
+#include "base/types/supports_to_string.h"
 
 namespace gurl_base {
 
-template <typename... Ts>
-std::string ToString(const Ts&... values);
+template <typename T>
+std::string ToString(const T& values);
 
 namespace internal {
 
-template <typename T>
-concept SupportsToString = requires(const T& t) { t.ToString(); };
-
-// I/O manipulators are function pointers, but should be sent directly to the
-// `ostream` instead of being cast to `const void*` like other function
-// pointers.
-template <typename T, typename = void>
-constexpr bool IsIomanip = false;
-template <typename T>
-constexpr bool
-    IsIomanip<T&(T&), std::enable_if_t<std::is_base_of_v<std::ios_base, T>>> =
-        true;
-
 // Function pointers implicitly convert to `bool`, so use this to avoid printing
-// function pointers as 1 or 0.
-template <typename T, typename = void>
-constexpr bool WillBeIncorrectlyStreamedAsBool = false;
+// function pointers as "true"/"false".
 template <typename T>
-constexpr bool WillBeIncorrectlyStreamedAsBool<
-    T,
-    std::enable_if_t<std::is_function_v<std::remove_pointer_t<T>> &&
-                     !IsIomanip<std::remove_pointer_t<T>>>> = true;
+concept WillBeIncorrectlyStreamedAsBool =
+    std::is_function_v<std::remove_pointer_t<T>>;
 
 // Fallback case when there is no better representation.
-template <typename T, typename = void>
+template <typename T>
 struct ToStringHelper {
   static void Stringify(const T& v, std::ostringstream& ss) {
-    ss << "[" << sizeof(v) << "-byte object at 0x" << std::addressof(v) << "]";
+    // We cast to `void*` to avoid converting a char-like type to char-like*
+    // which operator<< treats as a string but does not support for multi-byte
+    // char-like types.
+    ss << "[" << sizeof(v) << "-byte object at 0x"
+       << static_cast<const void*>(std::addressof(v)) << "]";
+  }
+};
+
+// Boolean values. (Handled explicitly so as to not rely on the behavior of
+// std::boolalpha.)
+template <>
+struct ToStringHelper<bool> {
+  static void Stringify(const bool& v, std::ostringstream& ss) {
+    ss << (v ? "true" : "false");
   }
 };
 
 // Most streamables.
 template <typename T>
-struct ToStringHelper<T,
-                      std::enable_if_t<SupportsOstreamOperator<const T&> &&
-                                       !WillBeIncorrectlyStreamedAsBool<T>>> {
+  requires(SupportsOstreamOperator<const T&> &&
+           !WillBeIncorrectlyStreamedAsBool<T>)
+struct ToStringHelper<T> {
   static void Stringify(const T& v, std::ostringstream& ss) { ss << v; }
 };
 
 // Functions and function pointers.
 template <typename T>
-struct ToStringHelper<T,
-                      std::enable_if_t<SupportsOstreamOperator<const T&> &&
-                                       WillBeIncorrectlyStreamedAsBool<T>>> {
+  requires(SupportsOstreamOperator<const T&> &&
+           WillBeIncorrectlyStreamedAsBool<T>)
+struct ToStringHelper<T> {
   static void Stringify(const T& v, std::ostringstream& ss) {
     ToStringHelper<const void*>::Stringify(reinterpret_cast<const void*>(v),
                                            ss);
   }
 };
 
+// Integral types that can't stream, like char16_t.
+template <typename T>
+  requires(!SupportsOstreamOperator<const T&> && std::is_integral_v<T>)
+struct ToStringHelper<T> {
+  static void Stringify(const T& v, std::ostringstream& ss) {
+    if constexpr (std::is_signed_v<T>) {
+      static_assert(sizeof(T) <= 8);
+      ss << static_cast<int64_t>(v);
+    } else {
+      static_assert(sizeof(T) <= 8);
+      ss << static_cast<uint64_t>(v);
+    }
+  }
+};
+
 // Non-streamables that have a `ToString` member.
 template <typename T>
-struct ToStringHelper<T,
-                      std::enable_if_t<!SupportsOstreamOperator<const T&> &&
-                                       SupportsToString<const T&>>> {
+  requires(!SupportsOstreamOperator<const T&> && SupportsToString<const T&>)
+struct ToStringHelper<T> {
   static void Stringify(const T& v, std::ostringstream& ss) {
-    // .ToString() may not return a std::string, e.g. blink::WTF::String.
+    // .ToString() may not return a std::string, e.g. blink::String.
     ToStringHelper<decltype(v.ToString())>::Stringify(v.ToString(), ss);
   }
 };
@@ -87,9 +101,8 @@
 // Non-streamable enums (i.e. scoped enums where no `operator<<` overload was
 // declared).
 template <typename T>
-struct ToStringHelper<
-    T,
-    std::enable_if_t<!SupportsOstreamOperator<const T&> && std::is_enum_v<T>>> {
+  requires(!SupportsOstreamOperator<const T&> && std::is_enum_v<T>)
+struct ToStringHelper<T> {
   static void Stringify(const T& v, std::ostringstream& ss) {
     using UT = typename std::underlying_type_t<T>;
     ToStringHelper<UT>::Stringify(static_cast<UT>(v), ss);
@@ -116,16 +129,65 @@
 }  // namespace internal
 
 // Converts any type to a string, preferring defined operator<<() or ToString()
-// methods if they exist. If multiple `values` are given, returns the
-// concatenation of the result of applying `ToString` to each value.
-template <typename... Ts>
-std::string ToString(const Ts&... values) {
+// methods if they exist.
+template <typename T>
+std::string ToString(const T& value) {
   std::ostringstream ss;
-  (..., internal::ToStringHelper<remove_cvref_t<decltype(values)>>::Stringify(
-            values, ss));
+  internal::ToStringHelper<std::remove_cvref_t<decltype(value)>>::Stringify(
+      value, ss);
   return ss.str();
 }
 
+BASE_EXPORT std::string ToString(std::string_view sv);
+BASE_EXPORT std::string ToString(std::u16string_view sv);
+BASE_EXPORT std::string ToString(std::wstring_view sv);
+
+namespace to_string_internal {
+
+template <typename T>
+concept SpanConvertsToStringView = requires {
+  { as_string_view(span<T>()) };
+};
+
+}  // namespace to_string_internal
+
+// Stringify gurl_base::span, hopefully in a way that's useful for tests.
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(to_string_internal::SpanConvertsToStringView<ElementType> ||
+           requires(const ElementType& t) {
+             { ToString(t) };
+           })
+constexpr std::string ToString(span<ElementType, Extent, InternalPtrType> r) {
+  std::string out = "[";
+  if constexpr (to_string_internal::SpanConvertsToStringView<ElementType>) {
+    const auto sv = as_string_view(r);
+    using T = std::remove_cvref_t<ElementType>;
+    if constexpr (std::same_as<wchar_t, T>) {
+      out += "L\"";
+      out += ToString(sv);
+    } else if constexpr (std::same_as<char16_t, T>) {
+      out += "u\"";
+      out += ToString(sv);
+    } else {
+      out += "\"";
+      out += sv;
+    }
+    out += '\"';
+  } else if constexpr (Extent != 0) {
+    // It would be nice to use `JoinString()` here, but making that `constexpr`
+    // is more trouble than it's worth.
+    if (!r.empty()) {
+      out += ToString(r.front());
+      for (const ElementType& e : r.template subspan<1>()) {
+        out += ", ";
+        out += ToString(e);
+      }
+    }
+  }
+  out += "]";
+  return out;
+}
+
 }  // namespace base
 
 #endif  // BASE_STRINGS_TO_STRING_H_
diff --git a/base/strings/to_string_unittest.cc b/base/strings/to_string_unittest.cc
index 840b301..8e2f083 100644
--- a/base/strings/to_string_unittest.cc
+++ b/base/strings/to_string_unittest.cc
@@ -8,6 +8,8 @@
 #include <ostream>
 #include <string>
 
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace gurl_base {
@@ -37,6 +39,12 @@
 static_assert(internal::SupportsToString<HasToString&&>,
               "&& with ToString() should be marked SupportsToString");
 
+// Booleans should stringify specifically as "true" and "false".
+TEST(ToStringTest, Booleans) {
+  EXPECT_EQ(ToString(true), "true");
+  EXPECT_EQ(ToString(false), "false");
+}
+
 TEST(ToStringTest, Streamable) {
   // Types with built-in <<.
   EXPECT_EQ(ToString("foo"), "foo");
@@ -57,9 +65,6 @@
 TEST(ToStringTest, UserDefinedStreamable) {
   // Type with user-defined <<.
   EXPECT_EQ(ToString(StreamableTestEnum::kGreeting), "hello");
-  EXPECT_EQ(ToString(StreamableTestEnum::kGreeting, " ",
-                     StreamableTestEnum::kLocation),
-            "hello world");
 }
 
 TEST(ToStringTest, UserDefinedToString) {
@@ -85,10 +90,9 @@
   EXPECT_EQ(ToString(NonStreamableTestEnum::kLocation), "1");
 }
 
-TEST(ToStringTest, IoManip) {
-  // I/O manipulators should have their expected effect, not be printed as
-  // function pointers.
-  EXPECT_EQ(ToString("42 in hex is ", std::hex, 42), "42 in hex is 2a");
+TEST(ToStringTest, WideChars) {
+  EXPECT_EQ(ToString(u'a'), "97");
+  EXPECT_EQ(ToString(L'a'), "97");
 }
 
 TEST(ToStringTest, Tuple) {
@@ -109,6 +113,21 @@
   EXPECT_EQ(ToString(Func), ToString(&Func));
 }
 
+TEST(ToStringTest, Pointer) {
+  int i = 42;
+  std::string result_string = ToString(&i);
+
+  // The result of ToString() on a pointer is a string that begins with "0x".
+  ASSERT_GT(result_string.size(), 2);
+  EXPECT_EQ(result_string.substr(0, 2), "0x");
+
+  // ... and whose contents is the hex representation of the value of the actual
+  // pointer value.
+  uint64_t result_int;
+  ASSERT_TRUE(HexStringToUInt64(result_string, &result_int));
+  EXPECT_EQ(reinterpret_cast<uintptr_t>(&i), result_int);
+}
+
 class OverloadsAddressOp {
  public:
   OverloadsAddressOp* operator&() { return nullptr; }
@@ -126,5 +145,24 @@
             ToString(static_cast<OverloadsAddressOp*>(nullptr)));
 }
 
+TEST(ToStringTest, Span) {
+  struct S {
+    std::string ToString() const { return "S()"; }
+  };
+
+  EXPECT_EQ(ToString(span<const int>({1, 2, 3})), "[1, 2, 3]");
+  EXPECT_EQ(ToString(span<const S>({S(), S()})), "[S(), S()]");
+  EXPECT_EQ(ToString(span<const char>({'a', 'b', 'c'})), "[\"abc\"]");
+  EXPECT_EQ(ToString(span<const char>({'a', 'b', 'c', '\0'})),
+            std::string_view("[\"abc\0\"]", 8u));
+  EXPECT_EQ(ToString(span<const char>({'a', 'b', '\0', 'c', '\0'})),
+            std::string_view("[\"ab\0c\0\"]", 9u));
+  EXPECT_EQ(ToString(span<int>()), "[]");
+  EXPECT_EQ(ToString(span<char>()), "[\"\"]");
+
+  EXPECT_EQ(ToString(span<const char16_t>({u'a', u'b', u'c'})), "[u\"abc\"]");
+  EXPECT_EQ(ToString(span<const wchar_t>({L'a', L'b', L'c'})), "[L\"abc\"]");
+}
+
 }  // namespace
 }  // namespace base
diff --git a/base/strings/utf_offset_string_conversions.cc b/base/strings/utf_offset_string_conversions.cc
index f893b89..cf6b2a6 100644
--- a/base/strings/utf_offset_string_conversions.cc
+++ b/base/strings/utf_offset_string_conversions.cc
@@ -8,9 +8,9 @@
 
 #include <algorithm>
 #include <memory>
+#include <string_view>
 
 #include "polyfills/base/check_op.h"
-#include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversion_utils.h"
 
 namespace gurl_base {
@@ -20,16 +20,16 @@
                                        size_t output_length)
     : original_offset(original_offset),
       original_length(original_length),
-      output_length(output_length) {
-}
+      output_length(output_length) {}
 
 // static
 void OffsetAdjuster::AdjustOffsets(const Adjustments& adjustments,
                                    std::vector<size_t>* offsets_for_adjustment,
                                    size_t limit) {
   GURL_DCHECK(offsets_for_adjustment);
-  for (auto& i : *offsets_for_adjustment)
+  for (auto& i : *offsets_for_adjustment) {
     AdjustOffset(adjustments, &i, limit);
+  }
 }
 
 // static
@@ -37,13 +37,15 @@
                                   size_t* offset,
                                   size_t limit) {
   GURL_DCHECK(offset);
-  if (*offset == std::u16string::npos)
+  if (*offset == std::u16string::npos) {
     return;
+  }
   size_t original_lengths = 0;
   size_t output_lengths = 0;
   for (const auto& i : adjustments) {
-    if (*offset <= i.original_offset)
+    if (*offset <= i.original_offset) {
       break;
+    }
     if (*offset < (i.original_offset + i.original_length)) {
       *offset = std::u16string::npos;
       return;
@@ -53,30 +55,35 @@
   }
   *offset += output_lengths - original_lengths;
 
-  if (*offset > limit)
+  if (*offset > limit) {
     *offset = std::u16string::npos;
+  }
 }
 
 // static
 void OffsetAdjuster::UnadjustOffsets(
     const Adjustments& adjustments,
     std::vector<size_t>* offsets_for_unadjustment) {
-  if (!offsets_for_unadjustment || adjustments.empty())
+  if (!offsets_for_unadjustment || adjustments.empty()) {
     return;
-  for (auto& i : *offsets_for_unadjustment)
+  }
+  for (auto& i : *offsets_for_unadjustment) {
     UnadjustOffset(adjustments, &i);
+  }
 }
 
 // static
 void OffsetAdjuster::UnadjustOffset(const Adjustments& adjustments,
                                     size_t* offset) {
-  if (*offset == std::u16string::npos)
+  if (*offset == std::u16string::npos) {
     return;
+  }
   size_t original_lengths = 0;
   size_t output_lengths = 0;
   for (const auto& i : adjustments) {
-    if (*offset + original_lengths - output_lengths <= i.original_offset)
+    if (*offset + original_lengths - output_lengths <= i.original_offset) {
       break;
+    }
     original_lengths += i.original_length;
     output_lengths += i.output_length;
     if ((*offset + original_lengths - output_lengths) <
@@ -186,13 +193,14 @@
 // the result.  If non-NULL, |adjustments| is set to reflect the all the
 // alterations to the string that are not one-character-to-one-character.
 // It will always be sorted by increasing offset.
-template<typename SrcChar, typename DestStdString>
+template <typename SrcChar, typename DestStdString>
 bool ConvertUnicode(const SrcChar* src,
                     size_t src_len,
                     DestStdString* output,
                     OffsetAdjuster::Adjustments* adjustments) {
-  if (adjustments)
+  if (adjustments) {
     adjustments->clear();
+  }
   bool success = true;
   for (size_t i = 0; i < src_len; i++) {
     base_icu::UChar32 code_point;
@@ -212,8 +220,7 @@
     // increment will place it at the right location), so we need to account
     // for that in determining the amount that was read.
     if (adjustments && ((i - original_i + 1) != chars_written)) {
-      adjustments->push_back(OffsetAdjuster::Adjustment(
-          original_i, i - original_i + 1, chars_written));
+      adjustments->emplace_back(original_i, i - original_i + 1, chars_written);
     }
   }
   return success;
@@ -229,7 +236,7 @@
 }
 
 std::u16string UTF8ToUTF16WithAdjustments(
-    const gurl_base::StringPiece& utf8,
+    std::string_view utf8,
     gurl_base::OffsetAdjuster::Adjustments* adjustments) {
   std::u16string result;
   UTF8ToUTF16WithAdjustments(utf8.data(), utf8.length(), &result, adjustments);
@@ -237,11 +244,12 @@
 }
 
 std::u16string UTF8ToUTF16AndAdjustOffsets(
-    const gurl_base::StringPiece& utf8,
+    std::string_view utf8,
     std::vector<size_t>* offsets_for_adjustment) {
   for (size_t& offset : *offsets_for_adjustment) {
-    if (offset > utf8.length())
+    if (offset > utf8.length()) {
       offset = std::u16string::npos;
+    }
   }
   OffsetAdjuster::Adjustments adjustments;
   std::u16string result = UTF8ToUTF16WithAdjustments(utf8, &adjustments);
@@ -250,11 +258,12 @@
 }
 
 std::string UTF16ToUTF8AndAdjustOffsets(
-    const gurl_base::StringPiece16& utf16,
+    std::u16string_view utf16,
     std::vector<size_t>* offsets_for_adjustment) {
   for (size_t& offset : *offsets_for_adjustment) {
-    if (offset > utf16.length())
+    if (offset > utf16.length()) {
       offset = std::u16string::npos;
+    }
   }
   std::string result;
   PrepareForUTF8Output(utf16.data(), utf16.length(), &result);
diff --git a/base/strings/utf_offset_string_conversions.h b/base/strings/utf_offset_string_conversions.h
index 03b2e7a..70b28e9 100644
--- a/base/strings/utf_offset_string_conversions.h
+++ b/base/strings/utf_offset_string_conversions.h
@@ -8,10 +8,10 @@
 #include <stddef.h>
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 
@@ -65,8 +65,7 @@
 
   // Adjusts the single |offset| to reflect the reverse of the adjustments
   // recorded in |adjustments|.
-  static void UnadjustOffset(const Adjustments& adjustments,
-                             size_t* offset);
+  static void UnadjustOffset(const Adjustments& adjustments, size_t* offset);
 
   // Combines two sequential sets of adjustments, storing the combined revised
   // adjustments in |adjustments_on_adjusted_string|.  That is, suppose a
@@ -97,17 +96,17 @@
     std::u16string* output,
     gurl_base::OffsetAdjuster::Adjustments* adjustments);
 [[nodiscard]] BASE_EXPORT std::u16string UTF8ToUTF16WithAdjustments(
-    const gurl_base::StringPiece& utf8,
+    std::string_view utf8,
     gurl_base::OffsetAdjuster::Adjustments* adjustments);
 // As above, but instead internally examines the adjustments and applies them
 // to |offsets_for_adjustment|.  Input offsets greater than the length of the
 // input string will be set to std::u16string::npos.  See comments by
 // AdjustOffsets().
 BASE_EXPORT std::u16string UTF8ToUTF16AndAdjustOffsets(
-    const gurl_base::StringPiece& utf8,
+    std::string_view utf8,
     std::vector<size_t>* offsets_for_adjustment);
 BASE_EXPORT std::string UTF16ToUTF8AndAdjustOffsets(
-    const gurl_base::StringPiece16& utf16,
+    std::u16string_view utf16,
     std::vector<size_t>* offsets_for_adjustment);
 
 }  // namespace base
diff --git a/base/strings/utf_offset_string_conversions_unittest.cc b/base/strings/utf_offset_string_conversions_unittest.cc
index 1f3e6db..a6ede89 100644
--- a/base/strings/utf_offset_string_conversions_unittest.cc
+++ b/base/strings/utf_offset_string_conversions_unittest.cc
@@ -7,8 +7,8 @@
 #include <stddef.h>
 
 #include <algorithm>
+#include <array>
 
-#include "base/strings/string_piece.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace gurl_base {
@@ -25,16 +25,16 @@
     size_t input_offset;
     size_t output_offset;
   } utf8_to_utf16_cases[] = {
-    {"", 0, 0},
-    {"", kNpos, kNpos},
-    {"\xe4\xbd\xa0\xe5\xa5\xbd", 1, kNpos},
-    {"\xe4\xbd\xa0\xe5\xa5\xbd", 3, 1},
-    {"\xed\xb0\x80z", 3, 3},
-    {"A\xF0\x90\x8C\x80z", 1, 1},
-    {"A\xF0\x90\x8C\x80z", 2, kNpos},
-    {"A\xF0\x90\x8C\x80z", 5, 3},
-    {"A\xF0\x90\x8C\x80z", 6, 4},
-    {"A\xF0\x90\x8C\x80z", kNpos, kNpos},
+      {"", 0, 0},
+      {"", kNpos, kNpos},
+      {"\xe4\xbd\xa0\xe5\xa5\xbd", 1, kNpos},
+      {"\xe4\xbd\xa0\xe5\xa5\xbd", 3, 1},
+      {"\xed\xb0\x80z", 3, 3},
+      {"A\xF0\x90\x8C\x80z", 1, 1},
+      {"A\xF0\x90\x8C\x80z", 2, kNpos},
+      {"A\xF0\x90\x8C\x80z", 5, 3},
+      {"A\xF0\x90\x8C\x80z", 6, 4},
+      {"A\xF0\x90\x8C\x80z", kNpos, kNpos},
   };
   for (const auto& i : utf8_to_utf16_cases) {
     const size_t offset = i.input_offset;
@@ -48,7 +48,8 @@
     char16_t utf16[10];
     size_t input_offset;
     size_t output_offset;
-  } utf16_to_utf8_cases[] = {
+  };
+  auto utf16_to_utf8_cases = std::to_array<UTF16ToUTF8Case>({
       {{}, 0, 0},
       // Converted to 3-byte utf-8 sequences
       {{0x5909, 0x63DB}, 3, kNpos},
@@ -65,7 +66,7 @@
       {{'A', 0xd800, 0xdf00, 'z'}, 2, kNpos},
       {{'A', 0xd800, 0xdf00, 'z'}, 3, 5},
       {{'A', 0xd800, 0xdf00, 'z'}, 4, 6},
-  };
+  });
   for (size_t i = 0; i < std::size(utf16_to_utf8_cases); ++i) {
     size_t offset = utf16_to_utf8_cases[i].input_offset;
     std::vector<size_t> offsets;
@@ -86,8 +87,9 @@
   }
   size_t unlimited_count = 0;
   for (auto ti : size_ts) {
-    if (ti != kNpos)
+    if (ti != kNpos) {
       ++unlimited_count;
+    }
   }
   EXPECT_EQ(11U, unlimited_count);
 
@@ -99,8 +101,9 @@
   }
   unlimited_count = 0;
   for (auto ti : size_ts) {
-    if (ti != kNpos)
+    if (ti != kNpos) {
       ++unlimited_count;
+    }
   }
   EXPECT_EQ(11U, unlimited_count);
 }
@@ -111,55 +114,79 @@
   // 1: abcXXXdef ==> abcXdef
   {
     std::vector<size_t> offsets;
-    for (size_t t = 0; t <= 9; ++t)
+    for (size_t t = 0; t <= 9; ++t) {
       offsets.push_back(t);
+    }
     OffsetAdjuster::Adjustments adjustments;
-    adjustments.push_back(OffsetAdjuster::Adjustment(3, 3, 1));
+    adjustments.emplace_back(3, 3, 1);
     OffsetAdjuster::AdjustOffsets(adjustments, &offsets);
-    size_t expected_1[] = {0, 1, 2, 3, kNpos, kNpos, 4, 5, 6, 7};
+    auto expected_1 =
+        std::to_array<size_t>({0, 1, 2, 3, kNpos, kNpos, 4, 5, 6, 7});
     EXPECT_EQ(offsets.size(), std::size(expected_1));
-    for (size_t i = 0; i < std::size(expected_1); ++i)
+    for (size_t i = 0; i < std::size(expected_1); ++i) {
       EXPECT_EQ(expected_1[i], offsets[i]);
+    }
   }
 
   // 2: XXXaXXXXbcXXXXXXXdefXXX ==> XaXXbcXXXXdefX
   {
     std::vector<size_t> offsets;
-    for (size_t t = 0; t <= 23; ++t)
+    for (size_t t = 0; t <= 23; ++t) {
       offsets.push_back(t);
+    }
     OffsetAdjuster::Adjustments adjustments;
-    adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 1));
-    adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 2));
-    adjustments.push_back(OffsetAdjuster::Adjustment(10, 7, 4));
-    adjustments.push_back(OffsetAdjuster::Adjustment(20, 3, 1));
+    adjustments.emplace_back(0, 3, 1);
+    adjustments.emplace_back(4, 4, 2);
+    adjustments.emplace_back(10, 7, 4);
+    adjustments.emplace_back(20, 3, 1);
     OffsetAdjuster::AdjustOffsets(adjustments, &offsets);
-    size_t expected_2[] = {
-      0, kNpos, kNpos, 1, 2, kNpos, kNpos, kNpos, 4, 5, 6, kNpos, kNpos, kNpos,
-      kNpos, kNpos, kNpos, 10, 11, 12, 13, kNpos, kNpos, 14
-    };
+    auto expected_2 = std::to_array<size_t>({
+        0,     kNpos, kNpos, 1,     2,     kNpos, kNpos, kNpos,
+        4,     5,     6,     kNpos, kNpos, kNpos, kNpos, kNpos,
+        kNpos, 10,    11,    12,    13,    kNpos, kNpos, 14,
+    });
     EXPECT_EQ(offsets.size(), std::size(expected_2));
-    for (size_t i = 0; i < std::size(expected_2); ++i)
+    for (size_t i = 0; i < std::size(expected_2); ++i) {
       EXPECT_EQ(expected_2[i], offsets[i]);
+    }
   }
 
   // 3: XXXaXXXXbcdXXXeXX ==> aXXXXbcdXXXe
   {
     std::vector<size_t> offsets;
-    for (size_t t = 0; t <= 17; ++t)
+    for (size_t t = 0; t <= 17; ++t) {
       offsets.push_back(t);
+    }
     OffsetAdjuster::Adjustments adjustments;
-    adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 0));
-    adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 4));
-    adjustments.push_back(OffsetAdjuster::Adjustment(11, 3, 3));
-    adjustments.push_back(OffsetAdjuster::Adjustment(15, 2, 0));
+    adjustments.emplace_back(0, 3, 0);
+    adjustments.emplace_back(4, 4, 4);
+    adjustments.emplace_back(11, 3, 3);
+    adjustments.emplace_back(15, 2, 0);
     OffsetAdjuster::AdjustOffsets(adjustments, &offsets);
-    size_t expected_3[] = {
-      0, kNpos, kNpos, 0, 1, kNpos, kNpos, kNpos, 5, 6, 7, 8, kNpos, kNpos, 11,
-      12, kNpos, 12
-    };
+    auto expected_3 = std::to_array<size_t>({
+        0,
+        kNpos,
+        kNpos,
+        0,
+        1,
+        kNpos,
+        kNpos,
+        kNpos,
+        5,
+        6,
+        7,
+        8,
+        kNpos,
+        kNpos,
+        11,
+        12,
+        kNpos,
+        12,
+    });
     EXPECT_EQ(offsets.size(), std::size(expected_3));
-    for (size_t i = 0; i < std::size(expected_3); ++i)
+    for (size_t i = 0; i < std::size(expected_3); ++i) {
       EXPECT_EQ(expected_3[i], offsets[i]);
+    }
   }
 }
 
@@ -169,55 +196,75 @@
   // 1: abcXXXdef ==> abcXdef
   {
     std::vector<size_t> offsets;
-    for (size_t t = 0; t <= 7; ++t)
+    for (size_t t = 0; t <= 7; ++t) {
       offsets.push_back(t);
+    }
     OffsetAdjuster::Adjustments adjustments;
-    adjustments.push_back(OffsetAdjuster::Adjustment(3, 3, 1));
+    adjustments.emplace_back(3, 3, 1);
     OffsetAdjuster::UnadjustOffsets(adjustments, &offsets);
-    size_t expected_1[] = {0, 1, 2, 3, 6, 7, 8, 9};
+    auto expected_1 = std::to_array<size_t>({0, 1, 2, 3, 6, 7, 8, 9});
     EXPECT_EQ(offsets.size(), std::size(expected_1));
-    for (size_t i = 0; i < std::size(expected_1); ++i)
+    for (size_t i = 0; i < std::size(expected_1); ++i) {
       EXPECT_EQ(expected_1[i], offsets[i]);
+    }
   }
 
   // 2: XXXaXXXXbcXXXXXXXdefXXX ==> XaXXbcXXXXdefX
   {
     std::vector<size_t> offsets;
-    for (size_t t = 0; t <= 14; ++t)
+    for (size_t t = 0; t <= 14; ++t) {
       offsets.push_back(t);
+    }
     OffsetAdjuster::Adjustments adjustments;
-    adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 1));
-    adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 2));
-    adjustments.push_back(OffsetAdjuster::Adjustment(10, 7, 4));
-    adjustments.push_back(OffsetAdjuster::Adjustment(20, 3, 1));
+    adjustments.emplace_back(0, 3, 1);
+    adjustments.emplace_back(4, 4, 2);
+    adjustments.emplace_back(10, 7, 4);
+    adjustments.emplace_back(20, 3, 1);
     OffsetAdjuster::UnadjustOffsets(adjustments, &offsets);
-    size_t expected_2[] = {
-      0, 3, 4, kNpos, 8, 9, 10, kNpos, kNpos, kNpos, 17, 18, 19, 20, 23
-    };
+    auto expected_2 = std::to_array<size_t>({
+        0,
+        3,
+        4,
+        kNpos,
+        8,
+        9,
+        10,
+        kNpos,
+        kNpos,
+        kNpos,
+        17,
+        18,
+        19,
+        20,
+        23,
+    });
     EXPECT_EQ(offsets.size(), std::size(expected_2));
-    for (size_t i = 0; i < std::size(expected_2); ++i)
+    for (size_t i = 0; i < std::size(expected_2); ++i) {
       EXPECT_EQ(expected_2[i], offsets[i]);
+    }
   }
 
   // 3: XXXaXXXXbcdXXXeXX ==> aXXXXbcdXXXe
   {
     std::vector<size_t> offsets;
-    for (size_t t = 0; t <= 12; ++t)
+    for (size_t t = 0; t <= 12; ++t) {
       offsets.push_back(t);
+    }
     OffsetAdjuster::Adjustments adjustments;
-    adjustments.push_back(OffsetAdjuster::Adjustment(0, 3, 0));
-    adjustments.push_back(OffsetAdjuster::Adjustment(4, 4, 4));
-    adjustments.push_back(OffsetAdjuster::Adjustment(11, 3, 3));
-    adjustments.push_back(OffsetAdjuster::Adjustment(15, 2, 0));
+    adjustments.emplace_back(0, 3, 0);
+    adjustments.emplace_back(4, 4, 4);
+    adjustments.emplace_back(11, 3, 3);
+    adjustments.emplace_back(15, 2, 0);
     OffsetAdjuster::UnadjustOffsets(adjustments, &offsets);
-    size_t expected_3[] = {
-      0,  // this could just as easily be 3
-      4, kNpos, kNpos, kNpos, 8, 9, 10, 11, kNpos, kNpos, 14,
-      15  // this could just as easily be 17
-    };
+    auto expected_3 = std::to_array<size_t>({
+        0,  // this could just as easily be 3
+        4, kNpos, kNpos, kNpos, 8, 9, 10, 11, kNpos, kNpos, 14,
+        15,  // this could just as easily be 17
+    });
     EXPECT_EQ(offsets.size(), std::size(expected_3));
-    for (size_t i = 0; i < std::size(expected_3); ++i)
+    for (size_t i = 0; i < std::size(expected_3); ++i) {
       EXPECT_EQ(expected_3[i], offsets[i]);
+    }
   }
 }
 
@@ -235,10 +282,10 @@
   // - remove the "tuv"
   // The resulting string should be ".deghijklmnopqrswxyz".
   OffsetAdjuster::Adjustments first_adjustments;
-  first_adjustments.push_back(OffsetAdjuster::Adjustment(0, 1, 0));
-  first_adjustments.push_back(OffsetAdjuster::Adjustment(1, 2, 1));
-  first_adjustments.push_back(OffsetAdjuster::Adjustment(5, 1, 0));
-  first_adjustments.push_back(OffsetAdjuster::Adjustment(19, 3, 0));
+  first_adjustments.emplace_back(0, 1, 0);
+  first_adjustments.emplace_back(1, 2, 1);
+  first_adjustments.emplace_back(5, 1, 0);
+  first_adjustments.emplace_back(19, 3, 0);
 
   // Set up |adjustments_on_adjusted_string| to
   // - combine the "." character that replaced "bc" with "d" into one character
@@ -249,16 +296,11 @@
   // - expand the "z" into two characters (call it "34")
   // The resulting string should be "?12@mnopqrswxy34".
   OffsetAdjuster::Adjustments adjustments_on_adjusted_string;
-  adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
-      0, 2, 1));
-  adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
-      2, 3, 0));
-  adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
-      5, 1, 2));
-  adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
-      6, 3, 1));
-  adjustments_on_adjusted_string.push_back(OffsetAdjuster::Adjustment(
-      19, 1, 2));
+  adjustments_on_adjusted_string.emplace_back(0, 2, 1);
+  adjustments_on_adjusted_string.emplace_back(2, 3, 0);
+  adjustments_on_adjusted_string.emplace_back(5, 1, 2);
+  adjustments_on_adjusted_string.emplace_back(6, 3, 1);
+  adjustments_on_adjusted_string.emplace_back(19, 1, 2);
 
   // Now merge the adjustments and check the results.
   OffsetAdjuster::MergeSequentialAdjustments(first_adjustments,
diff --git a/base/strings/utf_string_conversion_utils.cc b/base/strings/utf_string_conversion_utils.cc
index 261d730..94bda69 100644
--- a/base/strings/utf_string_conversion_utils.cc
+++ b/base/strings/utf_string_conversion_utils.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "base/strings/utf_string_conversion_utils.h"
 
 #include "base/third_party/icu/icu_utf.h"
@@ -11,14 +16,14 @@
 
 // CountUnicodeCharacters ------------------------------------------------------
 
-absl::optional<size_t> CountUnicodeCharacters(std::string_view text,
-                                              size_t limit) {
+std::optional<size_t> CountUnicodeCharacters(std::string_view text,
+                                             size_t limit) {
   base_icu::UChar32 unused = 0;
   size_t count = 0;
   for (size_t index = 0; count < limit && index < text.size();
        ++count, ++index) {
     if (!ReadUnicodeCharacter(text.data(), text.size(), &index, &unused)) {
-      return absl::nullopt;
+      return std::nullopt;
     }
   }
   return count;
@@ -55,8 +60,8 @@
     }
 
     // Valid surrogate pair.
-    *code_point = CBU16_GET_SUPPLEMENTARY(src[*char_index],
-                                          src[*char_index + 1]);
+    *code_point =
+        CBU16_GET_SUPPLEMENTARY(src[*char_index], src[*char_index + 1]);
     (*char_index)++;
   } else {
     // Not a surrogate, just one 16-bit word.
@@ -119,13 +124,14 @@
 
 // Generalized Unicode converter -----------------------------------------------
 
-template<typename CHAR>
+template <typename CHAR>
 void PrepareForUTF8Output(const CHAR* src,
                           size_t src_len,
                           std::string* output) {
   output->clear();
-  if (src_len == 0)
+  if (src_len == 0) {
     return;
+  }
   if (src[0] < 0x80) {
     // Assume that the entire input will be ASCII.
     output->reserve(src_len);
@@ -142,13 +148,14 @@
 #endif
 template void PrepareForUTF8Output(const char16_t*, size_t, std::string*);
 
-template<typename STRING>
+template <typename STRING>
 void PrepareForUTF16Or32Output(const char* src,
                                size_t src_len,
                                STRING* output) {
   output->clear();
-  if (src_len == 0)
+  if (src_len == 0) {
     return;
+  }
   if (static_cast<unsigned char>(src[0]) < 0x80) {
     // Assume the input is all ASCII, which means 1:1 correspondence.
     output->reserve(src_len);
diff --git a/base/strings/utf_string_conversion_utils.h b/base/strings/utf_string_conversion_utils.h
index 3dca4b7..283e863 100644
--- a/base/strings/utf_string_conversion_utils.h
+++ b/base/strings/utf_string_conversion_utils.h
@@ -12,13 +12,13 @@
 #include <stdint.h>
 
 #include <limits>
+#include <optional>
 #include <string>
 #include <string_view>
 
 #include "polyfills/base/base_export.h"
 #include "base/third_party/icu/icu_utf.h"
 #include "build/build_config.h"
-#include "absl/types/optional.h"
 
 namespace gurl_base {
 
@@ -46,7 +46,7 @@
 
 // Returns the number of Unicode characters in `text`, up to the supplied
 // `limit`, if `text` contains valid UTF-8. Returns `nullopt` otherwise.
-BASE_EXPORT absl::optional<size_t> CountUnicodeCharacters(
+BASE_EXPORT std::optional<size_t> CountUnicodeCharacters(
     std::string_view text,
     size_t limit = std::numeric_limits<size_t>::max());
 
@@ -59,12 +59,20 @@
 // (as in a for loop) will take the reader to the next character.
 //
 // Returns true on success. On false, |*code_point| will be invalid.
+// TODO(crbug.com/40284755): implement spanified version (or use string view).
+// BASE_EXPORT bool ReadUnicodeCharacter(gurl_base::span<const char> src,
+//                                       size_t* char_index,
+//                                       base_icu::UChar32* code_point_out);
 BASE_EXPORT bool ReadUnicodeCharacter(const char* src,
                                       size_t src_len,
                                       size_t* char_index,
                                       base_icu::UChar32* code_point_out);
 
 // Reads a UTF-16 character. The usage is the same as the 8-bit version above.
+// TODO(crbug.com/40284755): implement spanified version (or use string view).
+// BASE_EXPORT bool ReadUnicodeCharacter(gurl_base::span<const char16_t> src,
+//                                       size_t* char_index,
+//                                       base_icu::UChar32* code_point);
 BASE_EXPORT bool ReadUnicodeCharacter(const char16_t* src,
                                       size_t src_len,
                                       size_t* char_index,
@@ -72,6 +80,10 @@
 
 #if defined(WCHAR_T_IS_32_BIT)
 // Reads UTF-32 character. The usage is the same as the 8-bit version above.
+// TODO(crbug.com/40284755): implement spanified version (or use string view).
+// BASE_EXPORT bool ReadUnicodeCharacter(gurl_base::span<const wchar_t> src,
+//                                       size_t* char_index,
+//                                       base_icu::UChar32* code_point);
 BASE_EXPORT bool ReadUnicodeCharacter(const wchar_t* src,
                                       size_t src_len,
                                       size_t* char_index,
@@ -107,12 +119,18 @@
 // string, and reserves that amount of space.  We assume that the input
 // character types are unsigned, which will be true for UTF-16 and -32 on our
 // systems.
-template<typename CHAR>
+// TODO(crbug.com/40284755): implement spanified version.
+// template <typename CHAR>
+// void PrepareForUTF8Output(gurl_base::span<const CHAR> src, std::string* output);
+template <typename CHAR>
 void PrepareForUTF8Output(const CHAR* src, size_t src_len, std::string* output);
 
 // Prepares an output buffer (containing either UTF-16 or -32 data) given some
 // UTF-8 input that will be converted to it.  See PrepareForUTF8Output().
-template<typename STRING>
+// TODO(crbug.com/40284755): implement spanified version.
+// template <typename STRING>
+// void PrepareForUTF16Or32Output(gurl_base::span<const char> src, STRING* output);
+template <typename STRING>
 void PrepareForUTF16Or32Output(const char* src, size_t src_len, STRING* output);
 
 }  // namespace base
diff --git a/base/strings/utf_string_conversion_utils_unittest.cc b/base/strings/utf_string_conversion_utils_unittest.cc
index 10f9201..782dc46 100644
--- a/base/strings/utf_string_conversion_utils_unittest.cc
+++ b/base/strings/utf_string_conversion_utils_unittest.cc
@@ -6,7 +6,6 @@
 
 #include <string>
 
-#include "base/strings/string_piece.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace gurl_base {
@@ -15,7 +14,7 @@
   const struct TestCase {
     std::string value;
     size_t limit;
-    absl::optional<size_t> count;
+    std::optional<size_t> count;
   } test_cases[] = {
       {"", 0, 0},
       {"abc", 1, 1},
@@ -26,7 +25,7 @@
       // trigger linter errors about invalid ascii values.
       {reinterpret_cast<const char*>(u8"abc\U0001F4A9"), 4, 4},
       {reinterpret_cast<const char*>(u8"\U0001F4A9"), 1, 1},
-      {{1, static_cast<char>(-1)}, 5, absl::nullopt},
+      {{1, static_cast<char>(-1)}, 5, std::nullopt},
   };
   for (const auto& test_case : test_cases) {
     EXPECT_EQ(CountUnicodeCharacters(test_case.value, test_case.limit),
diff --git a/base/strings/utf_string_conversions.cc b/base/strings/utf_string_conversions.cc
index 31028fb..14fc779 100644
--- a/base/strings/utf_string_conversions.cc
+++ b/base/strings/utf_string_conversions.cc
@@ -2,16 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "base/strings/utf_string_conversions.h"
 
 #include <limits.h>
 #include <stdint.h>
 
+#include <concepts>
 #include <ostream>
 #include <string_view>
 #include <type_traits>
 
-#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_ostream_operators.h"
 #include "base/strings/utf_string_conversion_utils.h"
@@ -68,25 +73,26 @@
 // Convenience typedef that checks whether the passed in type is integral (i.e.
 // bool, char, int or their extended versions) and is of the correct size.
 template <typename Char, size_t N>
-using EnableIfBitsAre =
-    std::enable_if_t<std::is_integral_v<Char> && CHAR_BIT * sizeof(Char) == N,
-                     bool>;
+concept BitsAre = std::integral<Char> && CHAR_BIT * sizeof(Char) == N;
 
-template <typename Char, EnableIfBitsAre<Char, 8> = true>
+template <typename Char>
+  requires(BitsAre<Char, 8>)
 void UnicodeAppendUnsafe(Char* out,
                          size_t* size,
                          base_icu::UChar32 code_point) {
   CBU8_APPEND_UNSAFE(reinterpret_cast<uint8_t*>(out), *size, code_point);
 }
 
-template <typename Char, EnableIfBitsAre<Char, 16> = true>
+template <typename Char>
+  requires(BitsAre<Char, 16>)
 void UnicodeAppendUnsafe(Char* out,
                          size_t* size,
                          base_icu::UChar32 code_point) {
   CBU16_APPEND_UNSAFE(out, *size, code_point);
 }
 
-template <typename Char, EnableIfBitsAre<Char, 32> = true>
+template <typename Char>
+  requires(BitsAre<Char, 32>)
 void UnicodeAppendUnsafe(Char* out,
                          size_t* size,
                          base_icu::UChar32 code_point) {
@@ -222,10 +228,10 @@
 // UTF16 <-> UTF8 --------------------------------------------------------------
 
 bool UTF8ToUTF16(const char* src, size_t src_len, std::u16string* output) {
-  return UTFConversion(StringPiece(src, src_len), output);
+  return UTFConversion(std::string_view(src, src_len), output);
 }
 
-std::u16string UTF8ToUTF16(StringPiece utf8) {
+std::u16string UTF8ToUTF16(std::string_view utf8) {
   std::u16string ret;
   // Ignore the success flag of this call, it will do the best it can for
   // invalid input, which is what we want here.
@@ -234,10 +240,10 @@
 }
 
 bool UTF16ToUTF8(const char16_t* src, size_t src_len, std::string* output) {
-  return UTFConversion(StringPiece16(src, src_len), output);
+  return UTFConversion(std::u16string_view(src, src_len), output);
 }
 
-std::string UTF16ToUTF8(StringPiece16 utf16) {
+std::string UTF16ToUTF8(std::u16string_view utf16) {
   std::string ret;
   // Ignore the success flag of this call, it will do the best it can for
   // invalid input, which is what we want here.
@@ -264,7 +270,7 @@
   return true;
 }
 
-std::wstring UTF16ToWide(StringPiece16 utf16) {
+std::wstring UTF16ToWide(std::u16string_view utf16) {
   return std::wstring(utf16.begin(), utf16.end());
 }
 
@@ -283,10 +289,10 @@
 }
 
 bool UTF16ToWide(const char16_t* src, size_t src_len, std::wstring* output) {
-  return UTFConversion(StringPiece16(src, src_len), output);
+  return UTFConversion(std::u16string_view(src, src_len), output);
 }
 
-std::wstring UTF16ToWide(StringPiece16 utf16) {
+std::wstring UTF16ToWide(std::u16string_view utf16) {
   std::wstring ret;
   // Ignore the success flag of this call, it will do the best it can for
   // invalid input, which is what we want here.
@@ -301,10 +307,10 @@
 // UTF8ToWide is the same code, regardless of whether wide is 16 or 32 bits
 
 bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) {
-  return UTFConversion(StringPiece(src, src_len), output);
+  return UTFConversion(std::string_view(src, src_len), output);
 }
 
-std::wstring UTF8ToWide(StringPiece utf8) {
+std::wstring UTF8ToWide(std::string_view utf8) {
   std::wstring ret;
   // Ignore the success flag of this call, it will do the best it can for
   // invalid input, which is what we want here.
@@ -320,7 +326,7 @@
 }
 
 std::string WideToUTF8(std::wstring_view wide) {
-  return UTF16ToUTF8(StringPiece16(as_u16cstr(wide), wide.size()));
+  return UTF16ToUTF8(std::u16string_view(as_u16cstr(wide), wide.size()));
 }
 
 #elif defined(WCHAR_T_IS_32_BIT)
@@ -339,18 +345,18 @@
 
 #endif  // defined(WCHAR_T_IS_32_BIT)
 
-std::u16string ASCIIToUTF16(StringPiece ascii) {
+std::u16string ASCIIToUTF16(std::string_view ascii) {
   GURL_DCHECK(IsStringASCII(ascii)) << ascii;
   return std::u16string(ascii.begin(), ascii.end());
 }
 
-std::string UTF16ToASCII(StringPiece16 utf16) {
+std::string UTF16ToASCII(std::u16string_view utf16) {
   GURL_DCHECK(IsStringASCII(utf16)) << UTF16ToUTF8(utf16);
   return std::string(utf16.begin(), utf16.end());
 }
 
 #if defined(WCHAR_T_IS_16_BIT)
-std::wstring ASCIIToWide(StringPiece ascii) {
+std::wstring ASCIIToWide(std::string_view ascii) {
   GURL_DCHECK(IsStringASCII(ascii)) << ascii;
   return std::wstring(ascii.begin(), ascii.end());
 }
diff --git a/base/strings/utf_string_conversions.h b/base/strings/utf_string_conversions.h
index 385a933..6f95029 100644
--- a/base/strings/utf_string_conversions.h
+++ b/base/strings/utf_string_conversions.h
@@ -11,8 +11,6 @@
 #include <string_view>
 
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
-#include "base/types/always_false.h"
 #include "build/build_config.h"
 
 namespace gurl_base {
@@ -23,12 +21,14 @@
 // do the best it can and put the result in the output buffer. The versions that
 // return strings ignore this error and just return the best conversion
 // possible.
-BASE_EXPORT bool WideToUTF8(const wchar_t* src, size_t src_len,
+BASE_EXPORT bool WideToUTF8(const wchar_t* src,
+                            size_t src_len,
                             std::string* output);
 [[nodiscard]] BASE_EXPORT std::string WideToUTF8(std::wstring_view wide);
-BASE_EXPORT bool UTF8ToWide(const char* src, size_t src_len,
+BASE_EXPORT bool UTF8ToWide(const char* src,
+                            size_t src_len,
                             std::wstring* output);
-[[nodiscard]] BASE_EXPORT std::wstring UTF8ToWide(StringPiece utf8);
+[[nodiscard]] BASE_EXPORT std::wstring UTF8ToWide(std::string_view utf8);
 
 BASE_EXPORT bool WideToUTF16(const wchar_t* src,
                              size_t src_len,
@@ -37,29 +37,29 @@
 BASE_EXPORT bool UTF16ToWide(const char16_t* src,
                              size_t src_len,
                              std::wstring* output);
-[[nodiscard]] BASE_EXPORT std::wstring UTF16ToWide(StringPiece16 utf16);
+[[nodiscard]] BASE_EXPORT std::wstring UTF16ToWide(std::u16string_view utf16);
 
 BASE_EXPORT bool UTF8ToUTF16(const char* src,
                              size_t src_len,
                              std::u16string* output);
-[[nodiscard]] BASE_EXPORT std::u16string UTF8ToUTF16(StringPiece utf8);
+[[nodiscard]] BASE_EXPORT std::u16string UTF8ToUTF16(std::string_view utf8);
 BASE_EXPORT bool UTF16ToUTF8(const char16_t* src,
                              size_t src_len,
                              std::string* output);
-[[nodiscard]] BASE_EXPORT std::string UTF16ToUTF8(StringPiece16 utf16);
+[[nodiscard]] BASE_EXPORT std::string UTF16ToUTF8(std::u16string_view utf16);
 
 // This converts an ASCII string, typically a hardcoded constant, to a UTF16
 // string.
-[[nodiscard]] BASE_EXPORT std::u16string ASCIIToUTF16(StringPiece ascii);
+[[nodiscard]] BASE_EXPORT std::u16string ASCIIToUTF16(std::string_view ascii);
 
 // Converts to 7-bit ASCII by truncating. The result must be known to be ASCII
 // beforehand.
-[[nodiscard]] BASE_EXPORT std::string UTF16ToASCII(StringPiece16 utf16);
+[[nodiscard]] BASE_EXPORT std::string UTF16ToASCII(std::u16string_view utf16);
 
 #if defined(WCHAR_T_IS_16_BIT)
 // This converts an ASCII string, typically a hardcoded constant, to a wide
 // string.
-[[nodiscard]] BASE_EXPORT std::wstring ASCIIToWide(StringPiece ascii);
+[[nodiscard]] BASE_EXPORT std::wstring ASCIIToWide(std::string_view ascii);
 
 // Converts to 7-bit ASCII by truncating. The result must be known to be ASCII
 // beforehand.
@@ -72,19 +72,19 @@
 // time.
 template <size_t N>
 [[noreturn]] std::u16string WideToUTF16(const wchar_t (&str)[N]) {
-  static_assert(AlwaysFalse<decltype(N)>,
+  static_assert(false,
                 "Error: Use u\"...\" to create a std::u16string literal.");
 }
 
 template <size_t N>
 [[noreturn]] std::u16string UTF8ToUTF16(const char (&str)[N]) {
-  static_assert(AlwaysFalse<decltype(N)>,
+  static_assert(false,
                 "Error: Use u\"...\" to create a std::u16string literal.");
 }
 
 template <size_t N>
 [[noreturn]] std::u16string ASCIIToUTF16(const char (&str)[N]) {
-  static_assert(AlwaysFalse<decltype(N)>,
+  static_assert(false,
                 "Error: Use u\"...\" to create a std::u16string literal.");
 }
 
@@ -92,7 +92,7 @@
 // to allow this conversion.
 template <size_t N>
 std::u16string ASCIIToUTF16(char (&str)[N]) {
-  return ASCIIToUTF16(StringPiece(str));
+  return ASCIIToUTF16(std::string_view(str));
 }
 
 }  // namespace base
diff --git a/base/strings/utf_string_conversions_unittest.cc b/base/strings/utf_string_conversions_unittest.cc
index 55f6296..91d6c11 100644
--- a/base/strings/utf_string_conversions_unittest.cc
+++ b/base/strings/utf_string_conversions_unittest.cc
@@ -2,11 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
+#pragma allow_unsafe_libc_calls
+#endif
+
 #include "base/strings/utf_string_conversions.h"
 
 #include <stddef.h>
 
-#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -75,26 +79,27 @@
     const wchar_t* wide;
     bool success;
   } convert_cases[] = {
-    // Regular UTF-8 input.
-    {"\xe4\xbd\xa0\xe5\xa5\xbd", L"\x4f60\x597d", true},
-    // Non-character is passed through.
-    {"\xef\xbf\xbfHello", L"\xffffHello", true},
-    // Truncated UTF-8 sequence.
-    {"\xe4\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false},
-    // Truncated off the end.
-    {"\xe5\xa5\xbd\xe4\xa0", L"\x597d\xfffd", false},
-    // Non-shortest-form UTF-8.
-    {"\xf0\x84\xbd\xa0\xe5\xa5\xbd", L"\xfffd\xfffd\xfffd\xfffd\x597d", false},
-    // This UTF-8 character decodes to a UTF-16 surrogate, which is illegal.
-    {"\xed\xb0\x80", L"\xfffd\xfffd\xfffd", false},
-    // Non-BMP characters. The second is a non-character regarded as valid.
-    // The result will either be in UTF-16 or UTF-32.
+      // Regular UTF-8 input.
+      {"\xe4\xbd\xa0\xe5\xa5\xbd", L"\x4f60\x597d", true},
+      // Non-character is passed through.
+      {"\xef\xbf\xbfHello", L"\xffffHello", true},
+      // Truncated UTF-8 sequence.
+      {"\xe4\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false},
+      // Truncated off the end.
+      {"\xe5\xa5\xbd\xe4\xa0", L"\x597d\xfffd", false},
+      // Non-shortest-form UTF-8.
+      {"\xf0\x84\xbd\xa0\xe5\xa5\xbd", L"\xfffd\xfffd\xfffd\xfffd\x597d",
+       false},
+      // This UTF-8 character decodes to a UTF-16 surrogate, which is illegal.
+      {"\xed\xb0\x80", L"\xfffd\xfffd\xfffd", false},
+  // Non-BMP characters. The second is a non-character regarded as valid.
+  // The result will either be in UTF-16 or UTF-32.
 #if defined(WCHAR_T_IS_16_BIT)
-    {"A\xF0\x90\x8C\x80z", L"A\xd800\xdf00z", true},
-    {"A\xF4\x8F\xBF\xBEz", L"A\xdbff\xdffez", true},
+      {"A\xF0\x90\x8C\x80z", L"A\xd800\xdf00z", true},
+      {"A\xF4\x8F\xBF\xBEz", L"A\xdbff\xdffez", true},
 #elif defined(WCHAR_T_IS_32_BIT)
-    {"A\xF0\x90\x8C\x80z", L"A\x10300z", true},
-    {"A\xF4\x8F\xBF\xBEz", L"A\x10fffez", true},
+      {"A\xF0\x90\x8C\x80z", L"A\x10300z", true},
+      {"A\xF4\x8F\xBF\xBEz", L"A\x10fffez", true},
 #endif
   };
 
@@ -127,17 +132,17 @@
     const char* utf8;
     bool success;
   } convert_cases[] = {
-    // Regular UTF-16 input.
-    {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true},
-    // Test a non-BMP character.
-    {L"\xd800\xdf00", "\xF0\x90\x8C\x80", true},
-    // Non-characters are passed through.
-    {L"\xffffHello", "\xEF\xBF\xBFHello", true},
-    {L"\xdbff\xdffeHello", "\xF4\x8F\xBF\xBEHello", true},
-    // The first character is a truncated UTF-16 character.
-    {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false},
-    // Truncated at the end.
-    {L"\x597d\xd800", "\xe5\xa5\xbd\xef\xbf\xbd", false},
+      // Regular UTF-16 input.
+      {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true},
+      // Test a non-BMP character.
+      {L"\xd800\xdf00", "\xF0\x90\x8C\x80", true},
+      // Non-characters are passed through.
+      {L"\xffffHello", "\xEF\xBF\xBFHello", true},
+      {L"\xdbff\xdffeHello", "\xF4\x8F\xBF\xBEHello", true},
+      // The first character is a truncated UTF-16 character.
+      {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false},
+      // Truncated at the end.
+      {L"\x597d\xd800", "\xe5\xa5\xbd\xef\xbf\xbd", false},
   };
 
   for (const auto& test : convert_cases) {
@@ -157,18 +162,18 @@
     const char* utf8;
     bool success;
   } convert_cases[] = {
-    // Regular 16-bit input.
-    {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true},
-    // Test a non-BMP character.
-    {L"A\x10300z", "A\xF0\x90\x8C\x80z", true},
-    // Non-characters are passed through.
-    {L"\xffffHello", "\xEF\xBF\xBFHello", true},
-    {L"\x10fffeHello", "\xF4\x8F\xBF\xBEHello", true},
-    // Invalid Unicode code points.
-    {L"\xfffffffHello", "\xEF\xBF\xBDHello", false},
-    // The first character is a truncated UTF-16 character.
-    {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false},
-    {L"\xdc01Hello", "\xef\xbf\xbdHello", false},
+      // Regular 16-bit input.
+      {L"\x4f60\x597d", "\xe4\xbd\xa0\xe5\xa5\xbd", true},
+      // Test a non-BMP character.
+      {L"A\x10300z", "A\xF0\x90\x8C\x80z", true},
+      // Non-characters are passed through.
+      {L"\xffffHello", "\xEF\xBF\xBFHello", true},
+      {L"\x10fffeHello", "\xF4\x8F\xBF\xBEHello", true},
+      // Invalid Unicode code points.
+      {L"\xfffffffHello", "\xEF\xBF\xBDHello", false},
+      // The first character is a truncated UTF-16 character.
+      {L"\xd800\x597d", "\xef\xbf\xbd\xe5\xa5\xbd", false},
+      {L"\xdc01Hello", "\xef\xbf\xbdHello", false},
   };
 
   for (const auto& test : convert_cases) {
@@ -182,24 +187,14 @@
 #endif  // defined(WCHAR_T_IS_32_BIT)
 
 TEST(UTFStringConversionsTest, ConvertMultiString) {
-  static char16_t multi16[] = {'f',  'o', 'o', '\0', 'b',  'a', 'r',
-                               '\0', 'b', 'a', 'z',  '\0', '\0'};
-  static char multi[] = {
-    'f', 'o', 'o', '\0',
-    'b', 'a', 'r', '\0',
-    'b', 'a', 'z', '\0',
-    '\0'
-  };
-  std::u16string multistring16;
-  memcpy(WriteInto(&multistring16, std::size(multi16)), multi16,
-         sizeof(multi16));
-  EXPECT_EQ(std::size(multi16) - 1, multistring16.length());
-  std::string expected;
-  memcpy(WriteInto(&expected, std::size(multi)), multi, sizeof(multi));
-  EXPECT_EQ(std::size(multi) - 1, expected.length());
-  const std::string& converted = UTF16ToUTF8(multistring16);
-  EXPECT_EQ(std::size(multi) - 1, converted.length());
-  EXPECT_EQ(expected, converted);
+  // `operator""s` will avoid truncating the strings at the first embedded NUL.
+  using std::string_literals::operator""s;
+  std::u16string multistring16 = u"foo\0bar\0baz\0"s;
+  std::string expected = "foo\0bar\0baz\0"s;
+  ASSERT_EQ(12u, multistring16.size());
+  ASSERT_EQ(12u, expected.size());
+
+  EXPECT_EQ(expected, UTF16ToUTF8(multistring16));
 }
 
 }  // namespace base
diff --git a/base/template_util.h b/base/template_util.h
deleted file mode 100644
index df1ff8b..0000000
--- a/base/template_util.h
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2011 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_TEMPLATE_UTIL_H_
-#define BASE_TEMPLATE_UTIL_H_
-
-#include <stddef.h>
-
-#include <iosfwd>
-#include <iterator>
-#include <type_traits>
-#include <utility>
-
-#include "base/compiler_specific.h"
-
-namespace gurl_base {
-
-namespace internal {
-
-// Used to detech whether the given type is an iterator.  This is normally used
-// with std::enable_if to provide disambiguation for functions that take
-// templatzed iterators as input.
-template <typename T, typename = void>
-struct is_iterator : std::false_type {};
-
-template <typename T>
-struct is_iterator<
-    T,
-    std::void_t<typename std::iterator_traits<T>::iterator_category>>
-    : std::true_type {};
-
-// Helper to express preferences in an overload set. If more than one overload
-// are available for a given set of parameters the overload with the higher
-// priority will be chosen.
-template <size_t I>
-struct priority_tag : priority_tag<I - 1> {};
-
-template <>
-struct priority_tag<0> {};
-
-}  // namespace internal
-
-namespace internal {
-
-// The indirection with std::is_enum<T> is required, because instantiating
-// std::underlying_type_t<T> when T is not an enum is UB prior to C++20.
-template <typename T, bool = std::is_enum_v<T>>
-struct IsScopedEnumImpl : std::false_type {};
-
-template <typename T>
-struct IsScopedEnumImpl<T, /*std::is_enum_v<T>=*/true>
-    : std::negation<std::is_convertible<T, std::underlying_type_t<T>>> {};
-
-}  // namespace internal
-
-// Implementation of C++23's std::is_scoped_enum
-//
-// Reference: https://en.cppreference.com/w/cpp/types/is_scoped_enum
-template <typename T>
-struct is_scoped_enum : internal::IsScopedEnumImpl<T> {};
-
-// Implementation of C++20's std::remove_cvref.
-//
-// References:
-// - https://en.cppreference.com/w/cpp/types/remove_cvref
-// - https://wg21.link/meta.trans.other#lib:remove_cvref
-template <typename T>
-struct remove_cvref {
-  using type = std::remove_cv_t<std::remove_reference_t<T>>;
-};
-
-// Implementation of C++20's std::remove_cvref_t.
-//
-// References:
-// - https://en.cppreference.com/w/cpp/types/remove_cvref
-// - https://wg21.link/meta.type.synop#lib:remove_cvref_t
-template <typename T>
-using remove_cvref_t = typename remove_cvref<T>::type;
-
-// Simplified implementation of C++20's std::iter_value_t.
-// As opposed to std::iter_value_t, this implementation does not restrict
-// the type of `Iter` and does not consider specializations of
-// `indirectly_readable_traits`.
-//
-// Reference: https://wg21.link/readable.traits#2
-template <typename Iter>
-struct IterValueImpl {
-  using value_type = typename std::iterator_traits<Iter>::value_type;
-};
-
-template <typename T, bool Cond = false>
-struct IterValuePointerImpl {
-  // The `iterator_traits<T*>::value_type` member is not defined if T is not an
-  // object in C++20.
-};
-template <typename T>
-struct IterValuePointerImpl<T*, true> {
-  using value_type = typename std::iterator_traits<T*>::value_type;
-};
-
-template <typename T>
-struct IterValueImpl<T*> {
-  using value_type =
-      typename IterValuePointerImpl<T*, std::is_object_v<T>>::value_type;
-};
-
-template <typename Iter>
-using iter_value_t = typename IterValueImpl<remove_cvref_t<Iter>>::value_type;
-
-// Simplified implementation of C++20's std::iter_reference_t.
-// As opposed to std::iter_reference_t, this implementation does not restrict
-// the type of `Iter`.
-//
-// Reference: https://wg21.link/iterator.synopsis#:~:text=iter_reference_t
-template <typename Iter>
-using iter_reference_t = decltype(*std::declval<Iter&>());
-
-// Simplified implementation of C++20's std::indirect_result_t. As opposed to
-// std::indirect_result_t, this implementation does not restrict the type of
-// `Func` and `Iters`.
-//
-// Reference: https://wg21.link/iterator.synopsis#:~:text=indirect_result_t
-template <typename Func, typename... Iters>
-using indirect_result_t =
-    std::invoke_result_t<Func, iter_reference_t<Iters>...>;
-
-// Simplified implementation of C++20's std::projected. As opposed to
-// std::projected, this implementation does not explicitly restrict the type of
-// `Iter` and `Proj`, but rather does so implicitly by requiring
-// `indirect_result_t<Proj, Iter>` is a valid type. This is required for SFINAE
-// friendliness.
-//
-// Reference: https://wg21.link/projected
-template <typename Iter,
-          typename Proj,
-          typename IndirectResultT = indirect_result_t<Proj, Iter>>
-struct projected {
-  using value_type = remove_cvref_t<IndirectResultT>;
-
-  IndirectResultT operator*() const;  // not defined
-};
-
-}  // namespace base
-
-#endif  // BASE_TEMPLATE_UTIL_H_
diff --git a/base/third_party/icu/BUILD.gn b/base/third_party/icu/BUILD.gn
new file mode 100644
index 0000000..f3cba8c
--- /dev/null
+++ b/base/third_party/icu/BUILD.gn
@@ -0,0 +1,8 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("icu") {
+  sources = [ "icu_utf.h" ]
+  visibility = [ "//base" ]
+}
diff --git a/base/third_party/icu/README.chromium b/base/third_party/icu/README.chromium
index c514fc5..1c270bd 100644
--- a/base/third_party/icu/README.chromium
+++ b/base/third_party/icu/README.chromium
@@ -1,8 +1,10 @@
 Name: ICU
 URL: http://site.icu-project.org/
 Version: 60
-License: Unicode
+Update Mechanism: Manual
+License: Unicode-DFS-2016, ICU
 License File: LICENSE
+Security Critical: yes
 Shipped: no
 
 This file has the relevant components from ICU copied to handle basic UTF8/16/32
diff --git a/base/types/always_false.h b/base/types/always_false.h
deleted file mode 100644
index 7f43695..0000000
--- a/base/types/always_false.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_TYPES_ALWAYS_FALSE_H_
-#define BASE_TYPES_ALWAYS_FALSE_H_
-
-namespace gurl_base {
-
-// A helper that can be used with a static_assert() that must always fail (e.g.
-// for an undesirable template instantiation). Such a static_assert() cannot
-// simply be written as static_assert(false, ...) because that would always fail
-// to compile, even if the template was never instantiated. Instead, a common
-// idiom is to force the static_assert() to depend on a template parameter so
-// that it is only evaluated when the template is instantiated:
-//
-// template <typename U = T>
-// void SomeDangerousMethodThatShouldNeverCompile() {
-//   static_assert(gurl_base::AlwaysFalse<U>, "explanatory message here");
-// }
-//
-//
-// The issue of not being able to use static_assert(false, ...) in a
-// non-instantiated template was fixed in C++23. When Chromium switches to
-// building with C++23, remove this file and use false directly, and search
-// across the Chromium codebase for "AlwaysFalse", as there are other
-// implementations in places that cannot depend on this file.
-//
-// References:
-// - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2593r0.html
-// - https://github.com/cplusplus/papers/issues/1251
-
-namespace internal {
-
-template <typename... Args>
-struct AlwaysFalseHelper {
-  static constexpr bool kValue = false;
-};
-
-}  // namespace internal
-
-template <typename... Args>
-inline constexpr bool AlwaysFalse =
-    internal::AlwaysFalseHelper<Args...>::kValue;
-
-}  // namespace base
-
-#endif  // BASE_TYPES_ALWAYS_FALSE_H_
diff --git a/base/types/to_address.h b/base/types/to_address.h
new file mode 100644
index 0000000..8073310
--- /dev/null
+++ b/base/types/to_address.h
@@ -0,0 +1,40 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TYPES_TO_ADDRESS_H_
+#define BASE_TYPES_TO_ADDRESS_H_
+
+#include <memory>
+#include <type_traits>
+
+// SFINAE-compatible wrapper for `std::to_address()`.
+//
+// The standard does not require `std::to_address()` to be SFINAE-compatible
+// when code attempts instantiation with non-pointer-like types, and libstdc++'s
+// implementation hard errors. For the sake of templated code that wants simple,
+// unified handling, Chromium instead uses this wrapper, which provides that
+// guarantee. This allows code to use "`to_address()` would be valid here" as a
+// constraint to detect pointer-like types.
+namespace gurl_base {
+
+// Note that calling `std::to_address()` with a function pointer renders the
+// program ill-formed.
+template <typename T>
+  requires(!std::is_function_v<T>)
+constexpr T* to_address(T* p) noexcept {
+  return p;
+}
+
+// These constraints cover the cases where `std::to_address()`'s fancy pointer
+// overload is well-specified.
+template <typename P>
+  requires requires(const P& p) { std::pointer_traits<P>::to_address(p); } ||
+           requires(const P& p) { p.operator->(); }
+constexpr auto to_address(const P& p) noexcept {
+  return std::to_address(p);
+}
+
+}  // namespace base
+
+#endif  // BASE_TYPES_TO_ADDRESS_H_
diff --git a/base/win/win_handle_types_list.inc b/base/win/win_handle_types_list.inc
index 2241211..917c4b2 100644
--- a/base/win/win_handle_types_list.inc
+++ b/base/win/win_handle_types_list.inc
@@ -12,14 +12,19 @@
 // via specific pointee types declared in //base/win/windows_types.h
 // (e.g. `HDC` points to a fake/forward-declared `HDC__` struct).
 
+CHROME_WINDOWS_HANDLE_TYPE(HBITMAP)
+CHROME_WINDOWS_HANDLE_TYPE(HBRUSH)
 CHROME_WINDOWS_HANDLE_TYPE(HDC)
 CHROME_WINDOWS_HANDLE_TYPE(HDESK)
+CHROME_WINDOWS_HANDLE_TYPE(HFONT)
 CHROME_WINDOWS_HANDLE_TYPE(HGLRC)
 CHROME_WINDOWS_HANDLE_TYPE(HICON)
 CHROME_WINDOWS_HANDLE_TYPE(HINSTANCE)
 CHROME_WINDOWS_HANDLE_TYPE(HKEY)
 CHROME_WINDOWS_HANDLE_TYPE(HKL)
 CHROME_WINDOWS_HANDLE_TYPE(HMENU)
+CHROME_WINDOWS_HANDLE_TYPE(HPEN)
+CHROME_WINDOWS_HANDLE_TYPE(HRGN)
 CHROME_WINDOWS_HANDLE_TYPE(HWINSTA)
 CHROME_WINDOWS_HANDLE_TYPE(HWND)
 CHROME_WINDOWS_HANDLE_TYPE(HMONITOR)
diff --git a/build/build_config.h b/build/build_config.h
index 65a2465..9b40479 100644
--- a/build/build_config.h
+++ b/build/build_config.h
@@ -16,13 +16,13 @@
 //
 //  Operating System:
 //    IS_AIX / IS_ANDROID / IS_ASMJS / IS_CHROMEOS / IS_FREEBSD / IS_FUCHSIA /
-//    IS_IOS / IS_IOS_MACCATALYST / IS_LINUX / IS_MAC / IS_NACL / IS_NETBSD /
-//    IS_OPENBSD / IS_QNX / IS_SOLARIS / IS_WIN
+//    IS_IOS / IS_IOS_MACCATALYST / IS_IOS_TVOS / IS_LINUX / IS_MAC /
+//    IS_NETBSD / IS_OPENBSD / IS_QNX / IS_SOLARIS / IS_WATCHOS / IS_WIN
 //  Operating System family:
-//    IS_APPLE: IOS or MAC or IOS_MACCATALYST
+//    IS_APPLE: IOS or MAC or IOS_MACCATALYST or IOS_TVOS or WATCHOS
 //    IS_BSD: FREEBSD or NETBSD or OPENBSD
 //    IS_POSIX: AIX or ANDROID or ASMJS or CHROMEOS or FREEBSD or IOS or LINUX
-//              or MAC or NACL or NETBSD or OPENBSD or QNX or SOLARIS
+//              or MAC or NETBSD or OPENBSD or QNX or SOLARIS
 
 // This file also adds defines specific to the platform, architecture etc.
 //
@@ -49,16 +49,22 @@
 //    ARCH_CPU_31_BITS / ARCH_CPU_32_BITS / ARCH_CPU_64_BITS
 //    ARCH_CPU_BIG_ENDIAN / ARCH_CPU_LITTLE_ENDIAN
 
+// Mapping to some Rust conditionals:
+//
+// * `#[cfg(unix)]` ~= `BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)`
+
 #ifndef BUILD_BUILD_CONFIG_H_
 #define BUILD_BUILD_CONFIG_H_
 
 #include "build/buildflag.h"  // IWYU pragma: export
 
+// Clangd does not detect BUILDFLAG_INTERNAL_* indirect usage, so mark the
+// header as "always_keep" to avoid "unused include" warning.
+//
+// IWYU pragma: always_keep
+
 // A set of macros to use for platform detection.
-#if defined(__native_client__)
-// __native_client__ must be first, so that other OS_ defines are not set.
-#define OS_NACL 1
-#elif defined(ANDROID)
+#if defined(ANDROID)
 #define OS_ANDROID 1
 #elif defined(__APPLE__)
 // Only include TargetConditionals after testing ANDROID as some Android builds
@@ -72,6 +78,12 @@
 #if defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST
 #define OS_IOS_MACCATALYST
 #endif  // defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST
+#if defined(TARGET_OS_TV) && TARGET_OS_TV
+#define OS_IOS_TVOS 1
+#endif  // defined(TARGET_OS_TV) && TARGET_OS_TV
+#if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH
+#define OS_WATCHOS 1
+#endif  // defined(TARGET_OS_WATCH) && TARGET_OS_WATCH
 #else
 #define OS_MAC 1
 #endif  // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
@@ -125,11 +137,11 @@
 
 // For access to standard POSIXish features, use OS_POSIX instead of a
 // more specific macro.
-#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_ASMJS) ||  \
-    defined(OS_FREEBSD) || defined(OS_IOS) || defined(OS_LINUX) ||  \
-    defined(OS_CHROMEOS) || defined(OS_MAC) || defined(OS_NACL) ||  \
-    defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_QNX) || \
-    defined(OS_SOLARIS) || defined(OS_ZOS)
+#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_ASMJS) ||   \
+    defined(OS_FREEBSD) || defined(OS_IOS) || defined(OS_LINUX) ||   \
+    defined(OS_CHROMEOS) || defined(OS_MAC) || defined(OS_NETBSD) || \
+    defined(OS_OPENBSD) || defined(OS_QNX) || defined(OS_SOLARIS) || \
+    defined(OS_ZOS)
 #define OS_POSIX 1
 #endif
 
@@ -194,6 +206,12 @@
 #define BUILDFLAG_INTERNAL_IS_IOS_MACCATALYST() (0)
 #endif
 
+#if defined(OS_IOS_TVOS)
+#define BUILDFLAG_INTERNAL_IS_IOS_TVOS() (1)
+#else
+#define BUILDFLAG_INTERNAL_IS_IOS_TVOS() (0)
+#endif
+
 #if defined(OS_LINUX)
 #define BUILDFLAG_INTERNAL_IS_LINUX() (1)
 #else
@@ -206,12 +224,6 @@
 #define BUILDFLAG_INTERNAL_IS_MAC() (0)
 #endif
 
-#if defined(OS_NACL)
-#define BUILDFLAG_INTERNAL_IS_NACL() (1)
-#else
-#define BUILDFLAG_INTERNAL_IS_NACL() (0)
-#endif
-
 #if defined(OS_NETBSD)
 #define BUILDFLAG_INTERNAL_IS_NETBSD() (1)
 #else
@@ -242,6 +254,12 @@
 #define BUILDFLAG_INTERNAL_IS_SOLARIS() (0)
 #endif
 
+#if defined(OS_WATCHOS)
+#define BUILDFLAG_INTERNAL_IS_WATCHOS() (1)
+#else
+#define BUILDFLAG_INTERNAL_IS_WATCHOS() (0)
+#endif
+
 #if defined(OS_WIN)
 #define BUILDFLAG_INTERNAL_IS_WIN() (1)
 #else
@@ -308,7 +326,7 @@
 #define ARCH_CPU_ARM64 1
 #define ARCH_CPU_64_BITS 1
 #define ARCH_CPU_LITTLE_ENDIAN 1
-#elif defined(__pnacl__) || defined(__asmjs__) || defined(__wasm__)
+#elif defined(__asmjs__) || defined(__wasm__)
 #define ARCH_CPU_32_BITS 1
 #define ARCH_CPU_LITTLE_ENDIAN 1
 #elif defined(__MIPSEL__)
@@ -382,4 +400,24 @@
 #define BASE_STRING16_ITERATOR_IS_CHAR16_POINTER
 #endif
 
+// Architecture-specific feature detection.
+
+#if !defined(CPU_ARM_NEON)
+#if defined(ARCH_CPU_ARM_FAMILY) && \
+    (defined(__ARM_NEON__) || defined(__ARM_NEON))
+#define CPU_ARM_NEON 1
+#endif
+#endif  // !defined(CPU_ARM_NEON)
+
+// Sanity check.
+#if defined(ARCH_CPU_ARM64) && !defined(CPU_ARM_NEON)
+#error "AArch64 mandates NEON, should be detected"
+#endif
+
+#if !defined(HAVE_MIPS_MSA_INTRINSICS)
+#if defined(__mips_msa) && defined(__mips_isa_rev) && (__mips_isa_rev >= 5)
+#define HAVE_MIPS_MSA_INTRINSICS 1
+#endif
+#endif
+
 #endif  // BUILD_BUILD_CONFIG_H_
diff --git a/copy.bara.sky b/copy.bara.sky
index e19e158..5c03dc7 100644
--- a/copy.bara.sky
+++ b/copy.bara.sky
@@ -18,6 +18,7 @@
         "base/containers/contains.h",
         "base/containers/contiguous_iterator.h",
         "base/containers/span.h",
+	"base/containers/span_forward_internal.h",
         "base/containers/util.h",
         "base/cxx17_backports.h",
         "base/cxx20_is_constant_evaluated.h",
@@ -29,6 +30,7 @@
         "base/functional/not_fn.h",
         "base/i18n/uchar.h",
         "base/memory/raw_ptr_exclusion.h",
+	"base/memory/stack_allocated.h",
         "base/numerics/*.h",
         "base/no_destructor.h",
         "base/ranges/*.h",
@@ -38,6 +40,7 @@
         "base/template_util.h",
         "base/types/always_false.h",
         "base/types/supports_ostream_operator.h",
+	"base/types/to_address.h",
         "base/third_party/icu/**",
         "base/win/win_handle_types.h",
         "base/win/win_handle_types_list.inc",
@@ -75,8 +78,7 @@
     "base/check.h",
     "base/check_op.h",
     "base/component_export.h",
-    "base/cpu_reduction_experiment.h",
-    #"base/dcheck_is_on.h",
+    "base/dcheck_is_on.h",
     "base/debug/alias.h",
     "base/export_template.h",
     "base/feature_list.h",
@@ -95,7 +97,7 @@
         regex_groups = {"log": "\\bD?(LOG|CHECK|CHECK_(EQ|LT|GT|LE|GE|NE))\\b"},
     ),
     core.replace("DCHECK_IS_ON", "GURL_DCHECK_IS_ON"),
-    core.replace("NOTREACHED()", "GURL_NOTREACHED()"),
+    core.replace("${x}", "GURL_NOTREACHED()", regex_groups = {"x": "\\bNOTREACHED\\(\\)"}),
 
     # Rename base:: to gurl_base::
     core.replace("namespace base ", "namespace gurl_base "),
@@ -109,11 +111,9 @@
     core.move("url/url_idna_icu_alternatives_ios.mm", "url/url_idna_ascii_only.cc"),
 
     # Fix some Perfetto includes.
-    core.replace("base/trace_event/base_tracing.h", "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"),
+    core.replace("base/trace_event/trace_event.h", "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"),
     core.replace("base/trace_event/base_tracing_forward.h", "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"),
     core.replace("#include \"base/strings/string_number_conversions_win.h\"", ""),
-    # Patch out C++20 feature use
-    core.replace("                           std::is_same<iter_value_t<T>, char8_t>,", ""),
     #core.replace("#include \"base/allocator/partition_allocator/partition_alloc_config.h\"", ""),
 
     # Use system ICU.
diff --git a/polyfills/BUILD b/polyfills/BUILD
index 86cecc1..8269b55 100644
--- a/polyfills/BUILD
+++ b/polyfills/BUILD
@@ -11,7 +11,6 @@
         "base/check.h",
         "base/check_op.h",
         "base/component_export.h",
-        "base/cpu_reduction_experiment.h",
         "base/dcheck_is_on.h",
         "base/debug/alias.h",
         "base/export_template.h",
diff --git a/polyfills/base/check_op.h b/polyfills/base/check_op.h
index faba308..ecc127a 100644
--- a/polyfills/base/check_op.h
+++ b/polyfills/base/check_op.h
@@ -6,6 +6,5 @@
 #define POLYFILLS_BASE_CHECK_OP_H_
 
 #include "polyfills/base/logging.h"
-#include "base/template_util.h"
 
 #endif /* POLYFILLS_BASE_CHECK_OP_H_ */
diff --git a/polyfills/base/cpu_reduction_experiment.h b/polyfills/base/cpu_reduction_experiment.h
deleted file mode 100644
index db2ea65..0000000
--- a/polyfills/base/cpu_reduction_experiment.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef POLYFILLS_BASE_CPU_REDUCTION_EXPERIMENT_H_
-#define POLYFILLS_BASE_CPU_REDUCTION_EXPERIMENT_H_
-
-namespace base {
-
-inline bool IsRunningCpuReductionExperiment() { return false; }
-
-class CpuReductionExperimentFilter {
- public:
-  bool ShouldLogHistograms() { return false; }
-};
-
-}  // namespace base
-
-
-#endif  /* POLYFILLS_BASE_CPU_REDUCTION_EXPERIMENT_H_ */
diff --git a/polyfills/base/feature_list.h b/polyfills/base/feature_list.h
index 8ad7a08..6fd491d 100644
--- a/polyfills/base/feature_list.h
+++ b/polyfills/base/feature_list.h
@@ -7,8 +7,8 @@
 
 #define BASE_DECLARE_FEATURE(feature) extern const gurl_base::Feature feature
 
-#define BASE_FEATURE(feature, name, default_value) \
-  const gurl_base::Feature feature(name, default_value)
+#define BASE_FEATURE(feature, default_value) \
+  const gurl_base::Feature feature(#feature, default_value)
 
 namespace gurl_base {
 
diff --git a/polyfills/base/logging.h b/polyfills/base/logging.h
index 42a5068..fb549ef 100644
--- a/polyfills/base/logging.h
+++ b/polyfills/base/logging.h
@@ -21,6 +21,7 @@
 };
 
 #define GURL_CHECK_GE(statement, statement2) GurlFakeLogSink({statement, statement2})
+#define GURL_CHECK_GT(statement, statement2) GurlFakeLogSink({statement, statement2})
 #define GURL_CHECK_LE(statement, statement2) GurlFakeLogSink({statement, statement2})
 #define GURL_CHECK_LT(statement, statement2) GurlFakeLogSink({statement, statement2})
 #define GURL_CHECK_NE(statement, statement2) GurlFakeLogSink({statement, statement2})
diff --git a/polyfills/base/notreached.h b/polyfills/base/notreached.h
index 564d64c..f435d7c 100644
--- a/polyfills/base/notreached.h
+++ b/polyfills/base/notreached.h
@@ -7,4 +7,6 @@
 
 #include "polyfills/base/logging.h"
 
+#define DUMP_WILL_BE_NOTREACHED() GURL_DCHECK(false)
+
 #endif /* POLYFILLS_BASE_NOTREACHED_H_ */
diff --git a/url/BUILD b/url/BUILD
index 327c38a..fba7827 100644
--- a/url/BUILD
+++ b/url/BUILD
@@ -1,8 +1,9 @@
+load("@rules_cc//cc:defs.bzl", "cc_library")
+
 # 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.
 load("//build_config:build_config.bzl", "build_config")
-load("@rules_cc//cc:defs.bzl", "cc_library")
 
 idna_src = select({
     "//build_config:with_system_icu": ["url_idna_icu.cc"],
@@ -24,13 +25,13 @@
         "url_canon_internal_file.h",
         "url_canon_ip.cc",
         "url_canon_mailtourl.cc",
+        "url_canon_non_special_url.cc",
         "url_canon_path.cc",
         "url_canon_pathurl.cc",
         "url_canon_query.cc",
         "url_canon_relative.cc",
         "url_canon_stdstring.cc",
         "url_canon_stdurl.cc",
-        "url_constants.cc",
         "url_features.cc",
         "url_parse_file.cc",
         "url_parse_internal.h",
diff --git a/url/gurl.cc b/url/gurl.cc
index f905498..ed462fb 100644
--- a/url/gurl.cc
+++ b/url/gurl.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "url/gurl.h"
 
 #include <stddef.h>
@@ -13,16 +18,16 @@
 #include <utility>
 
 #include "polyfills/base/check_op.h"
+#include "polyfills/base/dcheck_is_on.h"
 #include "base/no_destructor.h"
 #include "polyfills/base/notreached.h"
 #include "base/strings/string_util.h"
-#include "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"
 #include "polyfills/base/trace_event/memory_usage_estimator.h"
+#include "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"
 #include "url/url_canon_stdstring.h"
 #include "url/url_util.h"
 
-GURL::GURL() : is_valid_(false) {
-}
+GURL::GURL() : is_valid_(false) {}
 
 GURL::GURL(const GURL& other)
     : spec_(other.spec_),
@@ -73,9 +78,8 @@
 template <typename T, typename CharT>
 void GURL::InitCanonical(T input_spec, bool trim_path_end) {
   url::StdStringCanonOutput output(&spec_);
-  is_valid_ = url::Canonicalize(
-      input_spec.data(), static_cast<int>(input_spec.length()), trim_path_end,
-      NULL, &output, &parsed_);
+  is_valid_ =
+      url::Canonicalize(input_spec, trim_path_end, nullptr, &output, &parsed_);
 
   output.Complete();  // Must be done before using string.
   if (is_valid_ && SchemeIsFileSystem()) {
@@ -92,7 +96,7 @@
                                         *parsed_.inner_parsed(), true);
   }
 
-#ifndef NDEBUG
+#if GURL_DCHECK_IS_ON()
   // For testing purposes, check that the parsed canonical URL is identical to
   // what we would have produced. Skip checking for invalid URLs have no meaning
   // and we can't always canonicalize then reproducibly.
@@ -102,8 +106,7 @@
     // We can't do this check on the inner_url of a filesystem URL, as
     // canonical_spec actually points to the start of the outer URL, so we'd
     // end up with infinite recursion in this constructor.
-    if (!url::FindAndCompareScheme(spec_.data(), spec_.length(),
-                                   url::kFileSystemScheme, &scheme) ||
+    if (!url::FindAndCompareScheme(spec_, url::kFileSystemScheme, &scheme) ||
         scheme.begin == parsed_.scheme.begin) {
       // We need to retain trailing whitespace on path URLs, as the |parsed_|
       // spec we originally received may legitimately contain trailing white-
@@ -159,20 +162,12 @@
   if (is_valid_ || spec_.empty())
     return spec_;
 
-  // TODO(crbug.com/851128): Make sure this no longer hits before making
-  // NOTREACHED_NORETURN();
-  GURL_NOTREACHED() << "Trying to get the spec of an invalid URL!";
+  // TODO(crbug.com/40580068): Make sure this no longer hits before making
+  // GURL_NOTREACHED();
+  DUMP_WILL_BE_NOTREACHED() << "Trying to get the spec of an invalid URL!";
   return gurl_base::EmptyString();
 }
 
-bool GURL::operator<(const GURL& other) const {
-  return spec_ < other.spec_;
-}
-
-bool GURL::operator>(const GURL& other) const {
-  return spec_ > other.spec_;
-}
-
 // Note: code duplicated below (it's inconvenient to use a template here).
 GURL GURL::Resolve(std::string_view relative) const {
   // Not allowed for invalid URLs.
@@ -181,10 +176,8 @@
 
   GURL result;
   url::StdStringCanonOutput output(&result.spec_);
-  if (!url::ResolveRelative(spec_.data(), static_cast<int>(spec_.length()),
-                            parsed_, relative.data(),
-                            static_cast<int>(relative.length()),
-                            nullptr, &output, &result.parsed_)) {
+  if (!url::ResolveRelative(spec_, parsed_, relative, nullptr, &output,
+                            &result.parsed_)) {
     // Error resolving, return an empty URL.
     return GURL();
   }
@@ -207,10 +200,8 @@
 
   GURL result;
   url::StdStringCanonOutput output(&result.spec_);
-  if (!url::ResolveRelative(spec_.data(), static_cast<int>(spec_.length()),
-                            parsed_, relative.data(),
-                            static_cast<int>(relative.length()),
-                            nullptr, &output, &result.parsed_)) {
+  if (!url::ResolveRelative(spec_, parsed_, relative, nullptr, &output,
+                            &result.parsed_)) {
     // Error resolving, return an empty URL.
     return GURL();
   }
@@ -234,9 +225,8 @@
     return GURL();
 
   url::StdStringCanonOutput output(&result.spec_);
-  result.is_valid_ = url::ReplaceComponents(
-      spec_.data(), static_cast<int>(spec_.length()), parsed_, replacements,
-      NULL, &output, &result.parsed_);
+  result.is_valid_ = url::ReplaceComponents(spec_, parsed_, replacements,
+                                            nullptr, &output, &result.parsed_);
 
   output.Complete();
 
@@ -253,9 +243,8 @@
     return GURL();
 
   url::StdStringCanonOutput output(&result.spec_);
-  result.is_valid_ = url::ReplaceComponents(
-      spec_.data(), static_cast<int>(spec_.length()), parsed_, replacements,
-      NULL, &output, &result.parsed_);
+  result.is_valid_ = url::ReplaceComponents(spec_, parsed_, replacements,
+                                            nullptr, &output, &result.parsed_);
 
   output.Complete();
 
@@ -293,8 +282,10 @@
 }
 
 GURL GURL::GetAsReferrer() const {
-  if (!is_valid() || !IsReferrerScheme(spec_.data(), parsed_.scheme))
+  if (!is_valid() ||
+      !url::IsReferrerScheme(parsed_.scheme.MaybeAsViewOn(spec_))) {
     return GURL();
+  }
 
   if (!has_ref() && !has_username() && !has_password())
     return GURL(*this);
@@ -344,7 +335,7 @@
 }
 
 bool GURL::IsStandard() const {
-  return url::IsStandard(spec_.data(), parsed_.scheme);
+  return url::IsStandard(parsed_.scheme.MaybeAsViewOn(spec_));
 }
 
 bool GURL::IsAboutBlank() const {
@@ -358,10 +349,7 @@
 bool GURL::SchemeIs(std::string_view lower_ascii_scheme) const {
   GURL_DCHECK(gurl_base::IsStringASCII(lower_ascii_scheme));
   GURL_DCHECK(gurl_base::ToLowerASCII(lower_ascii_scheme) == lower_ascii_scheme);
-
-  if (!has_scheme())
-    return lower_ascii_scheme.empty();
-  return scheme_piece() == lower_ascii_scheme;
+  return scheme() == lower_ascii_scheme;
 }
 
 bool GURL::SchemeIsHTTPOrHTTPS() const {
@@ -373,10 +361,7 @@
 }
 
 bool GURL::SchemeIsCryptographic() const {
-  if (!has_scheme())
-    return false;
-  return SchemeIsCryptographic(scheme_piece());
-}
+  return has_scheme() && SchemeIsCryptographic(scheme());}
 
 bool GURL::SchemeIsCryptographic(std::string_view lower_ascii_scheme) {
   GURL_DCHECK(gurl_base::IsStringASCII(lower_ascii_scheme));
@@ -395,21 +380,20 @@
 
 int GURL::IntPort() const {
   if (parsed_.port.is_nonempty())
-    return url::ParsePort(spec_.data(), parsed_.port);
+    return url::ParsePort(spec_, parsed_.port);
   return url::PORT_UNSPECIFIED;
 }
 
 int GURL::EffectiveIntPort() const {
   int int_port = IntPort();
   if (int_port == url::PORT_UNSPECIFIED && IsStandard())
-    return url::DefaultPortForScheme(spec_.data() + parsed_.scheme.begin,
-                                     parsed_.scheme.len);
+    return url::DefaultPortForScheme(scheme());
   return int_port;
 }
 
 std::string GURL::ExtractFileName() const {
   url::Component file_component;
-  url::ExtractFileName(spec_.data(), parsed_.path, &file_component);
+  url::ExtractFileName(spec_, parsed_.path, &file_component);
   return ComponentString(file_component);
 }
 
@@ -464,7 +448,7 @@
 }
 
 bool GURL::HostIsIPAddress() const {
-  return is_valid_ && url::HostIsIPAddress(host_piece());
+  return is_valid_ && url::HostIsIPAddress(host());
 }
 
 const GURL& GURL::EmptyGURL() {
@@ -479,7 +463,7 @@
   // FileSystem URLs have empty host_piece, so check this first.
   if (inner_url_ && SchemeIsFileSystem())
     return inner_url_->DomainIs(canonical_domain);
-  return url::DomainIs(host_piece(), canonical_domain);
+  return url::DomainIs(host(), canonical_domain);
 }
 
 bool GURL::EqualsIgnoringRef(const GURL& other) const {
@@ -504,33 +488,19 @@
 }
 
 bool GURL::IsAboutUrl(std::string_view allowed_path) const {
-  if (!SchemeIs(url::kAboutScheme))
-    return false;
-
-  if (has_host() || has_username() || has_password() || has_port())
-    return false;
-
-  return IsAboutPath(path_piece(), allowed_path);
+  bool has_wrong_components =
+      has_host() || has_username() || has_password() || has_port();
+  return SchemeIs(url::kAboutScheme) && !has_wrong_components &&
+         IsAboutPath(path(), allowed_path);
 }
 
 // static
 bool GURL::IsAboutPath(std::string_view actual_path,
                        std::string_view allowed_path) {
-  if (!gurl_base::StartsWith(actual_path, allowed_path))
-    return false;
-
-  if (actual_path.size() == allowed_path.size()) {
-    GURL_DCHECK_EQ(actual_path, allowed_path);
-    return true;
-  }
-
-  if ((actual_path.size() == allowed_path.size() + 1) &&
-      actual_path.back() == '/') {
-    GURL_DCHECK_EQ(actual_path, std::string(allowed_path) + '/');
-    return true;
-  }
-
-  return false;
+  return actual_path == allowed_path ||
+         (actual_path.size() == allowed_path.size() + 1 &&
+          actual_path.back() == '/' &&
+          gurl_base::StartsWith(actual_path, allowed_path));
 }
 
 void GURL::WriteIntoTrace(perfetto::TracedValue context) const {
@@ -545,37 +515,9 @@
   return x.possibly_invalid_spec() == y.possibly_invalid_spec();
 }
 
-bool operator!=(const GURL& x, const GURL& y) {
-  return !(x == y);
-}
-
 bool operator==(const GURL& x, std::string_view spec) {
   GURL_DCHECK_EQ(GURL(spec).possibly_invalid_spec(), spec)
       << "Comparisons of GURLs and strings must ensure as a precondition that "
          "the string is fully canonicalized.";
   return x.possibly_invalid_spec() == spec;
 }
-
-bool operator==(std::string_view spec, const GURL& x) {
-  return x == spec;
-}
-
-bool operator!=(const GURL& x, std::string_view spec) {
-  return !(x == spec);
-}
-
-bool operator!=(std::string_view spec, const GURL& x) {
-  return !(x == spec);
-}
-
-namespace url::debug {
-
-ScopedUrlCrashKey::ScopedUrlCrashKey(gurl_base::debug::CrashKeyString* crash_key,
-                                     const GURL& url)
-    : scoped_string_value_(
-          crash_key,
-          url.is_empty() ? "<empty url>" : url.possibly_invalid_spec()) {}
-
-ScopedUrlCrashKey::~ScopedUrlCrashKey() = default;
-
-}  // namespace url::debug
diff --git a/url/gurl.h b/url/gurl.h
index 931c803..0ccc71a 100644
--- a/url/gurl.h
+++ b/url/gurl.h
@@ -12,9 +12,8 @@
 #include <string>
 #include <string_view>
 
+#include "base/compiler_specific.h"
 #include "polyfills/base/component_export.h"
-#include "polyfills/base/debug/alias.h"
-#include "base/debug/crash_logging.h"
 #include "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"
 #include "url/third_party/mozilla/url_parse.h"
 #include "url/url_canon.h"
@@ -44,6 +43,10 @@
 // path that contains a literal '#'. Using string concatenation will generate a
 // URL with a truncated path and a reference fragment, while ReplaceComponents
 // will know to escape this and produce the desired result.
+//
+// WARNING: While there is no length limit on GURLs, the Mojo serialization
+// code will replace any very long URL with an invalid GURL.
+// See url::mojom::kMaxURLChars for more details.
 class COMPONENT_EXPORT(URL) GURL {
  public:
   using Replacements = url::StringViewReplacements<char>;
@@ -57,7 +60,9 @@
   GURL(const GURL& other);
   GURL(GURL&& other) noexcept;
 
-  // The strings to this constructor should be UTF-8 / UTF-16.
+  // The strings to this constructor should be UTF-8 / UTF-16. They will be
+  // parsed and canonicalized. For example, the host is lower cased, and
+  // characters may be percent-encoded or percent-decoded to normalize the URL.
   explicit GURL(std::string_view url_string);
   explicit GURL(std::u16string_view url_string);
 
@@ -135,8 +140,9 @@
   }
 
   // Allows GURL to used as a key in STL (for example, a std::set or std::map).
-  bool operator<(const GURL& other) const;
-  bool operator>(const GURL& other) const;
+  constexpr friend auto operator<=>(const GURL& lhs, const GURL& rhs) {
+    return lhs.spec_ <=> rhs.spec_;
+  }
 
   // Resolves a URL that's possibly relative to this object's URL, and returns
   // it. Absolute URLs are also handled according to the rules of URLs on web
@@ -152,8 +158,8 @@
   //
   // It is an error to resolve a URL relative to an invalid URL. The result
   // will be the empty URL.
-  GURL Resolve(std::string_view relative) const;
-  GURL Resolve(std::u16string_view relative) const;
+  [[nodiscard]] GURL Resolve(std::string_view relative) const;
+  [[nodiscard]] GURL Resolve(std::u16string_view relative) const;
 
   // Creates a new GURL by replacing the current URL's components with the
   // supplied versions. See the Replacements class in url_canon.h for more.
@@ -166,8 +172,8 @@
   //
   // Note that this intentionally disallows direct use of url::Replacements,
   // which is harder to use correctly.
-  GURL ReplaceComponents(const Replacements& replacements) const;
-  GURL ReplaceComponents(const ReplacementsW& replacements) const;
+  [[nodiscard]] GURL ReplaceComponents(const Replacements& replacements) const;
+  [[nodiscard]] GURL ReplaceComponents(const ReplacementsW& replacements) const;
 
   // A helper function that is equivalent to replacing the path with a slash
   // and clearing out everything after that. We sometimes need to know just the
@@ -178,7 +184,7 @@
   //
   // It is an error to get an empty path on an invalid URL. The result
   // will be the empty URL.
-  GURL GetWithEmptyPath() const;
+  [[nodiscard]] GURL GetWithEmptyPath() const;
 
   // A helper function to return a GURL without the filename, query values, and
   // fragment. For example,
@@ -187,7 +193,7 @@
   // GURL("https://www.foo.com/bar/").GetWithoutFilename().spec()
   // will return "https://www.foo.com/bar/". If the GURL is invalid or missing a
   // scheme, authority or path, it will return an empty, invalid GURL.
-  GURL GetWithoutFilename() const;
+  [[nodiscard]] GURL GetWithoutFilename() const;
 
   // A helper function to return a GURL without the Ref (also named Fragment
   // Identifier). For example,
@@ -195,7 +201,7 @@
   // will return "https://www.foo.com/index.html".
   // If the GURL is invalid or missing a
   // scheme, authority or path, it will return an empty, invalid GURL.
-  GURL GetWithoutRef() const;
+  [[nodiscard]] GURL GetWithoutRef() const;
 
   // A helper function to return a GURL containing just the scheme, host,
   // and port from a URL. Equivalent to clearing any username and password,
@@ -212,13 +218,13 @@
   // conversions will likely return a wrong result for about:blank and/or
   // in the presence of iframe.sandbox attribute. Prefer to get origins directly
   // from the source (e.g. RenderFrameHost::GetLastCommittedOrigin).
-  GURL DeprecatedGetOriginAsURL() const;
+  [[nodiscard]] GURL DeprecatedGetOriginAsURL() const;
 
   // A helper function to return a GURL stripped from the elements that are not
   // supposed to be sent as HTTP referrer: username, password and ref fragment.
   // For invalid URLs or URLs that no valid referrers, an empty URL will be
   // returned.
-  GURL GetAsReferrer() const;
+  [[nodiscard]] GURL GetAsReferrer() const;
 
   // Returns true if the scheme for the current URL is a known "standard-format"
   // scheme. A standard-format scheme adheres to what RFC 3986 calls "generic
@@ -285,8 +291,12 @@
   //
   // It is an error to get the content of an invalid URL: the result will be an
   // empty string.
+  //
+  // Important note: GetContent() and GetContentPiece() for some non-special
+  // URLs have different outcomes to comply with standards. Please see
+  // GURLTest::ContentForNonStandardURLs for more information.
   std::string GetContent() const;
-  std::string_view GetContentPiece() const;
+  std::string_view GetContentPiece() const LIFETIME_BOUND;
 
   // Returns true if the hostname is an IP address. Note: this function isn't
   // as cheap as a simple getter because it re-parses the hostname to verify.
@@ -294,26 +304,20 @@
 
   // Not including the colon. If you are comparing schemes, prefer SchemeIs.
   bool has_scheme() const { return parsed_.scheme.is_valid(); }
-  std::string scheme() const {
-    return ComponentString(parsed_.scheme);
-  }
-  std::string_view scheme_piece() const {
+  std::string GetScheme() const { return ComponentString(parsed_.scheme); }
+  std::string_view scheme() const LIFETIME_BOUND {
     return ComponentStringPiece(parsed_.scheme);
   }
 
   bool has_username() const { return parsed_.username.is_valid(); }
-  std::string username() const {
-    return ComponentString(parsed_.username);
-  }
-  std::string_view username_piece() const {
+  std::string GetUsername() const { return ComponentString(parsed_.username); }
+  std::string_view username() const LIFETIME_BOUND {
     return ComponentStringPiece(parsed_.username);
   }
 
   bool has_password() const { return parsed_.password.is_valid(); }
-  std::string password() const {
-    return ComponentString(parsed_.password);
-  }
-  std::string_view password_piece() const {
+  std::string GetPassword() const { return ComponentString(parsed_.password); }
+  std::string_view password() const LIFETIME_BOUND {
     return ComponentStringPiece(parsed_.password);
   }
 
@@ -324,10 +328,8 @@
     // Note that hosts are special, absence of host means length 0.
     return parsed_.host.is_nonempty();
   }
-  std::string host() const {
-    return ComponentString(parsed_.host);
-  }
-  std::string_view host_piece() const {
+  std::string GetHost() const { return ComponentString(parsed_.host); }
+  std::string_view host() const LIFETIME_BOUND {
     return ComponentStringPiece(parsed_.host);
   }
 
@@ -335,39 +337,31 @@
   // or EffectiveIntPort() instead of these. The getters will not include the
   // ':'.
   bool has_port() const { return parsed_.port.is_valid(); }
-  std::string port() const {
-    return ComponentString(parsed_.port);
-  }
-  std::string_view port_piece() const {
+  std::string GetPort() const { return ComponentString(parsed_.port); }
+  std::string_view port() const LIFETIME_BOUND {
     return ComponentStringPiece(parsed_.port);
   }
 
   // Including first slash following host, up to the query. The URL
   // "http://www.google.com/" has a path of "/".
   bool has_path() const { return parsed_.path.is_valid(); }
-  std::string path() const {
-    return ComponentString(parsed_.path);
-  }
-  std::string_view path_piece() const {
+  std::string GetPath() const { return ComponentString(parsed_.path); }
+  std::string_view path() const LIFETIME_BOUND {
     return ComponentStringPiece(parsed_.path);
   }
 
   // Stuff following '?' up to the ref. The getters will not include the '?'.
   bool has_query() const { return parsed_.query.is_valid(); }
-  std::string query() const {
-    return ComponentString(parsed_.query);
-  }
-  std::string_view query_piece() const {
+  std::string GetQuery() const { return ComponentString(parsed_.query); }
+  std::string_view query() const LIFETIME_BOUND {
     return ComponentStringPiece(parsed_.query);
   }
 
   // Stuff following '#' to the end of the string. This will be %-escaped UTF-8.
   // The getters will not include the '#'.
   bool has_ref() const { return parsed_.ref.is_valid(); }
-  std::string ref() const {
-    return ComponentString(parsed_.ref);
-  }
-  std::string_view ref_piece() const {
+  std::string GetRef() const { return ComponentString(parsed_.ref); }
+  std::string_view ref() const LIFETIME_BOUND {
     return ComponentStringPiece(parsed_.ref);
   }
 
@@ -389,14 +383,14 @@
   std::string PathForRequest() const;
 
   // Returns the same characters as PathForRequest(), avoiding a copy.
-  std::string_view PathForRequestPiece() const;
+  std::string_view PathForRequestPiece() const LIFETIME_BOUND;
 
   // Returns the host, excluding the square brackets surrounding IPv6 address
   // literals. This can be useful for passing to getaddrinfo().
   std::string HostNoBrackets() const;
 
   // Returns the same characters as HostNoBrackets(), avoiding a copy.
-  std::string_view HostNoBracketsPiece() const;
+  std::string_view HostNoBracketsPiece() const LIFETIME_BOUND;
 
   // Returns true if this URL's host matches or is in the same domain as
   // the given input string. For example, if the hostname of the URL is
@@ -439,12 +433,18 @@
   // See base/trace_event/memory_usage_estimator.h for more info.
   size_t EstimateMemoryUsage() const;
 
-  // Helper used by GURL::IsAboutUrl and KURL::IsAboutURL.
+  // Helper used by GURL::IsAboutUrl and KURL::IsAboutURL. Returns true if
+  // actual_path == allowed_path or actual_path == allowed_path + '/'.
   static bool IsAboutPath(std::string_view actual_path,
                           std::string_view allowed_path);
 
   void WriteIntoTrace(perfetto::TracedValue context) const;
 
+  template <typename H>
+  friend H AbslHashValue(H h, const GURL& c) {
+    return H::combine(std::move(h), c.spec_);
+  }
+
  private:
   // Variant of the string parsing constructor that allows the caller to elect
   // retain trailing whitespace, if any, on the passed URL spec, but only if
@@ -466,7 +466,7 @@
   std::string ComponentString(const url::Component& comp) const {
     return std::string(ComponentStringPiece(comp));
   }
-  std::string_view ComponentStringPiece(const url::Component& comp) const {
+  std::string_view ComponentStringPiece(const url::Component& comp) const LIFETIME_BOUND {
     if (comp.is_empty())
       return std::string_view();
     return std::string_view(spec_).substr(static_cast<size_t>(comp.begin),
@@ -495,40 +495,11 @@
 std::ostream& operator<<(std::ostream& out, const GURL& url);
 
 COMPONENT_EXPORT(URL) bool operator==(const GURL& x, const GURL& y);
-COMPONENT_EXPORT(URL) bool operator!=(const GURL& x, const GURL& y);
 
 // Equality operator for comparing raw spec_. This should be used in place of
 // url == GURL(spec) where |spec| is known (i.e. constants). This is to prevent
 // needlessly re-parsing |spec| into a temporary GURL.
 COMPONENT_EXPORT(URL)
 bool operator==(const GURL& x, std::string_view spec);
-COMPONENT_EXPORT(URL)
-bool operator==(std::string_view spec, const GURL& x);
-COMPONENT_EXPORT(URL)
-bool operator!=(const GURL& x, std::string_view spec);
-COMPONENT_EXPORT(URL)
-bool operator!=(std::string_view spec, const GURL& x);
-
-// DEBUG_ALIAS_FOR_GURL(var_name, url) copies |url| into a new stack-allocated
-// variable named |<var_name>|.  This helps ensure that the value of |url| gets
-// preserved in crash dumps.
-#define DEBUG_ALIAS_FOR_GURL(var_name, url) \
-  DEBUG_ALIAS_FOR_CSTR(var_name, (url).possibly_invalid_spec().c_str(), 128)
-
-namespace url::debug {
-
-class COMPONENT_EXPORT(URL) ScopedUrlCrashKey {
- public:
-  ScopedUrlCrashKey(gurl_base::debug::CrashKeyString* crash_key, const GURL& value);
-  ~ScopedUrlCrashKey();
-
-  ScopedUrlCrashKey(const ScopedUrlCrashKey&) = delete;
-  ScopedUrlCrashKey& operator=(const ScopedUrlCrashKey&) = delete;
-
- private:
-  gurl_base::debug::ScopedCrashKeyString scoped_string_value_;
-};
-
-}  // namespace url::debug
 
 #endif  // URL_GURL_H_
diff --git a/url/gurl_debug.cc b/url/gurl_debug.cc
new file mode 100644
index 0000000..7c2fada
--- /dev/null
+++ b/url/gurl_debug.cc
@@ -0,0 +1,19 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "url/gurl_debug.h"
+
+#include "url/gurl.h"
+
+namespace url::debug {
+
+ScopedUrlCrashKey::ScopedUrlCrashKey(gurl_base::debug::CrashKeyString* crash_key,
+                                     const GURL& url)
+    : scoped_string_value_(
+          crash_key,
+          url.is_empty() ? "<empty url>" : url.possibly_invalid_spec()) {}
+
+ScopedUrlCrashKey::~ScopedUrlCrashKey() = default;
+
+}  // namespace url::debug
diff --git a/url/gurl_debug.h b/url/gurl_debug.h
new file mode 100644
index 0000000..5c0c65c
--- /dev/null
+++ b/url/gurl_debug.h
@@ -0,0 +1,36 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef URL_GURL_DEBUG_H_
+#define URL_GURL_DEBUG_H_
+
+#include "polyfills/base/component_export.h"
+#include "polyfills/base/debug/alias.h"
+#include "base/debug/crash_logging.h"
+
+// DEBUG_ALIAS_FOR_GURL(var_name, url) copies |url| into a new stack-allocated
+// variable named |<var_name>|.  This helps ensure that the value of |url| gets
+// preserved in crash dumps.
+#define DEBUG_ALIAS_FOR_GURL(var_name, url) \
+  DEBUG_ALIAS_FOR_CSTR(var_name, (url).possibly_invalid_spec().c_str(), 128)
+
+class GURL;
+
+namespace url::debug {
+
+class COMPONENT_EXPORT(URL) ScopedUrlCrashKey {
+ public:
+  ScopedUrlCrashKey(gurl_base::debug::CrashKeyString* crash_key, const GURL& value);
+  ~ScopedUrlCrashKey();
+
+  ScopedUrlCrashKey(const ScopedUrlCrashKey&) = delete;
+  ScopedUrlCrashKey& operator=(const ScopedUrlCrashKey&) = delete;
+
+ private:
+  gurl_base::debug::ScopedCrashKeyString scoped_string_value_;
+};
+
+}  // namespace url::debug
+
+#endif  // URL_GURL_DEBUG_H_
diff --git a/url/gurl_unittest.cc b/url/gurl_unittest.cc
index 36dd969..de7394e 100644
--- a/url/gurl_unittest.cc
+++ b/url/gurl_unittest.cc
@@ -6,30 +6,107 @@
 
 #include <stddef.h>
 
+#include <array>
+
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl_abstract_tests.h"
+#include "url/gurl_debug.h"
 #include "url/origin.h"
 #include "url/url_canon.h"
 #include "url/url_test_utils.h"
 
 namespace url {
 
-namespace {
+class GURLTest : public testing::Test {
+ public:
+  GURLTest() = default;
 
-// Returns the canonicalized string for the given URL string for the
-// GURLTest.Types test.
-std::string TypesTestCase(const char* src) {
-  GURL gurl(src);
-  return gurl.possibly_invalid_spec();
-}
+ protected:
+  struct ResolveCase {
+    std::string_view base;
+    std::string_view relative;
+    bool expected_valid;
+    std::optional<std::string_view> expected;
+  };
 
-}  // namespace
+  using ApplyReplacementsFunc = GURL(const GURL&);
+
+  struct ReplaceCase {
+    std::string_view base;
+    ApplyReplacementsFunc* apply_replacements;
+    std::string_view expected;
+  };
+
+  struct ReplaceHostCase {
+    std::string_view base;
+    std::string_view replacement_host;
+    std::string_view expected;
+  };
+
+  struct ReplacePathCase {
+    std::string_view base;
+    std::string_view replacement_path;
+    std::string_view expected;
+  };
+
+  // Returns the canonicalized string for the given URL string for the
+  // GURLTest.Types test.
+  std::string TypesTestCase(const char* src) {
+    GURL gurl(src);
+    return gurl.possibly_invalid_spec();
+  }
+
+  void TestResolve(const ResolveCase& resolve_case) {
+    // 8-bit code path.
+    GURL input(resolve_case.base);
+    GURL output = input.Resolve(resolve_case.relative);
+    EXPECT_EQ(resolve_case.expected_valid, output.is_valid());
+    EXPECT_EQ(resolve_case.expected, output.spec());
+    EXPECT_EQ(output.SchemeIsFileSystem(), output.inner_url() != nullptr);
+
+    // Wide code path.
+    GURL inputw(gurl_base::UTF8ToUTF16(resolve_case.base));
+    GURL outputw = input.Resolve(gurl_base::UTF8ToUTF16(resolve_case.relative));
+    EXPECT_EQ(resolve_case.expected_valid, outputw.is_valid());
+    EXPECT_EQ(resolve_case.expected, outputw.spec());
+    EXPECT_EQ(outputw.SchemeIsFileSystem(), outputw.inner_url() != nullptr);
+  }
+
+  void TestReplace(const ReplaceCase& replace) {
+    GURL output = replace.apply_replacements(GURL(replace.base));
+    EXPECT_EQ(output.spec(), replace.expected);
+    EXPECT_EQ(output.SchemeIsFileSystem(), output.inner_url() != nullptr);
+    if (output.SchemeIsFileSystem()) {
+      // TODO(mmenke): inner_url()->spec() is currently the same as the spec()
+      // for the GURL itself.  This should be fixed.
+      // See https://crbug.com/619596
+      EXPECT_EQ(output.inner_url()->spec(), replace.expected);
+    }
+  }
+
+  void TestReplaceHost(const ReplaceHostCase& replace) {
+    GURL url(replace.base);
+    GURL::Replacements replacements;
+    replacements.SetHostStr(replace.replacement_host);
+    GURL output = url.ReplaceComponents(replacements);
+    EXPECT_EQ(output.spec(), replace.expected);
+  }
+
+  void TestReplacePath(const ReplacePathCase& replace) {
+    GURL url(replace.base);
+    GURL::Replacements replacements;
+    replacements.SetPathStr(replace.replacement_path);
+    GURL output = url.ReplaceComponents(replacements);
+    EXPECT_EQ(output.spec(), replace.expected);
+  }
+};
 
 // Different types of URLs should be handled differently, and handed off to
 // different canonicalizers.
-TEST(GURLTest, Types) {
+TEST_F(GURLTest, Types) {
   // URLs with unknown schemes should be treated as path URLs, even when they
   // have things like "://".
   EXPECT_EQ("something:///HOSTNAME.com/",
@@ -54,7 +131,7 @@
 // Test the basic creation and querying of components in a GURL. We assume that
 // the parser is already tested and works, so we are mostly interested if the
 // object does the right thing with the results.
-TEST(GURLTest, Components) {
+TEST_F(GURLTest, Components) {
   GURL empty_url(u"");
   EXPECT_TRUE(empty_url.is_empty());
   EXPECT_FALSE(empty_url.is_valid());
@@ -68,75 +145,93 @@
   // This is the narrow version of the URL, which should match the wide input.
   EXPECT_EQ("http://user:pass@google.com:99/foo;bar?q=a#ref", url.spec());
 
-  EXPECT_EQ("http", url.scheme());
-  EXPECT_EQ("user", url.username());
-  EXPECT_EQ("pass", url.password());
-  EXPECT_EQ("google.com", url.host());
-  EXPECT_EQ("99", url.port());
+  EXPECT_EQ("http", url.GetScheme());
+  EXPECT_EQ("user", url.GetUsername());
+  EXPECT_EQ("pass", url.GetPassword());
+  EXPECT_EQ("google.com", url.GetHost());
+  EXPECT_EQ("99", url.GetPort());
   EXPECT_EQ(99, url.IntPort());
-  EXPECT_EQ("/foo;bar", url.path());
-  EXPECT_EQ("q=a", url.query());
-  EXPECT_EQ("ref", url.ref());
+  EXPECT_EQ("/foo;bar", url.GetPath());
+  EXPECT_EQ("q=a", url.GetQuery());
+  EXPECT_EQ("ref", url.GetRef());
 
   // Test parsing userinfo with special characters.
   GURL url_special_pass("http://user:%40!$&'()*+,;=:@google.com:12345");
   EXPECT_TRUE(url_special_pass.is_valid());
   // GURL canonicalizes some delimiters.
-  EXPECT_EQ("%40!$&%27()*+,%3B%3D%3A", url_special_pass.password());
-  EXPECT_EQ("google.com", url_special_pass.host());
-  EXPECT_EQ("12345", url_special_pass.port());
+  EXPECT_EQ("%40!$&%27()*+,%3B%3D%3A", url_special_pass.GetPassword());
+  EXPECT_EQ("google.com", url_special_pass.GetHost());
+  EXPECT_EQ("12345", url_special_pass.GetPort());
+
+  // Test path collapsing.
+  GURL url_path_collapse("http://example.com/a/./b/c/d/../../e");
+  EXPECT_EQ("/a/b/e", url_path_collapse.GetPath());
+
+  // Test an IDNA (Internationalizing Domain Names in Applications) host.
+  GURL url_idna("http://Bücher.exAMple/");
+  EXPECT_EQ("xn--bcher-kva.example", url_idna.GetHost());
+
+  // Test non-ASCII characters, outside of the host (IDNA).
+  GURL url_non_ascii("http://example.com/foo/aβc%2Etxt?q=r🙂s");
+  EXPECT_EQ("/foo/a%CE%B2c.txt", url_non_ascii.GetPath());
+  EXPECT_EQ("q=r%F0%9F%99%82s", url_non_ascii.GetQuery());
+
+  // Test already percent-escaped strings.
+  GURL url_percent_escaped("http://example.com/a/./%2e/i%2E%2F%2fj?q=r%2Es");
+  EXPECT_EQ("/a/i.%2F%2fj", url_percent_escaped.GetPath());
+  EXPECT_EQ("q=r%2Es", url_percent_escaped.GetQuery());
 }
 
-TEST(GURLTest, Empty) {
+TEST_F(GURLTest, Empty) {
   GURL url;
   EXPECT_FALSE(url.is_valid());
   EXPECT_EQ("", url.spec());
 
-  EXPECT_EQ("", url.scheme());
-  EXPECT_EQ("", url.username());
-  EXPECT_EQ("", url.password());
-  EXPECT_EQ("", url.host());
-  EXPECT_EQ("", url.port());
+  EXPECT_EQ("", url.GetScheme());
+  EXPECT_EQ("", url.GetUsername());
+  EXPECT_EQ("", url.GetPassword());
+  EXPECT_EQ("", url.GetHost());
+  EXPECT_EQ("", url.GetPort());
   EXPECT_EQ(PORT_UNSPECIFIED, url.IntPort());
-  EXPECT_EQ("", url.path());
-  EXPECT_EQ("", url.query());
-  EXPECT_EQ("", url.ref());
+  EXPECT_EQ("", url.GetPath());
+  EXPECT_EQ("", url.GetQuery());
+  EXPECT_EQ("", url.GetRef());
 }
 
-TEST(GURLTest, Copy) {
+TEST_F(GURLTest, Copy) {
   GURL url(u"http://user:pass@google.com:99/foo;bar?q=a#ref");
 
   GURL url2(url);
   EXPECT_TRUE(url2.is_valid());
 
   EXPECT_EQ("http://user:pass@google.com:99/foo;bar?q=a#ref", url2.spec());
-  EXPECT_EQ("http", url2.scheme());
-  EXPECT_EQ("user", url2.username());
-  EXPECT_EQ("pass", url2.password());
-  EXPECT_EQ("google.com", url2.host());
-  EXPECT_EQ("99", url2.port());
+  EXPECT_EQ("http", url2.GetScheme());
+  EXPECT_EQ("user", url2.GetUsername());
+  EXPECT_EQ("pass", url2.GetPassword());
+  EXPECT_EQ("google.com", url2.GetHost());
+  EXPECT_EQ("99", url2.GetPort());
   EXPECT_EQ(99, url2.IntPort());
-  EXPECT_EQ("/foo;bar", url2.path());
-  EXPECT_EQ("q=a", url2.query());
-  EXPECT_EQ("ref", url2.ref());
+  EXPECT_EQ("/foo;bar", url2.GetPath());
+  EXPECT_EQ("q=a", url2.GetQuery());
+  EXPECT_EQ("ref", url2.GetRef());
 
   // Copying of invalid URL should be invalid
   GURL invalid;
   GURL invalid2(invalid);
   EXPECT_FALSE(invalid2.is_valid());
   EXPECT_EQ("", invalid2.spec());
-  EXPECT_EQ("", invalid2.scheme());
-  EXPECT_EQ("", invalid2.username());
-  EXPECT_EQ("", invalid2.password());
-  EXPECT_EQ("", invalid2.host());
-  EXPECT_EQ("", invalid2.port());
+  EXPECT_EQ("", invalid2.GetScheme());
+  EXPECT_EQ("", invalid2.GetUsername());
+  EXPECT_EQ("", invalid2.GetPassword());
+  EXPECT_EQ("", invalid2.GetHost());
+  EXPECT_EQ("", invalid2.GetPort());
   EXPECT_EQ(PORT_UNSPECIFIED, invalid2.IntPort());
-  EXPECT_EQ("", invalid2.path());
-  EXPECT_EQ("", invalid2.query());
-  EXPECT_EQ("", invalid2.ref());
+  EXPECT_EQ("", invalid2.GetPath());
+  EXPECT_EQ("", invalid2.GetQuery());
+  EXPECT_EQ("", invalid2.GetRef());
 }
 
-TEST(GURLTest, Assign) {
+TEST_F(GURLTest, Assign) {
   GURL url(u"http://user:pass@google.com:99/foo;bar?q=a#ref");
 
   GURL url2;
@@ -144,15 +239,15 @@
   EXPECT_TRUE(url2.is_valid());
 
   EXPECT_EQ("http://user:pass@google.com:99/foo;bar?q=a#ref", url2.spec());
-  EXPECT_EQ("http", url2.scheme());
-  EXPECT_EQ("user", url2.username());
-  EXPECT_EQ("pass", url2.password());
-  EXPECT_EQ("google.com", url2.host());
-  EXPECT_EQ("99", url2.port());
+  EXPECT_EQ("http", url2.GetScheme());
+  EXPECT_EQ("user", url2.GetUsername());
+  EXPECT_EQ("pass", url2.GetPassword());
+  EXPECT_EQ("google.com", url2.GetHost());
+  EXPECT_EQ("99", url2.GetPort());
   EXPECT_EQ(99, url2.IntPort());
-  EXPECT_EQ("/foo;bar", url2.path());
-  EXPECT_EQ("q=a", url2.query());
-  EXPECT_EQ("ref", url2.ref());
+  EXPECT_EQ("/foo;bar", url2.GetPath());
+  EXPECT_EQ("q=a", url2.GetQuery());
+  EXPECT_EQ("ref", url2.GetRef());
 
   // Assignment of invalid URL should be invalid
   GURL invalid;
@@ -160,56 +255,56 @@
   invalid2 = invalid;
   EXPECT_FALSE(invalid2.is_valid());
   EXPECT_EQ("", invalid2.spec());
-  EXPECT_EQ("", invalid2.scheme());
-  EXPECT_EQ("", invalid2.username());
-  EXPECT_EQ("", invalid2.password());
-  EXPECT_EQ("", invalid2.host());
-  EXPECT_EQ("", invalid2.port());
+  EXPECT_EQ("", invalid2.GetScheme());
+  EXPECT_EQ("", invalid2.GetUsername());
+  EXPECT_EQ("", invalid2.GetPassword());
+  EXPECT_EQ("", invalid2.GetHost());
+  EXPECT_EQ("", invalid2.GetPort());
   EXPECT_EQ(PORT_UNSPECIFIED, invalid2.IntPort());
-  EXPECT_EQ("", invalid2.path());
-  EXPECT_EQ("", invalid2.query());
-  EXPECT_EQ("", invalid2.ref());
+  EXPECT_EQ("", invalid2.GetPath());
+  EXPECT_EQ("", invalid2.GetQuery());
+  EXPECT_EQ("", invalid2.GetRef());
 }
 
 // This is a regression test for http://crbug.com/309975.
-TEST(GURLTest, SelfAssign) {
+TEST_F(GURLTest, SelfAssign) {
   GURL a("filesystem:http://example.com/temporary/");
   // This should not crash.
   a = *&a;  // The *& defeats Clang's -Wself-assign warning.
 }
 
-TEST(GURLTest, CopyFileSystem) {
+TEST_F(GURLTest, CopyFileSystem) {
   GURL url(u"filesystem:https://user:pass@google.com:99/t/foo;bar?q=a#ref");
 
   GURL url2(url);
   EXPECT_TRUE(url2.is_valid());
 
   EXPECT_EQ("filesystem:https://google.com:99/t/foo;bar?q=a#ref", url2.spec());
-  EXPECT_EQ("filesystem", url2.scheme());
-  EXPECT_EQ("", url2.username());
-  EXPECT_EQ("", url2.password());
-  EXPECT_EQ("", url2.host());
-  EXPECT_EQ("", url2.port());
+  EXPECT_EQ("filesystem", url2.GetScheme());
+  EXPECT_EQ("", url2.GetUsername());
+  EXPECT_EQ("", url2.GetPassword());
+  EXPECT_EQ("", url2.GetHost());
+  EXPECT_EQ("", url2.GetPort());
   EXPECT_EQ(PORT_UNSPECIFIED, url2.IntPort());
-  EXPECT_EQ("/foo;bar", url2.path());
-  EXPECT_EQ("q=a", url2.query());
-  EXPECT_EQ("ref", url2.ref());
+  EXPECT_EQ("/foo;bar", url2.GetPath());
+  EXPECT_EQ("q=a", url2.GetQuery());
+  EXPECT_EQ("ref", url2.GetRef());
 
   const GURL* inner = url2.inner_url();
   ASSERT_TRUE(inner);
-  EXPECT_EQ("https", inner->scheme());
-  EXPECT_EQ("", inner->username());
-  EXPECT_EQ("", inner->password());
-  EXPECT_EQ("google.com", inner->host());
-  EXPECT_EQ("99", inner->port());
+  EXPECT_EQ("https", inner->GetScheme());
+  EXPECT_EQ("", inner->GetUsername());
+  EXPECT_EQ("", inner->GetPassword());
+  EXPECT_EQ("google.com", inner->GetHost());
+  EXPECT_EQ("99", inner->GetPort());
   EXPECT_EQ(99, inner->IntPort());
-  EXPECT_EQ("/t", inner->path());
-  EXPECT_EQ("", inner->query());
-  EXPECT_EQ("", inner->ref());
+  EXPECT_EQ("/t", inner->GetPath());
+  EXPECT_EQ("", inner->GetQuery());
+  EXPECT_EQ("", inner->GetRef());
 }
 
-TEST(GURLTest, IsValid) {
-  const char* valid_cases[] = {
+TEST_F(GURLTest, IsValid) {
+  static constexpr auto valid_cases = std::to_array<const char*>({
       "http://google.com",
       "unknown://google.com",
       "http://user:pass@google.com",
@@ -221,13 +316,12 @@
       "http://user:pass@google.com:12345/path?k=v#fragment",
       "http:/path",
       "http:path",
-  };
-  for (size_t i = 0; i < std::size(valid_cases); i++) {
-    EXPECT_TRUE(GURL(valid_cases[i]).is_valid())
-        << "Case: " << valid_cases[i];
+  });
+  for (const char* valid_case : valid_cases) {
+    EXPECT_TRUE(GURL(valid_case).is_valid()) << "Case: " << valid_case;
   }
 
-  const char* invalid_cases[] = {
+  static constexpr auto invalid_cases = std::to_array<const char*>({
       "http://?k=v",
       "http:://google.com",
       "http//google.com",
@@ -236,24 +330,23 @@
       "file://server:0",
       "://google.com",
       "path",
-  };
-  for (size_t i = 0; i < std::size(invalid_cases); i++) {
-    EXPECT_FALSE(GURL(invalid_cases[i]).is_valid())
-        << "Case: " << invalid_cases[i];
+  });
+  for (const char* invalid_case : invalid_cases) {
+    EXPECT_FALSE(GURL(invalid_case).is_valid()) << "Case: " << invalid_case;
   }
 }
 
-TEST(GURLTest, ExtraSlashesBeforeAuthority) {
+TEST_F(GURLTest, ExtraSlashesBeforeAuthority) {
   // According to RFC3986, the hierarchical part for URI with an authority
   // must use only two slashes; GURL intentionally just ignores extra slashes
   // if there are more than 2, and parses the following part as an authority.
   GURL url("http:///host");
-  EXPECT_EQ("host", url.host());
-  EXPECT_EQ("/", url.path());
+  EXPECT_EQ("host", url.GetHost());
+  EXPECT_EQ("/", url.GetPath());
 }
 
 // Given invalid URLs, we should still get most of the components.
-TEST(GURLTest, ComponentGettersWorkEvenForInvalidURL) {
+TEST_F(GURLTest, ComponentGettersWorkEvenForInvalidURL) {
   constexpr struct InvalidURLTestExpectations {
     const char* url;
     const char* spec;
@@ -285,28 +378,23 @@
     const GURL url(e.url);
     EXPECT_FALSE(url.is_valid());
     EXPECT_EQ(e.spec, url.possibly_invalid_spec());
-    EXPECT_EQ(e.scheme, url.scheme());
-    EXPECT_EQ("", url.username());
-    EXPECT_EQ("", url.password());
-    EXPECT_EQ(e.host, url.host());
-    EXPECT_EQ(e.port, url.port());
+    EXPECT_EQ(e.scheme, url.GetScheme());
+    EXPECT_EQ("", url.GetUsername());
+    EXPECT_EQ("", url.GetPassword());
+    EXPECT_EQ(e.host, url.GetHost());
+    EXPECT_EQ(e.port, url.GetPort());
     EXPECT_EQ(PORT_INVALID, url.IntPort());
-    EXPECT_EQ(e.path, url.path());
-    EXPECT_EQ("", url.query());
-    EXPECT_EQ("", url.ref());
+    EXPECT_EQ(e.path, url.GetPath());
+    EXPECT_EQ("", url.GetQuery());
+    EXPECT_EQ("", url.GetRef());
   }
 }
 
-TEST(GURLTest, Resolve) {
+TEST_F(GURLTest, Resolve) {
   // The tricky cases for relative URL resolving are tested in the
   // canonicalizer unit test. Here, we just test that the GURL integration
   // works properly.
-  struct ResolveCase {
-    const char* base;
-    const char* relative;
-    bool expected_valid;
-    const char* expected;
-  } resolve_cases[] = {
+  static constexpr ResolveCase resolve_cases[] = {
       {"http://www.google.com/", "foo.html", true,
        "http://www.google.com/foo.html"},
       {"http://www.google.com/foo/", "bar", true,
@@ -323,10 +411,16 @@
        "http://www.google.com/foo#com"},
       {"http://www.google.com/", "Https:images.google.com", true,
        "https://images.google.com/"},
-      // A non-standard base can be replaced with a standard absolute URL.
+      // An opaque path URL can be replaced with a special absolute URL.
       {"data:blahblah", "http://google.com/", true, "http://google.com/"},
       {"data:blahblah", "http:google.com", true, "http://google.com/"},
       {"data:blahblah", "https:google.com", true, "https://google.com/"},
+      // An opaque path URL can not be replaced with a relative URL.
+      {"git:opaque", "", false, ""},
+      {"git:opaque", "path", false, ""},
+      // A non-special URL which doesn't have a host can be replaced with a
+      // relative URL.
+      {"git:/a", "b", true, "git:/b"},
       // Filesystem URLs have different paths to test.
       {"filesystem:http://www.google.com/type/", "foo.html", true,
        "filesystem:http://www.google.com/type/foo.html"},
@@ -344,31 +438,82 @@
       {"file:///some/dir/", "x-://host", true, "x-://host"},
       {"file:///some/dir/", "x!://host", true, "file:///some/dir/x!://host"},
       {"file:///some/dir/", "://host", true, "file:///some/dir/://host"},
+
+      // Non-special base URLs whose paths are empty.
+      {"git://host", "", true, "git://host"},
+      {"git://host", ".", true, "git://host/"},
+      {"git://host", "..", true, "git://host/"},
+      {"git://host", "a", true, "git://host/a"},
+      {"git://host", "/a", true, "git://host/a"},
+
+      // Non-special base URLs whose paths are "/".
+      {"git://host/", "", true, "git://host/"},
+      {"git://host/", ".", true, "git://host/"},
+      {"git://host/", "..", true, "git://host/"},
+      {"git://host/", "a", true, "git://host/a"},
+      {"git://host/", "/a", true, "git://host/a"},
+
+      // Non-special base URLs whose hosts and paths are non-empty.
+      {"git://host/b", "a", true, "git://host/a"},
+      {"git://host/b/c", "a", true, "git://host/b/a"},
+      {"git://host/b/c", "../a", true, "git://host/a"},
+
+      // An opaque path can be specified.
+      {"git://host", "git:opaque", true, "git:opaque"},
+      {"git://host/path#ref", "git:opaque", true, "git:opaque"},
+      {"git:/path", "git:opaque", true, "git:opaque"},
+      {"https://host/path", "git:opaque", true, "git:opaque"},
+
+      // Path-only base URLs should remain path-only URLs unless a host is
+      // specified.
+      {"git:/", "", true, "git:/"},
+      {"git:/", ".", true, "git:/"},
+      {"git:/", "..", true, "git:/"},
+      {"git:/", "a", true, "git:/a"},
+      {"git:/", "/a", true, "git:/a"},
+      {"git:/#ref", "", true, "git:/"},
+      {"git:/#ref", "a", true, "git:/a"},
+
+      // Non-special base URLs whose hosts and path are both empty. The
+      // result's host should remain empty unless a relative URL specify a
+      // host.
+      {"git://", "", true, "git://"},
+      {"git://", ".", true, "git:///"},
+      {"git://", "..", true, "git:///"},
+      {"git://", "a", true, "git:///a"},
+      {"git://", "/a", true, "git:///a"},
+
+      // Non-special base URLs whose hosts are empty, but with non-empty path.
+      {"git:///", "", true, "git:///"},
+      {"git:///", ".", true, "git:///"},
+      {"git:///", "..", true, "git:///"},
+      {"git:///", "a", true, "git:///a"},
+      {"git:///", "/a", true, "git:///a"},
+      {"git:///#ref", "", true, "git:///"},
+      {"git:///#ref", "a", true, "git:///a"},
+
+      // Relative URLs can specify empty hosts for non-special base URLs.
+      // e.g. "///path"
+      {"git://host/", "//", true, "git://"},
+      {"git://host/", "//a", true, "git://a"},
+      {"git://host/", "///", true, "git:///"},
+      {"git://host/", "////", true, "git:////"},
+      {"git://host/", "////..", true, "git:///"},
+      {"git://host/", "////../..", true, "git:///"},
+      {"git://host/", "////../../..", true, "git:///"},
   };
 
-  for (size_t i = 0; i < std::size(resolve_cases); i++) {
-    // 8-bit code path.
-    GURL input(resolve_cases[i].base);
-    GURL output = input.Resolve(resolve_cases[i].relative);
-    EXPECT_EQ(resolve_cases[i].expected_valid, output.is_valid()) << i;
-    EXPECT_EQ(resolve_cases[i].expected, output.spec()) << i;
-    EXPECT_EQ(output.SchemeIsFileSystem(), output.inner_url() != NULL);
-
-    // Wide code path.
-    GURL inputw(gurl_base::UTF8ToUTF16(resolve_cases[i].base));
-    GURL outputw =
-        input.Resolve(gurl_base::UTF8ToUTF16(resolve_cases[i].relative));
-    EXPECT_EQ(resolve_cases[i].expected_valid, outputw.is_valid()) << i;
-    EXPECT_EQ(resolve_cases[i].expected, outputw.spec()) << i;
-    EXPECT_EQ(outputw.SchemeIsFileSystem(), outputw.inner_url() != NULL);
+  for (const ResolveCase& c : resolve_cases) {
+    TestResolve(c);
   }
 }
 
-TEST(GURLTest, GetOrigin) {
+TEST_F(GURLTest, GetOrigin) {
   struct TestCase {
     const char* input;
     const char* expected;
-  } cases[] = {
+  };
+  static constexpr auto cases = std::to_array<TestCase>({
       {"http://www.google.com", "http://www.google.com/"},
       {"javascript:window.alert(\"hello,world\");", ""},
       {"http://user:pass@www.google.com:21/blah#baz",
@@ -382,107 +527,124 @@
        "http://google.com:21/"},
       {"blob:null/guid-goes-here", ""},
       {"blob:http://origin/guid-goes-here", "" /* should be http://origin/ */},
-  };
-  for (size_t i = 0; i < std::size(cases); i++) {
-    GURL url(cases[i].input);
+  });
+
+  for (const TestCase& c : cases) {
+    GURL url(c.input);
     GURL origin = url.DeprecatedGetOriginAsURL();
-    EXPECT_EQ(cases[i].expected, origin.spec());
+    EXPECT_EQ(c.expected, origin.spec());
   }
 }
 
-TEST(GURLTest, GetAsReferrer) {
+TEST_F(GURLTest, GetAsReferrer) {
   struct TestCase {
     const char* input;
     const char* expected;
-  } cases[] = {
-    {"http://www.google.com", "http://www.google.com/"},
-    {"http://user:pass@www.google.com:21/blah#baz", "http://www.google.com:21/blah"},
-    {"http://user@www.google.com", "http://www.google.com/"},
-    {"http://:pass@www.google.com", "http://www.google.com/"},
-    {"http://:@www.google.com", "http://www.google.com/"},
-    {"http://www.google.com/temp/foo?q#b", "http://www.google.com/temp/foo?q"},
-    {"not a url", ""},
-    {"unknown-scheme://foo.html", ""},
-    {"file:///tmp/test.html", ""},
-    {"https://www.google.com", "https://www.google.com/"},
   };
-  for (size_t i = 0; i < std::size(cases); i++) {
-    GURL url(cases[i].input);
+  static constexpr auto cases = std::to_array<TestCase>({
+      {"http://www.google.com", "http://www.google.com/"},
+      {"http://user:pass@www.google.com:21/blah#baz",
+       "http://www.google.com:21/blah"},
+      {"http://user@www.google.com", "http://www.google.com/"},
+      {"http://:pass@www.google.com", "http://www.google.com/"},
+      {"http://:@www.google.com", "http://www.google.com/"},
+      {"http://www.google.com/temp/foo?q#b",
+       "http://www.google.com/temp/foo?q"},
+      {"not a url", ""},
+      {"unknown-scheme://foo.html", ""},
+      {"file:///tmp/test.html", ""},
+      {"https://www.google.com", "https://www.google.com/"},
+  });
+  for (const TestCase& c : cases) {
+    GURL url(c.input);
     GURL origin = url.GetAsReferrer();
-    EXPECT_EQ(cases[i].expected, origin.spec());
+    EXPECT_EQ(c.expected, origin.spec());
   }
 }
 
-TEST(GURLTest, GetWithEmptyPath) {
+TEST_F(GURLTest, GetWithEmptyPath) {
   struct TestCase {
     const char* input;
     const char* expected;
-  } cases[] = {
-    {"http://www.google.com", "http://www.google.com/"},
-    {"javascript:window.alert(\"hello, world\");", ""},
-    {"http://www.google.com/foo/bar.html?baz=22", "http://www.google.com/"},
-    {"filesystem:http://www.google.com/temporary/bar.html?baz=22", "filesystem:http://www.google.com/temporary/"},
-    {"filesystem:file:///temporary/bar.html?baz=22", "filesystem:file:///temporary/"},
   };
-
-  for (size_t i = 0; i < std::size(cases); i++) {
-    GURL url(cases[i].input);
+  static constexpr auto cases = std::to_array<TestCase>({
+      {"http://www.google.com", "http://www.google.com/"},
+      {"javascript:window.alert(\"hello, world\");", ""},
+      {"http://www.google.com/foo/bar.html?baz=22", "http://www.google.com/"},
+      {"filesystem:http://www.google.com/temporary/bar.html?baz=22",
+       "filesystem:http://www.google.com/temporary/"},
+      {"filesystem:file:///temporary/bar.html?baz=22",
+       "filesystem:file:///temporary/"},
+  });
+  for (const auto& c : cases) {
+    GURL url(c.input);
     GURL empty_path = url.GetWithEmptyPath();
-    EXPECT_EQ(cases[i].expected, empty_path.spec());
+    EXPECT_EQ(c.expected, empty_path.spec());
   }
 }
 
-TEST(GURLTest, GetWithoutFilename) {
+TEST_F(GURLTest, GetWithoutFilename) {
   struct TestCase {
     const char* input;
     const char* expected;
-  } cases[] = {
-    // Common Standard URLs.
-    {"https://www.google.com",                    "https://www.google.com/"},
-    {"https://www.google.com/",                   "https://www.google.com/"},
-    {"https://www.google.com/maps.htm",           "https://www.google.com/"},
-    {"https://www.google.com/maps/",              "https://www.google.com/maps/"},
-    {"https://www.google.com/index.html",         "https://www.google.com/"},
-    {"https://www.google.com/index.html?q=maps",  "https://www.google.com/"},
-    {"https://www.google.com/index.html#maps/",   "https://www.google.com/"},
-    {"https://foo:bar@www.google.com/maps.htm",   "https://foo:bar@www.google.com/"},
-    {"https://www.google.com/maps/au/index.html", "https://www.google.com/maps/au/"},
-    {"https://www.google.com/maps/au/north",      "https://www.google.com/maps/au/"},
-    {"https://www.google.com/maps/au/north/",     "https://www.google.com/maps/au/north/"},
-    {"https://www.google.com/maps/au/index.html?q=maps#fragment/",     "https://www.google.com/maps/au/"},
-    {"http://www.google.com:8000/maps/au/index.html?q=maps#fragment/", "http://www.google.com:8000/maps/au/"},
-    {"https://www.google.com/maps/au/north/?q=maps#fragment",          "https://www.google.com/maps/au/north/"},
-    {"https://www.google.com/maps/au/north?q=maps#fragment",           "https://www.google.com/maps/au/"},
-    // Less common standard URLs.
-    {"filesystem:http://www.google.com/temporary/bar.html?baz=22", "filesystem:http://www.google.com/temporary/"},
-    {"file:///temporary/bar.html?baz=22","file:///temporary/"},
-    {"ftp://foo/test/index.html",        "ftp://foo/test/"},
-    {"gopher://foo/test/index.html",     "gopher://foo/test/"},
-    {"ws://foo/test/index.html",         "ws://foo/test/"},
-    // Non-standard, hierarchical URLs.
-    {"chrome://foo/bar.html", "chrome://foo/"},
-    {"httpa://foo/test/index.html", "httpa://foo/test/"},
-    // Non-standard, non-hierarchical URLs.
-    {"blob:https://foo.bar/test/index.html", ""},
-    {"about:blank", ""},
-    {"data:foobar", ""},
-    {"scheme:opaque_data", ""},
-    // Invalid URLs.
-    {"foobar", ""},
   };
+  static constexpr auto cases = std::to_array<TestCase>({
+      // Common Standard URLs.
+      {"https://www.google.com", "https://www.google.com/"},
+      {"https://www.google.com/", "https://www.google.com/"},
+      {"https://www.google.com/maps.htm", "https://www.google.com/"},
+      {"https://www.google.com/maps/", "https://www.google.com/maps/"},
+      {"https://www.google.com/index.html", "https://www.google.com/"},
+      {"https://www.google.com/index.html?q=maps", "https://www.google.com/"},
+      {"https://www.google.com/index.html#maps/", "https://www.google.com/"},
+      {"https://foo:bar@www.google.com/maps.htm",
+       "https://foo:bar@www.google.com/"},
+      {"https://www.google.com/maps/au/index.html",
+       "https://www.google.com/maps/au/"},
+      {"https://www.google.com/maps/au/north",
+       "https://www.google.com/maps/au/"},
+      {"https://www.google.com/maps/au/north/",
+       "https://www.google.com/maps/au/north/"},
+      {"https://www.google.com/maps/au/index.html?q=maps#fragment/",
+       "https://www.google.com/maps/au/"},
+      {"http://www.google.com:8000/maps/au/index.html?q=maps#fragment/",
+       "http://www.google.com:8000/maps/au/"},
+      {"https://www.google.com/maps/au/north/?q=maps#fragment",
+       "https://www.google.com/maps/au/north/"},
+      {"https://www.google.com/maps/au/north?q=maps#fragment",
+       "https://www.google.com/maps/au/"},
+      // Less common standard URLs.
+      {"filesystem:http://www.google.com/temporary/bar.html?baz=22",
+       "filesystem:http://www.google.com/temporary/"},
+      {"file:///temporary/bar.html?baz=22", "file:///temporary/"},
+      {"ftp://foo/test/index.html", "ftp://foo/test/"},
+      {"gopher://foo/test/index.html", "gopher://foo/test/"},
+      {"ws://foo/test/index.html", "ws://foo/test/"},
+      // Non-standard, hierarchical URLs.
+      {"chrome://foo/bar.html", "chrome://foo/"},
+      {"httpa://foo/test/index.html", "httpa://foo/test/"},
+      // Non-standard, non-hierarchical URLs.
+      {"blob:https://foo.bar/test/index.html", ""},
+      {"about:blank", ""},
+      {"data:foobar", ""},
+      {"scheme:opaque_data", ""},
+      // Invalid URLs.
+      {"foobar", ""},
+  });
 
-  for (size_t i = 0; i < std::size(cases); i++) {
-    GURL url(cases[i].input);
+  for (const auto& c : cases) {
+    GURL url(c.input);
     GURL without_filename = url.GetWithoutFilename();
-    EXPECT_EQ(cases[i].expected, without_filename.spec()) << i;
+    EXPECT_EQ(c.expected, without_filename.spec());
   }
 }
 
-TEST(GURLTest, GetWithoutRef) {
+TEST_F(GURLTest, GetWithoutRef) {
   struct TestCase {
     const char* input;
     const char* expected;
-  } cases[] = {
+  };
+  static constexpr auto cases = std::to_array<TestCase>({
       // Common Standard URLs.
       {"https://www.google.com/index.html",
        "https://www.google.com/index.html"},
@@ -544,26 +706,20 @@
       {"scheme:opaque_data", "scheme:opaque_data"},
       // Invalid URLs.
       {"foobar", ""},
-  };
+  });
 
-  for (size_t i = 0; i < std::size(cases); i++) {
-    GURL url(cases[i].input);
+  for (const auto& i : cases) {
+    GURL url(i.input);
     GURL without_ref = url.GetWithoutRef();
-    EXPECT_EQ(cases[i].expected, without_ref.spec());
+    EXPECT_EQ(i.expected, without_ref.spec());
   }
 }
 
-TEST(GURLTest, Replacements) {
+TEST_F(GURLTest, Replacements) {
   // The URL canonicalizer replacement test will handle most of these case.
   // The most important thing to do here is to check that the proper
   // canonicalizer gets called based on the scheme of the input.
-  struct ReplaceCase {
-    using ApplyReplacementsFunc = GURL(const GURL&);
-
-    const char* base;
-    ApplyReplacementsFunc* apply_replacements;
-    const char* expected;
-  } replace_cases[] = {
+  static constexpr ReplaceCase replace_cases[] = {
       {.base = "http://www.google.com/foo/bar.html?foo#bar",
        .apply_replacements =
            +[](const GURL& url) {
@@ -574,21 +730,6 @@
              return url.ReplaceComponents(replacements);
            },
        .expected = "http://www.google.com/"},
-      {.base = "http://www.google.com/foo/bar.html?foo#bar",
-       .apply_replacements =
-           +[](const GURL& url) {
-             GURL::Replacements replacements;
-             replacements.SetSchemeStr("javascript");
-             replacements.ClearUsername();
-             replacements.ClearPassword();
-             replacements.ClearHost();
-             replacements.ClearPort();
-             replacements.SetPathStr("window.open('foo');");
-             replacements.ClearQuery();
-             replacements.ClearRef();
-             return url.ReplaceComponents(replacements);
-           },
-       .expected = "javascript:window.open('foo');"},
       {.base = "file:///C:/foo/bar.txt",
        .apply_replacements =
            +[](const GURL& url) {
@@ -641,24 +782,100 @@
              return url.ReplaceComponents(replacements);
            },
        .expected = "filesystem:http://www.google.com/foo/bar.html?foo#bar"},
-  };
+      // Regression test for https://crbug.com/375660989.
+      //
+      // "steam:" is explicitly registered as an opaque non-special scheme for
+      // compatibility reasons. See SchemeRegistry::opaque_non_special_schemes.
+      {.base = "steam:a",
+       .apply_replacements =
+           +[](const GURL& url) {
+             GURL::Replacements replacements;
+             replacements.SetPathStr("b");
+             return url.ReplaceComponents(replacements);
+           },
+       .expected = "steam:b"},
+
+      // Test cases that Chromium used to parse incorrectly.
+      {.base = "git://a1/a2?a3=a4#a5",
+       .apply_replacements =
+           +[](const GURL& url) {
+             GURL::Replacements replacements;
+             replacements.SetHostStr("b1");
+             replacements.SetPortStr("99");
+             replacements.SetPathStr("b2");
+             replacements.SetQueryStr("b3=b4");
+             replacements.SetRefStr("b5");
+             return url.ReplaceComponents(replacements);
+           },
+       .expected = "git://b1:99/b2?b3=b4#b5"},
+      // URL Standard: https://url.spec.whatwg.org/#dom-url-username
+      // > 1. If this’s URL cannot have a username/password/port, then return.
+      {.base = "git:///",
+       .apply_replacements =
+           +[](const GURL& url) {
+             GURL::Replacements replacements;
+             replacements.SetUsernameStr("x");
+             return url.ReplaceComponents(replacements);
+           },
+       .expected = "git:///"},
+      // URL Standard: https://url.spec.whatwg.org/#dom-url-password
+      // > 1. If this’s URL cannot have a username/password/port, then return.
+      {.base = "git:///",
+       .apply_replacements =
+           +[](const GURL& url) {
+             GURL::Replacements replacements;
+             replacements.SetPasswordStr("x");
+             return url.ReplaceComponents(replacements);
+           },
+       .expected = "git:///"},
+      // URL Standard: https://url.spec.whatwg.org/#dom-url-port
+      // > 1. If this’s URL cannot have a username/password/port, then return.
+      {.base = "git:///",
+       .apply_replacements =
+           +[](const GURL& url) {
+             GURL::Replacements replacements;
+             replacements.SetPortStr("80");
+             return url.ReplaceComponents(replacements);
+           },
+       .expected = "git:///"}};
 
   for (const ReplaceCase& c : replace_cases) {
-    GURL output = c.apply_replacements(GURL(c.base));
+    TestReplace(c);
+  }
 
-    EXPECT_EQ(c.expected, output.spec());
+  static constexpr ReplaceHostCase replace_host_cases[] = {
+      {"git:/", "host", "git://host/"},
+      {"git:/a", "host", "git://host/a"},
+      {"git://", "host", "git://host"},
+      {"git:///", "host", "git://host/"},
+      {"git://h/a", "host", "git://host/a"},
+      // The following behavior is different from Web-facing URL APIs
+      // because DOMURLUtils::setHostname disallows setting an empty host.
+      //
+      // Web-facing URL API behavior is:
+      // > const url = new URL("git://u:p@h:80/");
+      // > url.hostname = "";
+      // > assertEquals(url.href, "git://u:p@h:80/");
+      {"git://u:p@h:80/", "", "git:///"}};
+  for (const ReplaceHostCase& c : replace_host_cases) {
+    TestReplaceHost(c);
+  }
 
-    EXPECT_EQ(output.SchemeIsFileSystem(), output.inner_url() != NULL);
-    if (output.SchemeIsFileSystem()) {
-      // TODO(mmenke): inner_url()->spec() is currently the same as the spec()
-      // for the GURL itself.  This should be fixed.
-      // See https://crbug.com/619596
-      EXPECT_EQ(c.expected, output.inner_url()->spec());
-    }
+  static constexpr ReplacePathCase replace_path_cases[] = {
+      {"git:/", "a", "git:/a"},
+      {"git:/", "", "git:/"},
+      {"git:/", "//a", "git:/.//a"},
+      {"git:/", "/.//a", "git:/.//a"},
+      {"git://", "a", "git:///a"},
+      {"git:///", "a", "git:///a"},
+      {"git://host", "a", "git://host/a"},
+      {"git://host/b", "a", "git://host/a"}};
+  for (const ReplacePathCase& c : replace_path_cases) {
+    TestReplacePath(c);
   }
 }
 
-TEST(GURLTest, ClearFragmentOnDataUrl) {
+TEST(GURLTypedTest, ClearFragmentOnDataUrl) {
   // http://crbug.com/291747 - a data URL may legitimately have trailing
   // whitespace in the spec after the ref is cleared. Test this does not trigger
   // the Parsed importing validation GURL_DCHECK in GURL.
@@ -683,7 +900,7 @@
   EXPECT_TRUE(import_url.is_valid());
   EXPECT_EQ(url_no_ref, import_url);
   EXPECT_EQ("data: one ", import_url.spec());
-  EXPECT_EQ(" one ", import_url.path());
+  EXPECT_EQ(" one ", import_url.GetPath());
 
   // For completeness, test that re-parsing the same URL rather than importing
   // it trims the trailing whitespace.
@@ -692,12 +909,13 @@
   EXPECT_EQ("data: one", reparsed_url.spec());
 }
 
-TEST(GURLTest, PathForRequest) {
+TEST_F(GURLTest, PathForRequest) {
   struct TestCase {
     const char* input;
     const char* expected;
     const char* inner_expected;
-  } cases[] = {
+  };
+  static constexpr auto cases = std::to_array<TestCase>({
       {"http://www.google.com", "/", nullptr},
       {"http://www.google.com/", "/", nullptr},
       {"http://www.google.com/foo/bar.html?baz=22", "/foo/bar.html?baz=22",
@@ -709,109 +927,111 @@
        "/foo/bar.html?query", "/temporary"},
       {"filesystem:http://www.google.com/temporary/foo/bar.html?query",
        "/foo/bar.html?query", "/temporary"},
-  };
+  });
 
-  for (size_t i = 0; i < std::size(cases); i++) {
-    GURL url(cases[i].input);
-    EXPECT_EQ(cases[i].expected, url.PathForRequest());
-    EXPECT_EQ(cases[i].expected, url.PathForRequestPiece());
-    EXPECT_EQ(cases[i].inner_expected == NULL, url.inner_url() == NULL);
-    if (url.inner_url() && cases[i].inner_expected) {
-      EXPECT_EQ(cases[i].inner_expected, url.inner_url()->PathForRequest());
-      EXPECT_EQ(cases[i].inner_expected,
-                url.inner_url()->PathForRequestPiece());
+  for (const auto& i : cases) {
+    GURL url(i.input);
+    EXPECT_EQ(i.expected, url.PathForRequest());
+    EXPECT_EQ(i.expected, url.PathForRequestPiece());
+    EXPECT_EQ(i.inner_expected == nullptr, url.inner_url() == nullptr);
+    if (url.inner_url() && i.inner_expected) {
+      EXPECT_EQ(i.inner_expected, url.inner_url()->PathForRequest());
+      EXPECT_EQ(i.inner_expected, url.inner_url()->PathForRequestPiece());
     }
   }
 }
 
-TEST(GURLTest, EffectiveIntPort) {
+TEST_F(GURLTest, EffectiveIntPort) {
   struct PortTest {
     const char* spec;
     int expected_int_port;
-  } port_tests[] = {
-    // http
-    {"http://www.google.com/", 80},
-    {"http://www.google.com:80/", 80},
-    {"http://www.google.com:443/", 443},
-
-    // https
-    {"https://www.google.com/", 443},
-    {"https://www.google.com:443/", 443},
-    {"https://www.google.com:80/", 80},
-
-    // ftp
-    {"ftp://www.google.com/", 21},
-    {"ftp://www.google.com:21/", 21},
-    {"ftp://www.google.com:80/", 80},
-
-    // file - no port
-    {"file://www.google.com/", PORT_UNSPECIFIED},
-    {"file://www.google.com:443/", PORT_UNSPECIFIED},
-
-    // data - no port
-    {"data:www.google.com:90", PORT_UNSPECIFIED},
-    {"data:www.google.com", PORT_UNSPECIFIED},
-
-    // filesystem - no port
-    {"filesystem:http://www.google.com:90/t/foo", PORT_UNSPECIFIED},
-    {"filesystem:file:///t/foo", PORT_UNSPECIFIED},
   };
+  static constexpr auto port_tests = std::to_array<PortTest>({
+      // http
+      {"http://www.google.com/", 80},
+      {"http://www.google.com:80/", 80},
+      {"http://www.google.com:443/", 443},
 
-  for (size_t i = 0; i < std::size(port_tests); i++) {
-    GURL url(port_tests[i].spec);
-    EXPECT_EQ(port_tests[i].expected_int_port, url.EffectiveIntPort());
+      // https
+      {"https://www.google.com/", 443},
+      {"https://www.google.com:443/", 443},
+      {"https://www.google.com:80/", 80},
+
+      // ftp
+      {"ftp://www.google.com/", 21},
+      {"ftp://www.google.com:21/", 21},
+      {"ftp://www.google.com:80/", 80},
+
+      // file - no port
+      {"file://www.google.com/", PORT_UNSPECIFIED},
+      {"file://www.google.com:443/", PORT_UNSPECIFIED},
+
+      // data - no port
+      {"data:www.google.com:90", PORT_UNSPECIFIED},
+      {"data:www.google.com", PORT_UNSPECIFIED},
+
+      // filesystem - no port
+      {"filesystem:http://www.google.com:90/t/foo", PORT_UNSPECIFIED},
+      {"filesystem:file:///t/foo", PORT_UNSPECIFIED},
+  });
+
+  for (const auto& port_test : port_tests) {
+    GURL url(port_test.spec);
+    EXPECT_EQ(port_test.expected_int_port, url.EffectiveIntPort());
   }
 }
 
-TEST(GURLTest, IPAddress) {
+TEST_F(GURLTest, IPAddress) {
   struct IPTest {
     const char* spec;
     bool expected_ip;
-  } ip_tests[] = {
-    {"http://www.google.com/", false},
-    {"http://192.168.9.1/", true},
-    {"http://192.168.9.1.2/", false},
-    {"http://192.168.m.1/", false},
-    {"http://2001:db8::1/", false},
-    {"http://[2001:db8::1]/", true},
-    {"", false},
-    {"some random input!", false},
   };
+  static constexpr auto ip_tests = std::to_array<IPTest>({
+      {"http://www.google.com/", false},
+      {"http://192.168.9.1/", true},
+      {"http://192.168.9.1.2/", false},
+      {"http://192.168.m.1/", false},
+      {"http://2001:db8::1/", false},
+      {"http://[2001:db8::1]/", true},
+      {"", false},
+      {"some random input!", false},
+  });
 
-  for (size_t i = 0; i < std::size(ip_tests); i++) {
-    GURL url(ip_tests[i].spec);
-    EXPECT_EQ(ip_tests[i].expected_ip, url.HostIsIPAddress());
+  for (const auto& ip_test : ip_tests) {
+    GURL url(ip_test.spec);
+    EXPECT_EQ(ip_test.expected_ip, url.HostIsIPAddress());
   }
 }
 
-TEST(GURLTest, HostNoBrackets) {
+TEST_F(GURLTest, HostNoBrackets) {
   struct TestCase {
     const char* input;
     const char* expected_host;
     const char* expected_plainhost;
-  } cases[] = {
-    {"http://www.google.com", "www.google.com", "www.google.com"},
-    {"http://[2001:db8::1]/", "[2001:db8::1]", "2001:db8::1"},
-    {"http://[::]/", "[::]", "::"},
-
-    // Don't require a valid URL, but don't crash either.
-    {"http://[]/", "[]", ""},
-    {"http://[x]/", "[x]", "x"},
-    {"http://[x/", "[x", "[x"},
-    {"http://x]/", "x]", "x]"},
-    {"http://[/", "[", "["},
-    {"http://]/", "]", "]"},
-    {"", "", ""},
   };
-  for (size_t i = 0; i < std::size(cases); i++) {
-    GURL url(cases[i].input);
-    EXPECT_EQ(cases[i].expected_host, url.host());
-    EXPECT_EQ(cases[i].expected_plainhost, url.HostNoBrackets());
-    EXPECT_EQ(cases[i].expected_plainhost, url.HostNoBracketsPiece());
+  static constexpr auto cases = std::to_array<TestCase>({
+      {"http://www.google.com", "www.google.com", "www.google.com"},
+      {"http://[2001:db8::1]/", "[2001:db8::1]", "2001:db8::1"},
+      {"http://[::]/", "[::]", "::"},
+
+      // Don't require a valid URL, but don't crash either.
+      {"http://[]/", "[]", ""},
+      {"http://[x]/", "[x]", "x"},
+      {"http://[x/", "[x", "[x"},
+      {"http://x]/", "x]", "x]"},
+      {"http://[/", "[", "["},
+      {"http://]/", "]", "]"},
+      {"", "", ""},
+  });
+  for (const auto& i : cases) {
+    GURL url(i.input);
+    EXPECT_EQ(i.expected_host, url.GetHost());
+    EXPECT_EQ(i.expected_plainhost, url.HostNoBrackets());
+    EXPECT_EQ(i.expected_plainhost, url.HostNoBracketsPiece());
   }
 }
 
-TEST(GURLTest, DomainIs) {
+TEST_F(GURLTest, DomainIs) {
   GURL url_1("http://google.com/foo");
   EXPECT_TRUE(url_1.DomainIs("google.com"));
 
@@ -838,11 +1058,11 @@
 
   GURL url_with_escape_chars("https://www.,.test");
   EXPECT_TRUE(url_with_escape_chars.is_valid());
-  EXPECT_EQ(url_with_escape_chars.host(), "www.,.test");
+  EXPECT_EQ(url_with_escape_chars.GetHost(), "www.,.test");
   EXPECT_TRUE(url_with_escape_chars.DomainIs(",.test"));
 }
 
-TEST(GURLTest, DomainIsTerminatingDotBehavior) {
+TEST_F(GURLTest, DomainIsTerminatingDotBehavior) {
   // If the host part ends with a dot, it matches input domains
   // with or without a dot.
   GURL url_with_dot("http://www.google.com./foo");
@@ -861,7 +1081,7 @@
   EXPECT_FALSE(url_with_two_dots.DomainIs("google.com"));
 }
 
-TEST(GURLTest, DomainIsWithFilesystemScheme) {
+TEST_F(GURLTest, DomainIsWithFilesystemScheme) {
   GURL url_1("filesystem:http://www.google.com:99/foo/");
   EXPECT_TRUE(url_1.DomainIs("google.com"));
 
@@ -870,7 +1090,7 @@
 }
 
 // Newlines should be stripped from inputs.
-TEST(GURLTest, Newlines) {
+TEST_F(GURLTest, Newlines) {
   // Constructor.
   GURL url_1(" \t ht\ntp://\twww.goo\rgle.com/as\ndf \n ");
   EXPECT_EQ("http://www.google.com/asdf", url_1.spec());
@@ -898,7 +1118,7 @@
   // Note that newlines are NOT stripped from ReplaceComponents.
 }
 
-TEST(GURLTest, IsStandard) {
+TEST_F(GURLTest, IsStandard) {
   GURL a("http:foo/bar");
   EXPECT_TRUE(a.IsStandard());
 
@@ -912,19 +1132,19 @@
   EXPECT_FALSE(d.IsStandard());
 }
 
-TEST(GURLTest, SchemeIsHTTPOrHTTPS) {
+TEST_F(GURLTest, SchemeIsHTTPOrHTTPS) {
   EXPECT_TRUE(GURL("http://bar/").SchemeIsHTTPOrHTTPS());
   EXPECT_TRUE(GURL("HTTPS://BAR").SchemeIsHTTPOrHTTPS());
   EXPECT_FALSE(GURL("ftp://bar/").SchemeIsHTTPOrHTTPS());
 }
 
-TEST(GURLTest, SchemeIsWSOrWSS) {
+TEST_F(GURLTest, SchemeIsWSOrWSS) {
   EXPECT_TRUE(GURL("WS://BAR/").SchemeIsWSOrWSS());
   EXPECT_TRUE(GURL("wss://bar/").SchemeIsWSOrWSS());
   EXPECT_FALSE(GURL("http://bar/").SchemeIsWSOrWSS());
 }
 
-TEST(GURLTest, SchemeIsCryptographic) {
+TEST_F(GURLTest, SchemeIsCryptographic) {
   EXPECT_TRUE(GURL("https://foo.bar.com/").SchemeIsCryptographic());
   EXPECT_TRUE(GURL("HTTPS://foo.bar.com/").SchemeIsCryptographic());
   EXPECT_TRUE(GURL("HtTpS://foo.bar.com/").SchemeIsCryptographic());
@@ -937,7 +1157,7 @@
   EXPECT_FALSE(GURL("ws://foo.bar.com/").SchemeIsCryptographic());
 }
 
-TEST(GURLTest, SchemeIsCryptographicStatic) {
+TEST_F(GURLTest, SchemeIsCryptographicStatic) {
   EXPECT_TRUE(GURL::SchemeIsCryptographic("https"));
   EXPECT_TRUE(GURL::SchemeIsCryptographic("wss"));
   EXPECT_FALSE(GURL::SchemeIsCryptographic("http"));
@@ -945,13 +1165,13 @@
   EXPECT_FALSE(GURL::SchemeIsCryptographic("ftp"));
 }
 
-TEST(GURLTest, SchemeIsBlob) {
+TEST_F(GURLTest, SchemeIsBlob) {
   EXPECT_TRUE(GURL("BLOB://BAR/").SchemeIsBlob());
   EXPECT_TRUE(GURL("blob://bar/").SchemeIsBlob());
   EXPECT_FALSE(GURL("http://bar/").SchemeIsBlob());
 }
 
-TEST(GURLTest, SchemeIsLocal) {
+TEST_F(GURLTest, SchemeIsLocal) {
   EXPECT_TRUE(GURL("BLOB://BAR/").SchemeIsLocal());
   EXPECT_TRUE(GURL("blob://bar/").SchemeIsLocal());
   EXPECT_TRUE(GURL("DATA:TEXT/HTML,BAR").SchemeIsLocal());
@@ -968,8 +1188,8 @@
 // Tests that the 'content' of the URL is properly extracted. This can be
 // complex in cases such as multiple schemes (view-source:http:) or for
 // javascript URLs. See GURL::GetContent for more details.
-TEST(GURLTest, ContentForNonStandardURLs) {
-  struct TestCase {
+TEST_F(GURLTest, ContentForNonStandardURLs) {
+  static constexpr struct TestCase {
     const char* url;
     const char* expected;
   } cases[] = {
@@ -982,7 +1202,6 @@
       // content not the scheme.
       {"view-source:http://example.com/path", "http://example.com/path"},
       {"blob:http://example.com/GUID", "http://example.com/GUID"},
-      {"blob://http://example.com/GUID", "//http://example.com/GUID"},
       {"blob:http://user:password@example.com/GUID",
        "http://user:password@example.com/GUID"},
 
@@ -999,6 +1218,10 @@
       // Javascript URLs include '#' symbols in their content.
       {"javascript:#", "#"},
       {"javascript:alert('#');", "alert('#');"},
+
+      // Test cases which Chromium used to handle wrongly.
+      {"blob://http://example.com/GUID", "http//example.com/GUID"},
+      {"git://host/path#fragment", "host/path"},
   };
 
   for (const auto& test : cases) {
@@ -1011,8 +1234,8 @@
 // Tests that the URL path is properly extracted for unusual URLs. This can be
 // complex in cases such as multiple schemes (view-source:http:) or when
 // octothorpes ('#') are involved.
-TEST(GURLTest, PathForNonStandardURLs) {
-  struct TestCase {
+TEST_F(GURLTest, PathForNonStandardURLs) {
+  static constexpr struct TestCase {
     const char* url;
     const char* expected;
   } cases[] = {
@@ -1021,7 +1244,6 @@
        "this is arbitrary content"},
       {"view-source:http://example.com/path", "http://example.com/path"},
       {"blob:http://example.com/GUID", "http://example.com/GUID"},
-      {"blob://http://example.com/GUID", "//http://example.com/GUID"},
       {"blob:http://user:password@example.com/GUID",
        "http://user:password@example.com/GUID"},
 
@@ -1030,18 +1252,22 @@
       {"data:text/html,Question?<div style=\"color: #bad\">idea</div>",
        "text/html,Question"},
 
+      // Test cases which Chromium used to handle wrongly.
+      {"blob://http://example.com/GUID", "//example.com/GUID"},
+      {"git://host/path#fragment", "/path"},
+
       // TODO(mkwst): This seems like a bug. https://crbug.com/513600
       {"filesystem:http://example.com/path", "/"},
   };
 
   for (const auto& test : cases) {
     GURL url(test.url);
-    EXPECT_EQ(test.expected, url.path()) << test.url;
+    EXPECT_EQ(test.expected, url.GetPath()) << test.url;
   }
 }
 
-TEST(GURLTest, EqualsIgnoringRef) {
-  const struct {
+TEST_F(GURLTest, EqualsIgnoringRef) {
+  static constexpr struct {
     const char* url_a;
     const char* url_b;
     bool are_equals;
@@ -1103,13 +1329,13 @@
   }
 }
 
-TEST(GURLTest, DebugAlias) {
+TEST_F(GURLTest, DebugAlias) {
   GURL url("https://foo.com/bar");
   DEBUG_ALIAS_FOR_GURL(url_debug_alias, url);
   EXPECT_STREQ("https://foo.com/bar", url_debug_alias);
 }
 
-TEST(GURLTest, InvalidHost) {
+TEST_F(GURLTest, InvalidHost) {
   // This contains an invalid percent escape (%T%) and also a valid
   // percent escape that's not 7-bit ascii (%ae), so that the unescaped
   // host contains both an invalid percent escape and invalid UTF-8.
@@ -1121,27 +1347,27 @@
   // The invalid percent escape becomes an escaped percent sign (%25), and the
   // invalid UTF-8 character becomes REPLACEMENT CHARACTER' (U+FFFD) encoded as
   // UTF-8.
-  EXPECT_EQ(url.host_piece(), "%25t%EF%BF%BD");
+  EXPECT_EQ(url.host(), "%25t%EF%BF%BD");
 }
 
-TEST(GURLTest, PortZero) {
+TEST_F(GURLTest, PortZero) {
   GURL port_zero_url("http://127.0.0.1:0/blah");
 
   // https://url.spec.whatwg.org/#port-state says that the port 1) consists of
   // ASCII digits (this excludes negative numbers) and 2) cannot be greater than
   // 2^16-1.  This means that port=0 should be valid.
   EXPECT_TRUE(port_zero_url.is_valid());
-  EXPECT_EQ("0", port_zero_url.port());
-  EXPECT_EQ("127.0.0.1", port_zero_url.host());
-  EXPECT_EQ("http", port_zero_url.scheme());
+  EXPECT_EQ("0", port_zero_url.GetPort());
+  EXPECT_EQ("127.0.0.1", port_zero_url.GetHost());
+  EXPECT_EQ("http", port_zero_url.GetScheme());
 
   // https://crbug.com/1065532: SchemeHostPort would previously incorrectly
   // consider port=0 to be invalid.
   SchemeHostPort scheme_host_port(port_zero_url);
   EXPECT_TRUE(scheme_host_port.IsValid());
-  EXPECT_EQ(port_zero_url.scheme(), scheme_host_port.scheme());
-  EXPECT_EQ(port_zero_url.host(), scheme_host_port.host());
-  EXPECT_EQ(port_zero_url.port(),
+  EXPECT_EQ(port_zero_url.GetScheme(), scheme_host_port.scheme());
+  EXPECT_EQ(port_zero_url.GetHost(), scheme_host_port.host());
+  EXPECT_EQ(port_zero_url.GetPort(),
             gurl_base::NumberToString(scheme_host_port.port()));
 
   // https://crbug.com/1065532: The SchemeHostPort problem above would lead to
@@ -1151,9 +1377,10 @@
   url::Origin resolved_origin =
       url::Origin::Resolve(port_zero_url, another_origin);
   EXPECT_FALSE(resolved_origin.opaque());
-  EXPECT_EQ(port_zero_url.scheme(), resolved_origin.scheme());
-  EXPECT_EQ(port_zero_url.host(), resolved_origin.host());
-  EXPECT_EQ(port_zero_url.port(), gurl_base::NumberToString(resolved_origin.port()));
+  EXPECT_EQ(port_zero_url.GetScheme(), resolved_origin.scheme());
+  EXPECT_EQ(port_zero_url.GetHost(), resolved_origin.host());
+  EXPECT_EQ(port_zero_url.GetPort(),
+            gurl_base::NumberToString(resolved_origin.port()));
 
   // port=0 and default HTTP port are different.
   GURL default_port("http://127.0.0.1/foo");
diff --git a/url/origin.cc b/url/origin.cc
index 94d5197..b3820a6 100644
--- a/url/origin.cc
+++ b/url/origin.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 
 #include <algorithm>
+#include <compare>
 #include <ostream>
 #include <string>
 #include <string_view>
@@ -16,17 +17,19 @@
 #include "base/base64.h"
 #include "polyfills/base/check.h"
 #include "polyfills/base/check_op.h"
+#include "base/compiler_specific.h"
 #include "base/containers/contains.h"
 #include "base/containers/span.h"
 #include "base/debug/crash_logging.h"
 #include "base/pickle.h"
 #include "base/strings/strcat.h"
-#include "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"
 #include "polyfills/base/trace_event/memory_usage_estimator.h"
+#include "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"
 #include "base/unguessable_token.h"
 #include "url/gurl.h"
 #include "url/scheme_host_port.h"
 #include "url/url_constants.h"
+#include "url/url_features.h"
 #include "url/url_util.h"
 
 namespace url {
@@ -53,7 +56,7 @@
     // It's SchemeHostPort's responsibility to filter out unrecognized schemes;
     // sanity check that this is happening.
     GURL_DCHECK(!tuple.IsValid() || url.IsStandard() ||
-           gurl_base::Contains(GetLocalSchemes(), url.scheme_piece()) ||
+           gurl_base::Contains(GetLocalSchemes(), url.scheme()) ||
            AllowNonStandardSchemesForAndroidWebView());
   }
 
@@ -160,7 +163,7 @@
 bool Origin::IsSameOriginWith(const Origin& other) const {
   // scheme/host/port must match, even for opaque origins where |tuple_| holds
   // the precursor origin.
-  return std::tie(tuple_, nonce_) == std::tie(other.tuple_, other.nonce_);
+  return *this == other;
 }
 
 bool Origin::IsSameOriginWith(const GURL& url) const {
@@ -183,7 +186,7 @@
   // For "no access" schemes, blink's SecurityOrigin will always create an
   // opaque unique one. However, about: scheme is also registered as such but
   // does not behave this way, therefore exclude it from this check.
-  if (gurl_base::Contains(url::GetNoAccessSchemes(), url.scheme()) &&
+  if (gurl_base::Contains(url::GetNoAccessSchemes(), url.GetScheme()) &&
       !url.SchemeIs(kAboutScheme)) {
     // If |this| is not opaque, definitely return false as the expectation
     // is for opaque origin.
@@ -203,8 +206,8 @@
   // origin can always create an opaque tuple origin.
   if (url.IsStandard()) {
     // Note: if extra copies of the scheme and host are undesirable, this check
-    // can be implemented using StringPiece comparisons, but it has to account
-    // explicitly checks on port numbers.
+    // can be implemented using std::string_view comparisons, but it has to
+    // account explicitly checks on port numbers.
     if (url.SchemeIsFileSystem()) {
       url_tuple = SchemeHostPort(*url.inner_url());
     } else {
@@ -245,22 +248,22 @@
   if (!tuple_.IsValid())
     return true;
 
-  // However, when there is precursor present, the schemes must match.
-  return url.scheme() == tuple_.scheme();
+  // However, when there is precursor present, that must match.
+  return SchemeHostPort(url) == tuple_;
 }
 
 bool Origin::DomainIs(std::string_view canonical_domain) const {
   return !opaque() && url::DomainIs(tuple_.host(), canonical_domain);
 }
 
-bool Origin::operator<(const Origin& other) const {
-  return std::tie(tuple_, nonce_) < std::tie(other.tuple_, other.nonce_);
-}
-
 Origin Origin::DeriveNewOpaqueOrigin() const {
   return Origin(Nonce(), tuple_);
 }
 
+const gurl_base::UnguessableToken* Origin::GetNonceForTesting() const {
+  return GetNonceForSerialization();
+}
+
 std::string Origin::GetDebugString(bool include_nonce) const {
   // Handle non-opaque origins first, as they are simpler.
   if (!opaque()) {
@@ -332,18 +335,20 @@
     pickle.WriteUInt64(0);
   }
 
-  gurl_base::span<const uint8_t> data(static_cast<const uint8_t*>(pickle.data()),
-                                 pickle.size());
+  gurl_base::span<const uint8_t> UNSAFE_TODO(
+      data(static_cast<const uint8_t*>(pickle.data()), pickle.size()));
   // Base64 encode the data to make it nicer to play with.
   return gurl_base::Base64Encode(data);
 }
 
 // static
-std::optional<Origin> Origin::Deserialize(const std::string& value) {
+std::optional<Origin> Origin::Deserialize(std::string_view value) {
   std::string data;
   if (!gurl_base::Base64Decode(value, &data))
     return std::nullopt;
-  gurl_base::Pickle pickle(reinterpret_cast<char*>(&data[0]), data.size());
+
+  gurl_base::Pickle pickle =
+      gurl_base::Pickle::WithUnownedBuffer(gurl_base::as_byte_span(data));
   gurl_base::PickleIterator reader(pickle);
 
   std::string pickled_url;
@@ -453,10 +458,11 @@
   return *this;
 }
 
-bool Origin::Nonce::operator<(const Origin::Nonce& other) const {
+std::strong_ordering Origin::Nonce::operator<=>(
+    const Origin::Nonce& other) const {
   // When comparing, lazy-generation is required of both tokens, so that an
   // ordering is established.
-  return token() < other.token();
+  return token() <=> other.token();
 }
 
 bool Origin::Nonce::operator==(const Origin::Nonce& other) const {
@@ -466,22 +472,4 @@
   return (other.token_ == token_) && !(token_.is_empty() && (&other != this));
 }
 
-bool Origin::Nonce::operator!=(const Origin::Nonce& other) const {
-  return !(*this == other);
-}
-
-namespace debug {
-
-ScopedOriginCrashKey::ScopedOriginCrashKey(
-    gurl_base::debug::CrashKeyString* crash_key,
-    const url::Origin* value)
-    : scoped_string_value_(
-          crash_key,
-          value ? value->GetDebugString(false /* include_nonce */)
-                : "nullptr") {}
-
-ScopedOriginCrashKey::~ScopedOriginCrashKey() = default;
-
-}  // namespace debug
-
 }  // namespace url
diff --git a/url/origin.h b/url/origin.h
index 6528946..ffb66f6 100644
--- a/url/origin.h
+++ b/url/origin.h
@@ -7,14 +7,14 @@
 
 #include <stdint.h>
 
+#include <compare>
 #include <memory>
+#include <optional>
 #include <string>
 #include <string_view>
+#include <utility>
 
-#include <optional>
 #include "polyfills/base/component_export.h"
-#include "polyfills/base/debug/alias.h"
-#include "base/debug/crash_logging.h"
 #include "base/gtest_prod_util.h"
 #include "base/strings/string_util.h"
 #include "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h"
@@ -37,16 +37,15 @@
 class StorageKeyTest;
 }  // namespace blink
 
+namespace content {
+class SiteInfo;
+}  // namespace content
+
 namespace IPC {
 template <class P>
 struct ParamTraits;
 }  // namespace IPC
 
-namespace ipc_fuzzer {
-template <class T>
-struct FuzzTraits;
-}  // namespace ipc_fuzzer
-
 namespace mojo {
 template <typename DataViewType, typename T>
 struct StructTraits;
@@ -57,6 +56,10 @@
 class SchemefulSite;
 }  // namespace net
 
+namespace optimization_guide {
+class SecurityOriginSerializer;
+}
+
 namespace url {
 
 namespace mojom {
@@ -224,11 +227,10 @@
   // are exact matches. Two opaque origins are same-origin only if their
   // internal nonce values match. A non-opaque origin is never same-origin with
   // an opaque origin.
+  //
+  // If you are looking for a same _site_ check between origins, see
+  // net::SchemefulSite::IsSameSite.
   bool IsSameOriginWith(const Origin& other) const;
-  bool operator==(const Origin& other) const { return IsSameOriginWith(other); }
-  bool operator!=(const Origin& other) const {
-    return !IsSameOriginWith(other);
-  }
 
   // Non-opaque origin is "same-origin" with `url` if their schemes, hosts, and
   // ports are exact matches. Opaque origin is never "same-origin" with any
@@ -281,7 +283,15 @@
 
   // Allows Origin to be used as a key in STL (for example, a std::set or
   // std::map).
-  bool operator<(const Origin& other) const;
+  friend bool operator==(const Origin& left, const Origin& right) = default;
+  friend auto operator<=>(const Origin& left, const Origin& right) = default;
+
+  // Allows Origin to be used as a key in ABSL (for example, absl::flat_hash_set
+  // or absl::flat_hash_map).
+  template <typename H>
+  friend H AbslHashValue(H h, const Origin& o) {
+    return H::combine(std::move(h), o.tuple_, o.nonce_);
+  }
 
   // Creates a new opaque origin that is guaranteed to be cross-origin to all
   // currently existing origins. An origin created by this method retains its
@@ -301,18 +311,22 @@
   // |d|, and |d| is cross-origin to |a| and |c|.
   Origin DeriveNewOpaqueOrigin() const;
 
+  // Returns the nonce associated with the origin, if it is opaque, or nullptr
+  // otherwise. This is only for use in tests.
+  const gurl_base::UnguessableToken* GetNonceForTesting() const;
+
   // Creates a string representation of the object that can be used for logging
   // and debugging. It serializes the internal state, such as the nonce value
   // and precursor information.
   std::string GetDebugString(bool include_nonce = true) const;
 
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ROBOLECTRIC)
-  gurl_base::android::ScopedJavaLocalRef<jobject> ToJavaObject() const;
-  static Origin FromJavaObject(
-      const gurl_base::android::JavaRef<jobject>& java_origin);
+  jni_zero::ScopedJavaLocalRef<jobject> ToJavaObject(JNIEnv* env) const;
+  static Origin FromJavaObject(JNIEnv* env,
+                               const jni_zero::JavaRef<jobject>& java_origin);
   static jlong CreateNative(JNIEnv* env,
-                            const gurl_base::android::JavaRef<jstring>& java_scheme,
-                            const gurl_base::android::JavaRef<jstring>& java_host,
+                            const jni_zero::JavaRef<jstring>& java_scheme,
+                            const jni_zero::JavaRef<jstring>& java_host,
                             uint16_t port,
                             bool is_opaque,
                             uint64_t tokenHighBits,
@@ -336,17 +350,20 @@
   friend class blink::SecurityOrigin;
   friend class blink::SecurityOriginTest;
   friend class blink::StorageKey;
+  // SiteInfo needs the nonce to compute the site URL for some opaque origins,
+  // like data: URLs.
+  friend class content::SiteInfo;
   // SchemefulSite needs access to the serialization/deserialization logic which
   // includes the nonce.
   friend class net::SchemefulSite;
   friend class OriginTest;
   friend struct mojo::UrlOriginAdapter;
-  friend struct ipc_fuzzer::FuzzTraits<Origin>;
   friend struct mojo::StructTraits<url::mojom::OriginDataView, url::Origin>;
   friend IPC::ParamTraits<url::Origin>;
   friend COMPONENT_EXPORT(URL) std::ostream& operator<<(std::ostream& out,
                                                         const Origin& origin);
   friend class blink::StorageKeyTest;
+  friend class optimization_guide::SecurityOriginSerializer;
 
   // Origin::Nonce is a wrapper around gurl_base::UnguessableToken that generates
   // the random value only when the value is first accessed. The lazy generation
@@ -381,11 +398,17 @@
     Nonce(Nonce&&) noexcept;
     Nonce& operator=(Nonce&&) noexcept;
 
-    // Note that operator<, used by maps type containers, will trigger |token_|
-    // lazy-initialization. Equality comparisons do not.
-    bool operator<(const Nonce& other) const;
+    // Note that operator<=>, used by maps type containers, will trigger
+    // |token_| lazy-initialization. Equality comparisons do not.
+    std::strong_ordering operator<=>(const Nonce& other) const;
     bool operator==(const Nonce& other) const;
-    bool operator!=(const Nonce& other) const;
+
+    // Hashes the Nonce for absl hash containers. Will trigger |token_|
+    // lazy-initialization.
+    template <typename H>
+    friend H AbslHashValue(H h, const Nonce& n) {
+      return H::combine(std::move(h), n.token());
+    }
 
    private:
     friend class OriginTest;
@@ -449,7 +472,7 @@
 
   // Deserializes an origin from |ToValueWithNonce|. Returns nullopt if the
   // value was invalid in any way.
-  static std::optional<Origin> Deserialize(const std::string& value);
+  static std::optional<Origin> Deserialize(std::string_view value);
 
   // The tuple is used for both tuple origins (e.g. https://example.com:80), as
   // well as for opaque origins, where it tracks the tuple origin from which
@@ -471,29 +494,24 @@
 
 COMPONENT_EXPORT(URL) bool IsSameOriginWith(const GURL& a, const GURL& b);
 
-// DEBUG_ALIAS_FOR_ORIGIN(var_name, origin) copies `origin` into a new
-// stack-allocated variable named `<var_name>`. This helps ensure that the
-// value of `origin` gets preserved in crash dumps.
-#define DEBUG_ALIAS_FOR_ORIGIN(var_name, origin) \
-  DEBUG_ALIAS_FOR_CSTR(var_name, (origin).Serialize().c_str(), 128)
-
-namespace debug {
-
-class COMPONENT_EXPORT(URL) ScopedOriginCrashKey {
- public:
-  ScopedOriginCrashKey(gurl_base::debug::CrashKeyString* crash_key,
-                       const url::Origin* value);
-  ~ScopedOriginCrashKey();
-
-  ScopedOriginCrashKey(const ScopedOriginCrashKey&) = delete;
-  ScopedOriginCrashKey& operator=(const ScopedOriginCrashKey&) = delete;
-
- private:
-  gurl_base::debug::ScopedCrashKeyString scoped_string_value_;
-};
-
-}  // namespace debug
-
 }  // namespace url
 
+#if BUILDFLAG(IS_ANDROID)
+namespace jni_zero {
+
+// @JniType conversion function.
+template <>
+inline url::Origin FromJniType<url::Origin>(JNIEnv* env,
+                                            const JavaRef<jobject>& j_obj) {
+  return url::Origin::FromJavaObject(env, j_obj);
+}
+template <>
+inline ScopedJavaLocalRef<jobject> ToJniType(JNIEnv* env,
+                                             const url::Origin& obj) {
+  return obj.ToJavaObject(env);
+}
+
+}  // namespace jni_zero
+#endif
+
 #endif  // URL_ORIGIN_H_
diff --git a/url/origin_abstract_tests.h b/url/origin_abstract_tests.h
index 78483bf..b0737eb 100644
--- a/url/origin_abstract_tests.h
+++ b/url/origin_abstract_tests.h
@@ -5,6 +5,7 @@
 #ifndef URL_ORIGIN_ABSTRACT_TESTS_H_
 #define URL_ORIGIN_ABSTRACT_TESTS_H_
 
+#include <initializer_list>
 #include <string>
 #include <string_view>
 #include <type_traits>
@@ -277,6 +278,46 @@
   EXPECT_TRUE(this->IsOpaque(origin));
 }
 
+TYPED_TEST_P(
+    AbstractOriginTest,
+    AndroidWebViewHackWithStandardCompliantNonSpecialSchemeURLParsing) {
+  EnableNonStandardSchemesForAndroidWebView();
+
+  // Non-Standard scheme cases.
+  {
+    auto origin_a = this->CreateOriginFromString("non-standard://a.com:80");
+    // Ensure that a host and a port are discarded.
+    EXPECT_EQ(this->GetHost(origin_a), "");
+    EXPECT_EQ(this->GetPort(origin_a), 0);
+    EXPECT_EQ(this->Serialize(origin_a), "non-standard://");
+    EXPECT_FALSE(this->IsOpaque(origin_a));
+
+    // URLs are considered same-origin if their schemes match, even if
+    // their host and port are different.
+    auto origin_b = this->CreateOriginFromString("non-standard://b.com:90");
+    EXPECT_TRUE(this->IsSameOrigin(origin_a, origin_b));
+
+    // URLs are not considered same-origin if their schemes don't match,
+    // even if their host and port are same.
+    auto another_origin_a =
+        this->CreateOriginFromString("another-non-standard://a.com:80");
+    EXPECT_FALSE(this->IsSameOrigin(origin_a, another_origin_a));
+  }
+
+  // Standard scheme cases.
+  {
+    // Ensure that the behavior of a standard URL is preserved.
+    auto origin_a = this->CreateOriginFromString("https://a.com:80");
+    EXPECT_EQ(this->GetHost(origin_a), "a.com");
+    EXPECT_EQ(this->GetPort(origin_a), 80);
+    EXPECT_EQ(this->Serialize(origin_a), "https://a.com:80");
+    EXPECT_FALSE(this->IsOpaque(origin_a));
+
+    auto origin_b = this->CreateOriginFromString("https://b.com:80");
+    EXPECT_FALSE(this->IsSameOrigin(origin_a, origin_b));
+  }
+}
+
 TYPED_TEST_P(AbstractOriginTest, OpaqueOriginsFromValidUrls) {
   const char* kTestCases[] = {
       // Built-in noaccess schemes.
@@ -495,11 +536,8 @@
       // always have empty hostnames, but are allowed to be url::Origins.
       {"local:", {"local", "", 0}},
       {"local:foo", {"local", "", 0}},
-      {"local://bar", {"local", "", 0}},
-      {"also-local://bar", {"also-local", "", 0}},
 
       {"std-with-host://host", {"std-with-host", "host", 0}},
-      {"local://host", {"local", "", 0}},
       {"local-std-with-host://host", {"local-std-with-host", "host", 0}},
   };
 
@@ -514,13 +552,33 @@
   }
 }
 
-REGISTER_TYPED_TEST_SUITE_P(AbstractOriginTest,
-                            NonStandardSchemeWithAndroidWebViewHack,
-                            OpaqueOriginsFromValidUrls,
-                            OpaqueOriginsFromInvalidUrls,
-                            TupleOrigins,
-                            CustomSchemes_OpaqueOrigins,
-                            CustomSchemes_TupleOrigins);
+TYPED_TEST_P(AbstractOriginTest,
+             CustomSchemes_TupleOrigins_StandardCompliantNonSpecialSchemeFlag) {
+  struct TestCase {
+    std::string_view input;
+    SchemeHostPort expected_tuple;
+  } test_cases[] = {
+      {"local://bar", {"local", "bar", 0}},
+      {"also-local://bar", {"also-local", "bar", 0}},
+  };
+  for (const TestCase& test : test_cases) {
+    SCOPED_TRACE(testing::Message() << "Test input: " << test.input);
+    EXPECT_TRUE(this->IsValidUrl(test.input));
+    auto origin = this->CreateOriginFromString(test.input);
+    this->VerifyTupleOriginInvariants(origin, test.expected_tuple);
+  }
+}
+
+REGISTER_TYPED_TEST_SUITE_P(
+    AbstractOriginTest,
+    NonStandardSchemeWithAndroidWebViewHack,
+    AndroidWebViewHackWithStandardCompliantNonSpecialSchemeURLParsing,
+    OpaqueOriginsFromValidUrls,
+    OpaqueOriginsFromInvalidUrls,
+    TupleOrigins,
+    CustomSchemes_OpaqueOrigins,
+    CustomSchemes_TupleOrigins,
+    CustomSchemes_TupleOrigins_StandardCompliantNonSpecialSchemeFlag);
 
 }  // namespace url
 
diff --git a/url/origin_debug.cc b/url/origin_debug.cc
new file mode 100644
index 0000000..2bb4559
--- /dev/null
+++ b/url/origin_debug.cc
@@ -0,0 +1,20 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "url/origin_debug.h"
+
+#include "url/origin.h"
+
+namespace url::debug {
+
+ScopedOriginCrashKey::ScopedOriginCrashKey(
+    gurl_base::debug::CrashKeyString* crash_key,
+    const url::Origin* value)
+    : scoped_string_value_(
+          crash_key,
+          value ? value->GetDebugString(/*include_nonce=*/false) : "nullptr") {}
+
+ScopedOriginCrashKey::~ScopedOriginCrashKey() = default;
+
+}  // namespace url::debug
diff --git a/url/origin_debug.h b/url/origin_debug.h
new file mode 100644
index 0000000..56f132e
--- /dev/null
+++ b/url/origin_debug.h
@@ -0,0 +1,41 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef URL_ORIGIN_DEBUG_H_
+#define URL_ORIGIN_DEBUG_H_
+
+#include "polyfills/base/component_export.h"
+#include "polyfills/base/debug/alias.h"
+#include "base/debug/crash_logging.h"
+
+// DEBUG_ALIAS_FOR_ORIGIN(var_name, origin) copies `origin` into a new
+// stack-allocated variable named `<var_name>`. This helps ensure that the
+// value of `origin` gets preserved in crash dumps.
+#define DEBUG_ALIAS_FOR_ORIGIN(var_name, origin) \
+  DEBUG_ALIAS_FOR_CSTR(var_name, (origin).Serialize().c_str(), 128)
+
+namespace url {
+
+class Origin;
+
+namespace debug {
+
+class COMPONENT_EXPORT(URL) ScopedOriginCrashKey {
+ public:
+  ScopedOriginCrashKey(gurl_base::debug::CrashKeyString* crash_key,
+                       const url::Origin* value);
+  ~ScopedOriginCrashKey();
+
+  ScopedOriginCrashKey(const ScopedOriginCrashKey&) = delete;
+  ScopedOriginCrashKey& operator=(const ScopedOriginCrashKey&) = delete;
+
+ private:
+  gurl_base::debug::ScopedCrashKeyString scoped_string_value_;
+};
+
+}  // namespace debug
+
+}  // namespace url
+
+#endif  // URL_ORIGIN_DEBUG_H_
diff --git a/url/origin_unittest.cc b/url/origin_unittest.cc
index 842522f..ab36643 100644
--- a/url/origin_unittest.cc
+++ b/url/origin_unittest.cc
@@ -2,15 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "url/origin.h"
+
 #include <stddef.h>
 #include <stdint.h>
 
 #include "polyfills/base/memory/raw_ptr.h"
+#include "base/test/scoped_feature_list.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "absl/container/flat_hash_set.h"
 #include "url/gurl.h"
-#include "url/origin.h"
 #include "url/origin_abstract_tests.h"
+#include "url/origin_debug.h"
 #include "url/url_util.h"
 
 namespace url {
@@ -96,6 +100,34 @@
     return Origin::Deserialize(value);
   }
 
+ protected:
+  struct SerializationTestCase {
+    std::string_view url;
+    std::string_view expected;
+    std::optional<std::string_view> expected_log;
+  };
+
+  void TestSerialization(const SerializationTestCase& test_case) const {
+    SCOPED_TRACE(test_case.url);
+    GURL url(test_case.url);
+    EXPECT_TRUE(url.is_valid());
+    Origin origin = Origin::Create(url);
+    std::string serialized = origin.Serialize();
+
+    ExpectParsedUrlsEqual(GURL(serialized), origin.GetURL());
+
+    EXPECT_EQ(test_case.expected, serialized);
+
+    // The '<<' operator sometimes produces additional information.
+    std::stringstream out;
+    out << origin;
+    if (test_case.expected_log) {
+      EXPECT_EQ(test_case.expected_log, out.str());
+    } else {
+      EXPECT_EQ(test_case.expected, out.str());
+    }
+  }
+
  private:
   ScopedSchemeRegistryForTests scoped_registry_;
 };
@@ -178,6 +210,18 @@
             url::Origin::Resolve(GURL("about:blank?hello#whee"), opaque_b));
 }
 
+TEST_F(OriginTest, Hashing) {
+  url::Origin origin = url::Origin::Create(GURL("http://www.google.com"));
+  url::Origin opaque;
+  EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque));
+
+  // Test that origins support absl hashing. Hashing an opaque origin should
+  // trigger lazy initialization of its nonce.
+  absl::flat_hash_set<url::Origin> origin_set{origin, opaque};
+  EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque));
+  EXPECT_THAT(origin_set, ::testing::UnorderedElementsAre(origin, opaque));
+}
+
 TEST_F(OriginTest, ConstructFromTuple) {
   struct TestCases {
     const char* const scheme;
@@ -204,11 +248,7 @@
 }
 
 TEST_F(OriginTest, Serialization) {
-  struct TestCases {
-    const char* const url;
-    const char* const expected;
-    const char* const expected_log;
-  } cases[] = {
+  SerializationTestCase cases[] = {
       {"http://192.168.9.1/", "http://192.168.9.1"},
       {"http://[2001:db8::1]/", "http://[2001:db8::1]"},
       {"http://☃.net/", "http://xn--n3h.net"},
@@ -220,25 +260,32 @@
       {"file://example.com/etc/passwd", "file://",
        "file:// [internally: file://example.com]"},
       {"data:,", "null", "null [internally: (nonce TBD) anonymous]"},
+      {"git:", "null", "null [internally: (nonce TBD) anonymous]"},
+      {"git:/", "null", "null [internally: (nonce TBD) anonymous]"},
+      {"git://host/path", "null", "null [internally: (nonce TBD) anonymous]"},
+      {"local-and-standard://host/path", "local-and-standard://host"},
+      // A port is omitted if the scheme doesn't have the default port.
+      // See SchemeHostPort::SerializeInternal for details.
+      {"local-and-standard://host:123/path", "local-and-standard://host"},
+      {"standard-but-noaccess://host/path", "null",
+       "null [internally: (nonce TBD) anonymous]"},
+      {"local-but-nonstandard://host/path", "local-but-nonstandard://host"},
+      {"local-but-nonstandard://host:123/path", "local-but-nonstandard://host"},
   };
-
   for (const auto& test_case : cases) {
-    SCOPED_TRACE(test_case.url);
-    GURL url(test_case.url);
-    EXPECT_TRUE(url.is_valid());
-    Origin origin = Origin::Create(url);
-    std::string serialized = origin.Serialize();
-    ExpectParsedUrlsEqual(GURL(serialized), origin.GetURL());
+    TestSerialization(test_case);
+  }
+}
 
-    EXPECT_EQ(test_case.expected, serialized);
+TEST_F(OriginTest, SerializationWithAndroidWebViewHackEnabled) {
+  EnableNonStandardSchemesForAndroidWebView();
 
-    // The '<<' operator sometimes produces additional information.
-    std::stringstream out;
-    out << origin;
-    if (test_case.expected_log)
-      EXPECT_EQ(test_case.expected_log, out.str());
-    else
-      EXPECT_EQ(test_case.expected, out.str());
+  SerializationTestCase cases[] = {
+      {"nonstandard://host/path", "nonstandard://"},
+      {"nonstandard://host:123/path", "nonstandard://"},
+  };
+  for (const auto& test_case : cases) {
+    TestSerialization(test_case);
   }
 }
 
@@ -493,11 +540,13 @@
 
   // Call origin.CanBeDerivedFrom(url) for each of the following test cases
   // and ensure that it returns |expected_value|
-  const struct {
+  struct TestCase {
     const char* url;
     raw_ptr<Origin> origin;
     bool expected_value;
-  } kTestCases[] = {
+  };
+
+  const TestCase cases[] = {
       {"https://a.com", &regular_origin, true},
       // Web URL can commit in an opaque origin with precursor information.
       // Example: iframe sandbox navigated to a.com.
@@ -609,9 +658,12 @@
       {"local-but-nonstandard://a.com", &local_non_standard_origin, true},
       {"local-but-nonstandard://a.com",
        &local_non_standard_opaque_precursor_origin, true},
+      {"local-but-nonstandard://b.com", &local_non_standard_origin, false},
+      {"local-but-nonstandard://b.com",
+       &local_non_standard_opaque_precursor_origin, false},
   };
 
-  for (const auto& test_case : kTestCases) {
+  for (const auto& test_case : cases) {
     SCOPED_TRACE(testing::Message() << "(origin, url): (" << *test_case.origin
                                     << ", " << test_case.url << ")");
     EXPECT_EQ(test_case.expected_value,
@@ -769,6 +821,47 @@
   EXPECT_TRUE(foo_origin.IsSameOriginWith(GURL("blob:https://foo.com/guid")));
 }
 
+TEST_F(OriginTest, IsSameOriginLocalNonStandardScheme) {
+  GURL a_url = GURL("local-but-nonstandard://a.com/");
+  GURL b_url = GURL("local-but-nonstandard://b.com/");
+  url::Origin a_origin = url::Origin::Create(a_url);
+  url::Origin b_origin = url::Origin::Create(b_url);
+
+  EXPECT_TRUE(a_origin.IsSameOriginWith(a_origin));
+  EXPECT_TRUE(a_origin.IsSameOriginWith(a_url));
+
+  EXPECT_FALSE(a_origin.IsSameOriginWith(b_origin));
+  EXPECT_FALSE(a_origin.IsSameOriginWith(b_url));
+}
+
+TEST_F(OriginTest, OriginWithAndroidWebViewHackEnabled) {
+  EnableNonStandardSchemesForAndroidWebView();
+
+  GURL a_url = GURL("nonstandard://a.com/");
+  GURL b_url = GURL("nonstandard://b.com/");
+  url::Origin a_origin = url::Origin::Create(a_url);
+  url::Origin b_origin = url::Origin::Create(b_url);
+
+  EXPECT_TRUE(a_origin.IsSameOriginWith(a_origin));
+  EXPECT_TRUE(a_origin.IsSameOriginWith(a_url));
+
+  // When AndroidWebViewHack is enabled, only a scheme part is checked. Thus,
+  // "nonstandard://a.com/" and "nonstandard://b.com/" are considered as the
+  // same origin. This is not ideal, given that a host and a port are available
+  // for non-special url schemes being parsed after complying with the
+  // standards, but we can't check a host nor a port to avoid breaking existing
+  // WebView code. See https://crbug.com/40063064 for details.
+  EXPECT_TRUE(a_origin.IsSameOriginWith(b_origin));
+  EXPECT_TRUE(a_origin.IsSameOriginWith(b_url));
+  EXPECT_TRUE(a_origin.CanBeDerivedFrom(b_url));
+
+  GURL another_scheme_url = GURL("another-nonstandard://a.com/");
+  url::Origin another_scheme_origin = url::Origin::Create(another_scheme_url);
+  EXPECT_FALSE(a_origin.IsSameOriginWith(another_scheme_origin));
+  EXPECT_FALSE(a_origin.IsSameOriginWith(another_scheme_url));
+  EXPECT_FALSE(a_origin.CanBeDerivedFrom(another_scheme_url));
+}
+
 INSTANTIATE_TYPED_TEST_SUITE_P(UrlOrigin,
                                AbstractOriginTest,
                                UrlOriginTestTraits);
diff --git a/url/scheme_host_port.cc b/url/scheme_host_port.cc
index 0b5e867..2b4e7f0 100644
--- a/url/scheme_host_port.cc
+++ b/url/scheme_host_port.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 #include <string.h>
 
+#include <compare>
 #include <ostream>
 #include <string_view>
 #include <tuple>
@@ -22,13 +23,14 @@
 #include "url/url_canon.h"
 #include "url/url_canon_stdstring.h"
 #include "url/url_constants.h"
+#include "url/url_features.h"
 #include "url/url_util.h"
 
 namespace url {
 
 namespace {
 
-bool IsCanonicalHost(const std::string_view& host) {
+bool IsCanonicalHost(std::string_view host, bool is_file_scheme) {
   std::string canon_host;
 
   // Try to canonicalize the host (copy/pasted from net/base. :( ).
@@ -36,8 +38,13 @@
                                      gurl_base::checked_cast<int>(host.length()));
   StdStringCanonOutput canon_host_output(&canon_host);
   CanonHostInfo host_info;
-  CanonicalizeHostVerbose(host.data(), raw_host_component,
-                          &canon_host_output, &host_info);
+  if (is_file_scheme) {
+    CanonicalizeFileHostVerbose(host, raw_host_component, canon_host_output,
+                                host_info);
+  } else {
+    CanonicalizeSpecialHostVerbose(host, raw_host_component, canon_host_output,
+                                   host_info);
+  }
 
   if (host_info.out_host.is_nonempty() &&
       host_info.family != CanonHostInfo::BROKEN) {
@@ -56,8 +63,8 @@
 // ShouldTreatAsOpaqueOrigin in Blink (there might be existing differences in
 // behavior between these 2 layers, but we should avoid introducing new
 // differences).
-bool IsValidInput(const std::string_view& scheme,
-                  const std::string_view& host,
+bool IsValidInput(std::string_view scheme,
+                  std::string_view host,
                   uint16_t port,
                   SchemeHostPort::ConstructPolicy policy) {
   // Empty schemes are never valid.
@@ -70,20 +77,18 @@
     return false;
 
   SchemeType scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
-  bool is_standard = GetStandardSchemeType(
-      scheme.data(),
-      Component(0, gurl_base::checked_cast<int>(scheme.length())),
-      &scheme_type);
+  bool is_standard = GetStandardSchemeType(scheme, &scheme_type);
   if (!is_standard) {
     // To be consistent with ShouldTreatAsOpaqueOrigin in Blink, local
     // non-standard schemes are currently allowed to be tuple origins.
-    // Nonstandard schemes don't have hostnames, so their tuple is just
-    // ("protocol", "", 0).
     //
     // TODO: Migrate "content:" and "externalfile:" to be standard schemes, and
     // remove this local scheme exception.
-    if (gurl_base::Contains(GetLocalSchemes(), scheme) && host.empty() && port == 0)
+    // For standard compliant non special scheme url parsing, a host can be
+    // empty for non-special URLs. Therefore, we don't check a host nor port.
+    if (gurl_base::Contains(GetLocalSchemes(), scheme)) {
       return true;
+    }
 
     // Otherwise, allow non-standard schemes only if the Android WebView
     // workaround is enabled.
@@ -104,9 +109,9 @@
       // Don't do an expensive canonicalization if the host is already
       // canonicalized.
       GURL_DCHECK(policy == SchemeHostPort::CHECK_CANONICALIZATION ||
-             IsCanonicalHost(host));
+             IsCanonicalHost(host, scheme == url::kFileScheme));
       if (policy == SchemeHostPort::CHECK_CANONICALIZATION &&
-          !IsCanonicalHost(host)) {
+          !IsCanonicalHost(host, scheme == url::kFileScheme)) {
         return false;
       }
 
@@ -122,9 +127,9 @@
       // Don't do an expensive canonicalization if the host is already
       // canonicalized.
       GURL_DCHECK(policy == SchemeHostPort::CHECK_CANONICALIZATION ||
-             IsCanonicalHost(host));
+             IsCanonicalHost(host, scheme == url::kFileScheme));
       if (policy == SchemeHostPort::CHECK_CANONICALIZATION &&
-          !IsCanonicalHost(host)) {
+          !IsCanonicalHost(host, scheme == url::kFileScheme)) {
         return false;
       }
 
@@ -135,7 +140,6 @@
 
     default:
       GURL_NOTREACHED();
-      return false;
   }
 }
 
@@ -147,6 +151,11 @@
                                std::string host,
                                uint16_t port,
                                ConstructPolicy policy) {
+  if (ShouldDiscardHostAndPort(scheme)) {
+    host = "";
+    port = 0;
+  }
+
   if (!IsValidInput(scheme, host, port, policy)) {
     GURL_DCHECK(!IsValid());
     return;
@@ -171,8 +180,8 @@
   if (!url.is_valid())
     return;
 
-  std::string_view scheme = url.scheme_piece();
-  std::string_view host = url.host_piece();
+  std::string_view scheme = url.scheme();
+  std::string_view host = url.host();
 
   // A valid GURL never returns PORT_INVALID.
   int port = url.EffectiveIntPort();
@@ -183,6 +192,11 @@
     GURL_DCHECK_LE(port, 65535);
   }
 
+  if (ShouldDiscardHostAndPort(scheme)) {
+    host = "";
+    port = 0;
+  }
+
   if (!IsValidInput(scheme, host, port, ALREADY_CANONICALIZED))
     return;
 
@@ -222,11 +236,15 @@
     return GURL(serialized);
 
   // If the serialized string is passed to GURL for parsing, it will append an
-  // empty path "/". Add that here. Note: per RFC 6454 we cannot do this for
-  // normal Origin serialization.
+  // empty path "/" for standard URLs but only if they are special. Add that
+  // here. Note: Non-special urls with empty paths do not have an appended "/".
+  // Note: per RFC 6454 we cannot do this for normal Origin serialization.
   GURL_DCHECK(!parsed.path.is_valid());
-  parsed.path = Component(serialized.length(), 1);
-  serialized.append("/");
+  if (IsStandardScheme(scheme_)) {
+    parsed.path = Component(serialized.length(), 1);
+    serialized.append("/");
+  }
+
   return GURL(std::move(serialized), parsed, true);
 }
 
@@ -235,11 +253,6 @@
          gurl_base::trace_event::EstimateMemoryUsage(host_);
 }
 
-bool SchemeHostPort::operator<(const SchemeHostPort& other) const {
-  return std::tie(port_, scheme_, host_) <
-         std::tie(other.port_, other.scheme_, other.host_);
-}
-
 std::string SchemeHostPort::SerializeInternal(url::Parsed* parsed) const {
   std::string result;
   if (!IsValid())
@@ -262,8 +275,7 @@
 
   // Omit the port component if the port matches with the default port
   // defined for the scheme, if any.
-  int default_port = DefaultPortForScheme(scheme_.data(),
-                                          static_cast<int>(scheme_.length()));
+  int default_port = DefaultPortForScheme(scheme_);
   if (default_port == PORT_UNSPECIFIED)
     return result;
   if (port_ != default_port) {
@@ -276,6 +288,10 @@
   return result;
 }
 
+bool SchemeHostPort::ShouldDiscardHostAndPort(std::string_view scheme) {
+  return IsAndroidWebViewHackEnabledScheme(scheme);
+}
+
 std::ostream& operator<<(std::ostream& out,
                          const SchemeHostPort& scheme_host_port) {
   return out << scheme_host_port.Serialize();
diff --git a/url/scheme_host_port.h b/url/scheme_host_port.h
index 9938824..a67eef8 100644
--- a/url/scheme_host_port.h
+++ b/url/scheme_host_port.h
@@ -7,8 +7,10 @@
 
 #include <stdint.h>
 
+#include <compare>
 #include <string>
 #include <string_view>
+#include <utility>
 
 #include "polyfills/base/component_export.h"
 
@@ -147,23 +149,30 @@
   // Note that this comparison is _not_ the same as an origin-based comparison.
   // In particular, invalid SchemeHostPort objects match each other (and
   // themselves). Opaque origins, on the other hand, would not.
-  bool operator==(const SchemeHostPort& other) const {
-    return port_ == other.port() && scheme_ == other.scheme() &&
-           host_ == other.host();
-  }
-  bool operator!=(const SchemeHostPort& other) const {
-    return !(*this == other);
-  }
-  // Allows SchemeHostPort to be used as a key in STL (for example, a std::set
-  // or std::map).
-  bool operator<(const SchemeHostPort& other) const;
+  friend bool operator==(const SchemeHostPort& left,
+                         const SchemeHostPort& right) = default;
+  friend auto operator<=>(const SchemeHostPort& left,
+                          const SchemeHostPort& right) = default;
 
- private:
+  template <typename H>
+  friend H AbslHashValue(H h, const SchemeHostPort& tuple) {
+    return H::combine(std::move(h), tuple.port_, tuple.scheme_, tuple.host_);
+  }
+
+  // Whether to discard host and port information for a specific scheme.
+  //
+  // Note that this hack is required to avoid breaking existing Android WebView
+  // behaviors. Currently, Android WebView doesn't use host and port information
+  // for non-special URLs. See https://crbug.com/40063064 for details.
+  static bool ShouldDiscardHostAndPort(std::string_view scheme);
+
   std::string SerializeInternal(url::Parsed* parsed) const;
 
+ private:
+  // Note: `port_` is declared first to control the sort order.
+  uint16_t port_ = 0;
   std::string scheme_;
   std::string host_;
-  uint16_t port_ = 0;
 };
 
 COMPONENT_EXPORT(URL)
diff --git a/url/scheme_host_port_unittest.cc b/url/scheme_host_port_unittest.cc
index 49bcf25..7b30921 100644
--- a/url/scheme_host_port_unittest.cc
+++ b/url/scheme_host_port_unittest.cc
@@ -7,6 +7,8 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <array>
+
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 #include "url/url_util.h"
@@ -63,6 +65,9 @@
       "about:srcdoc#ref", "about:srcdoc?query=123", "data:text/html,Hello!",
       "javascript:alert(1)",
 
+      // Non-special URLs which don't have an opaque path.
+      "git:/", "git://", "git:///", "git://host/", "git://host/path",
+
       // GURLs where GURL::is_valid returns false translate into an invalid
       // SchemeHostPort.
       "file://example.com:443/etc/passwd", "#!^%!$!&*",
@@ -131,6 +136,8 @@
                {"filesystem", "", 0},
                {"http", "", 80},
                {"data", "example.com", 80},
+               {"git", "", 0},
+               {"git", "example.com", 80},
                {"http", "☃.net", 80},
                {"http\nmore", "example.com", 80},
                {"http\rmore", "example.com", 80},
@@ -246,12 +253,13 @@
 }
 
 TEST_F(SchemeHostPortTest, Comparison) {
-  // These tuples are arranged in increasing order:
+  // These tuples are arranged in increasing order
   struct SchemeHostPorts {
     const char* scheme;
     const char* host;
     uint16_t port;
-  } tuples[] = {
+  };
+  auto tuples = std::to_array<SchemeHostPorts>({
       {"http", "a", 80},
       {"http", "b", 80},
       {"https", "a", 80},
@@ -260,7 +268,7 @@
       {"http", "b", 81},
       {"https", "a", 81},
       {"https", "b", 81},
-  };
+  });
 
   for (size_t i = 0; i < std::size(tuples); i++) {
     url::SchemeHostPort current(tuples[i].scheme, tuples[i].host,
diff --git a/url/third_party/mozilla/README.chromium b/url/third_party/mozilla/README.chromium
index 9df1992..5bd6273 100644
--- a/url/third_party/mozilla/README.chromium
+++ b/url/third_party/mozilla/README.chromium
@@ -1,12 +1,14 @@
 Name: url_parse
-Version: unknown
-URL: https://hg.mozilla.org/mozilla-central/file/tip/netwerk/base/nsURLParsers.cpp
-License: BSD and MPL 1.1/GPL 2.0/LGPL 2.1
+URL: https://github.com/mozilla-firefox/firefox.git
+Revision: c7df6606be1bfdf6d6cec37a4ff644a9841c3ec2
+Update Mechanism: Static.HardFork
+License: MPL-2.0
 License File: LICENSE.txt
+Update Mechanism: Static (https://crbug.com/419415029)
 Shipped: yes
 Security Critical: yes
 CPEPrefix: unknown
 
 Description:
 
-The file url_parse.cc is based on nsURLParsers.cc from Mozilla.
+The files `url_parse.*` were forked from Mozilla's nsURLParsers.cc in ~2013.
diff --git a/url/third_party/mozilla/url_parse.cc b/url/third_party/mozilla/url_parse.cc
index a7b72a5..e0b4a95 100644
--- a/url/third_party/mozilla/url_parse.cc
+++ b/url/third_party/mozilla/url_parse.cc
@@ -39,6 +39,7 @@
 #include <stdlib.h>
 
 #include <ostream>
+#include <string_view>
 
 #include "polyfills/base/check_op.h"
 #include "url/url_parse_internal.h"
@@ -47,6 +48,21 @@
 
 namespace url {
 
+std::ostream& operator<<(std::ostream& os, const Parsed& parsed) {
+  return os << "{ scheme: " << parsed.scheme
+            << ", username: " << parsed.username
+            << ", password: " << parsed.password << ", host: " << parsed.host
+            << ", port: " << parsed.port << ", path: " << parsed.path
+            << ", query: " << parsed.query << ", ref: " << parsed.ref
+            << ", has_opaque_path: " << parsed.has_opaque_path << " }";
+}
+
+Component MakeRange(size_t begin, size_t end) {
+  GURL_CHECK_LE(begin, end);
+  return Component(gurl_base::checked_cast<int>(begin),
+                   gurl_base::checked_cast<int>(end - begin));
+}
+
 namespace {
 
 // Returns true if the given character is a valid digit to use in a port.
@@ -60,10 +76,12 @@
 template <typename CHAR>
 int FindNextAuthorityTerminator(const CHAR* spec,
                                 int start_offset,
-                                int spec_len) {
+                                int spec_len,
+                                ParserMode parser_mode) {
   for (int i = start_offset; i < spec_len; i++) {
-    if (IsAuthorityTerminator(spec[i]))
+    if (IsAuthorityTerminator(spec[i], parser_mode)) {
       return i;
+    }
   }
   return spec_len;  // Not found.
 }
@@ -141,8 +159,9 @@
 // filled into the given *port variable, or -1 if there is no port number or it
 // is invalid.
 template <typename CHAR>
-void DoParseAuthority(const CHAR* spec,
+void DoParseAuthority(std::basic_string_view<CHAR> spec,
                       const Component& auth,
+                      ParserMode parser_mode,
                       Component* username,
                       Component* password,
                       Component* hostname,
@@ -151,7 +170,18 @@
   if (auth.len == 0) {
     username->reset();
     password->reset();
-    hostname->reset();
+    if (parser_mode == ParserMode::kSpecialURL) {
+      hostname->reset();
+    } else {
+      // Non-special URLs can have an empty host. The difference between "host
+      // is empty" and "host does not exist" matters in the canonicalization
+      // phase.
+      //
+      // Examples:
+      // - "git:///" => host is empty (this case).
+      // - "git:/" => host does not exist.
+      *hostname = Component(auth.begin, 0);
+    }
     port_num->reset();
     return;
   }
@@ -164,15 +194,15 @@
 
   if (spec[i] == '@') {
     // Found user info: <user-info>@<server-info>
-    ParseUserInfo(spec, Component(auth.begin, i - auth.begin), username,
+    ParseUserInfo(spec.data(), Component(auth.begin, i - auth.begin), username,
                   password);
-    ParseServerInfo(spec, MakeRange(i + 1, auth.begin + auth.len), hostname,
-                    port_num);
+    ParseServerInfo(spec.data(), MakeRange(i + 1, auth.begin + auth.len),
+                    hostname, port_num);
   } else {
     // No user info, everything is server info.
     username->reset();
     password->reset();
-    ParseServerInfo(spec, auth, hostname, port_num);
+    ParseServerInfo(spec.data(), auth, hostname, port_num);
   }
 }
 
@@ -225,15 +255,7 @@
                Component* query,
                Component* ref) {
   // path = [/]<segment1>/<segment2>/<...>/<segmentN>;<param>?<query>#<ref>
-
-  // Special case when there is no path.
-  if (path.len == -1) {
-    filepath->reset();
-    query->reset();
-    ref->reset();
-    return;
-  }
-  GURL_DCHECK(path.is_nonempty()) << "We should never have 0 length paths";
+  GURL_DCHECK(path.is_valid());
 
   // Search for first occurrence of either ? or #.
   int query_separator = -1;  // Index of the '?'
@@ -264,24 +286,32 @@
     query->reset();
   }
 
-  // File path: treat an empty file path as no file path.
-  if (file_end != path.begin)
+  if (file_end != path.begin) {
     *filepath = MakeRange(path.begin, file_end);
-  else
+  } else {
+    // File path: treat an empty file path as no file path.
+    //
+    // TODO(crbug.com/40063064): Consider to assign zero-length path component
+    // for non-special URLs because a path can be empty in non-special URLs.
+    // Currently, we don't have to distinguish between them. There is no visible
+    // difference.
     filepath->reset();
+  }
 }
 
-template <typename CHAR>
-bool DoExtractScheme(const CHAR* url, int url_len, Component* scheme) {
+template <typename CharT>
+bool DoExtractScheme(std::basic_string_view<CharT> url, Component* scheme) {
   // Skip leading whitespace and control characters.
-  int begin = 0;
-  while (begin < url_len && ShouldTrimFromURL(url[begin]))
+  size_t begin = 0;
+  while (begin < url.size() && ShouldTrimFromURL(url[begin])) {
     begin++;
-  if (begin == url_len)
+  }
+  if (begin == url.size()) {
     return false;  // Input is empty or all whitespace.
+  }
 
   // Find the first colon character.
-  for (int i = begin; i < url_len; i++) {
+  for (size_t i = begin; i < url.size(); i++) {
     if (url[i] == ':') {
       *scheme = MakeRange(begin, i);
       return true;
@@ -309,128 +339,225 @@
 // canonicalizer handles them, meaning if you've been to the corresponding
 // "http://foo.com/" link, it will be colored.
 template <typename CHAR>
-void DoParseAfterScheme(const CHAR* spec,
-                        int spec_len,
-                        int after_scheme,
-                        Parsed* parsed) {
-  int num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len);
+void DoParseAfterSpecialScheme(std::basic_string_view<CHAR> spec,
+                               int after_scheme,
+                               Parsed* parsed) {
+  int spec_len = gurl_base::checked_cast<int>(spec.length());
+  int num_slashes = CountConsecutiveSlashesOrBackslashes(spec, after_scheme);
   int after_slashes = after_scheme + num_slashes;
 
   // First split into two main parts, the authority (username, password, host,
   // and port) and the full path (path, query, and reference).
-  Component authority;
-  Component full_path;
+  //
+  // Treat everything from `after_slashes` to the next slash (or end of spec) to
+  // be the authority. Note that we ignore the number of slashes and treat it as
+  // the authority.
+  int end_auth = FindNextAuthorityTerminator(spec.data(), after_slashes,
+                                             spec_len, ParserMode::kSpecialURL);
 
-  // Found "//<some data>", looks like an authority section. Treat everything
-  // from there to the next slash (or end of spec) to be the authority. Note
-  // that we ignore the number of slashes and treat it as the authority.
-  int end_auth = FindNextAuthorityTerminator(spec, after_slashes, spec_len);
-  authority = Component(after_slashes, end_auth - after_slashes);
-
-  if (end_auth == spec_len)  // No beginning of path found.
-    full_path = Component();
-  else  // Everything starting from the slash to the end is the path.
-    full_path = Component(end_auth, spec_len - end_auth);
+  Component authority(after_slashes, end_auth - after_slashes);
+  // Everything starting from the slash to the end is the path.
+  Component full_path(end_auth, spec_len - end_auth);
 
   // Now parse those two sub-parts.
-  DoParseAuthority(spec, authority, &parsed->username, &parsed->password,
-                   &parsed->host, &parsed->port);
-  ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref);
+  DoParseAuthority(spec, authority, ParserMode::kSpecialURL, &parsed->username,
+                   &parsed->password, &parsed->host, &parsed->port);
+  ParsePath(spec.data(), full_path, &parsed->path, &parsed->query,
+            &parsed->ref);
 }
 
 // The main parsing function for standard URLs. Standard URLs have a scheme,
 // host, path, etc.
-template <typename CHAR>
-void DoParseStandardURL(const CHAR* spec, int spec_len, Parsed* parsed) {
-  GURL_DCHECK(spec_len >= 0);
-
+template <typename CharT>
+Parsed DoParseStandardUrl(std::basic_string_view<CharT> url) {
   // Strip leading & trailing spaces and control characters.
-  int begin = 0;
-  TrimURL(spec, &begin, &spec_len);
+  auto [begin, url_len] = TrimUrl(url);
+  url = url.substr(0, url_len);
 
   int after_scheme;
-  if (DoExtractScheme(spec, spec_len, &parsed->scheme)) {
-    after_scheme = parsed->scheme.end() + 1;  // Skip past the colon.
+  Parsed parsed;
+  if (DoExtractScheme(url, &parsed.scheme)) {
+    after_scheme = parsed.scheme.end() + 1;  // Skip past the colon.
   } else {
     // Say there's no scheme when there is no colon. We could also say that
     // everything is the scheme. Both would produce an invalid URL, but this way
     // seems less wrong in more cases.
-    parsed->scheme.reset();
-    after_scheme = begin;
+    parsed.scheme.reset();
+    after_scheme = gurl_base::checked_cast<int>(begin);
   }
-  DoParseAfterScheme(spec, spec_len, after_scheme, parsed);
+  DoParseAfterSpecialScheme(url, after_scheme, &parsed);
+  return parsed;
 }
 
 template <typename CHAR>
-void DoParseFileSystemURL(const CHAR* spec, int spec_len, Parsed* parsed) {
-  GURL_DCHECK(spec_len >= 0);
+void DoParseAfterNonSpecialScheme(std::basic_string_view<CHAR> spec,
+                                  int after_scheme,
+                                  Parsed* parsed) {
+  // The implementation is similar to `DoParseAfterSpecialScheme()`, but there
+  // are many subtle differences. So we have a different function for parsing
+  // non-special URLs.
 
-  // Get the unused parts of the URL out of the way.
-  parsed->username.reset();
-  parsed->password.reset();
-  parsed->host.reset();
-  parsed->port.reset();
-  parsed->path.reset();          // May use this; reset for convenience.
-  parsed->ref.reset();           // May use this; reset for convenience.
-  parsed->query.reset();         // May use this; reset for convenience.
-  parsed->clear_inner_parsed();  // May use this; reset for convenience.
+  int spec_len = gurl_base::checked_cast<int>(spec.length());
+  int num_slashes = CountConsecutiveSlashesButNotCountBackslashes(
+      spec.data(), after_scheme, spec_len);
 
-  // Strip leading & trailing spaces and control characters.
-  int begin = 0;
-  TrimURL(spec, &begin, &spec_len);
+  if (num_slashes >= 2) {
+    // Found "//<some data>", looks like an authority section.
+    //
+    // e.g.
+    //   "git://host:8000/path"
+    //          ^
+    //
+    // The state machine transition in the URL Standard is:
+    //
+    // https://url.spec.whatwg.org/#scheme-state
+    // => https://url.spec.whatwg.org/#path-or-authority-state
+    // => https://url.spec.whatwg.org/#authority-state
+    //
+    parsed->has_opaque_path = false;
 
-  // Handle empty specs or ones that contain only whitespace or control chars.
-  if (begin == spec_len) {
-    parsed->scheme.reset();
+    int after_slashes = after_scheme + 2;
+
+    // First split into two main parts, the authority (username, password, host,
+    // and port) and the full path (path, query, and reference).
+    //
+    // Treat everything from there to the next slash (or end of spec) to be the
+    // authority. Note that we ignore the number of slashes and treat it as the
+    // authority.
+    int end_auth = FindNextAuthorityTerminator(
+        spec.data(), after_slashes, spec_len, ParserMode::kNonSpecialURL);
+    Component authority(after_slashes, end_auth - after_slashes);
+
+    // Now parse those two sub-parts.
+    DoParseAuthority(spec, authority, ParserMode::kNonSpecialURL,
+                     &parsed->username, &parsed->password, &parsed->host,
+                     &parsed->port);
+
+    // Everything starting from the slash to the end is the path.
+    Component full_path(end_auth, spec_len - end_auth);
+    ParsePath(spec.data(), full_path, &parsed->path, &parsed->query,
+              &parsed->ref);
     return;
   }
 
-  int inner_start = -1;
+  if (num_slashes == 1) {
+    // Examples:
+    //   "git:/path"
+    //        ^
+    //
+    // The state machine transition in the URL Standard is:
+    //
+    // https://url.spec.whatwg.org/#scheme-state
+    // => https://url.spec.whatwg.org/#path-or-authority-state
+    // => https://url.spec.whatwg.org/#path-state
+    parsed->has_opaque_path = false;
+  } else {
+    // We didn't found "//" nor "/", so entering into an opaque-path-state.
+    //
+    // Examples:
+    //   "git:opaque path"
+    //        ^
+    //
+    // The state machine transition in the URL Standard is:
+    //
+    // https://url.spec.whatwg.org/#scheme-state
+    // => https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state
+    parsed->has_opaque_path = true;
+  }
 
+  parsed->username.reset();
+  parsed->password.reset();
+  // It's important to reset `parsed->host` here to distinguish between "host
+  // is empty" and "host doesn't exist".
+  parsed->host.reset();
+  parsed->port.reset();
+
+  // Everything starting after scheme to the end is the path.
+  Component full_path(after_scheme, spec_len - after_scheme);
+  ParsePath(spec.data(), full_path, &parsed->path, &parsed->query,
+            &parsed->ref);
+}
+
+// The main parsing function for non-special scheme URLs.
+template <typename CharT>
+Parsed DoParseNonSpecialUrl(std::basic_string_view<CharT> url,
+                            bool trim_path_end) {
+  // Strip leading & trailing spaces and control characters.
+  auto [begin, url_len] = TrimUrl(url, trim_path_end);
+  url = url.substr(0, url_len);
+
+  int after_scheme;
+  Parsed parsed;
+  if (DoExtractScheme(url, &parsed.scheme)) {
+    after_scheme = parsed.scheme.end() + 1;  // Skip past the colon.
+  } else {
+    // Say there's no scheme when there is no colon. We could also say that
+    // everything is the scheme. Both would produce an invalid URL, but this way
+    // seems less wrong in more cases.
+    parsed.scheme.reset();
+    after_scheme = 0;
+  }
+  DoParseAfterNonSpecialScheme(url, after_scheme, &parsed);
+  return parsed;
+}
+
+template <typename CharT>
+Parsed DoParseFileSystemUrl(std::basic_string_view<CharT> url) {
+  // Strip leading & trailing spaces and control characters.
+  auto [begin, url_len] = TrimUrl(url);
+
+  // Handle empty specs or ones that contain only whitespace or control chars.
+  if (begin == url_len) {
+    return {};
+  }
+
+  size_t inner_start = std::basic_string_view<CharT>::npos;
   // Extract the scheme.  We also handle the case where there is no scheme.
-  if (DoExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) {
+  Parsed parsed;
+  if (DoExtractScheme(url.substr(begin, url_len - begin), &parsed.scheme)) {
     // Offset the results since we gave ExtractScheme a substring.
-    parsed->scheme.begin += begin;
+    parsed.scheme.OffsetBy(begin);
 
-    if (parsed->scheme.end() == spec_len - 1)
-      return;
+    size_t scheme_end = parsed.scheme.CheckedEnd();
+    if (scheme_end == url_len - 1) {
+      return parsed;
+    }
 
-    inner_start = parsed->scheme.end() + 1;
+    inner_start = scheme_end + 1;
   } else {
     // No scheme found; that's not valid for filesystem URLs.
-    parsed->scheme.reset();
-    return;
+    return {};
   }
 
   Component inner_scheme;
-  const CHAR* inner_spec = &spec[inner_start];
-  int inner_spec_len = spec_len - inner_start;
-
-  if (DoExtractScheme(inner_spec, inner_spec_len, &inner_scheme)) {
+  std::basic_string_view inner_url =
+      url.substr(inner_start, url_len - inner_start);
+  if (DoExtractScheme(inner_url, &inner_scheme)) {
     // Offset the results since we gave ExtractScheme a substring.
-    inner_scheme.begin += inner_start;
+    inner_scheme.OffsetBy(inner_start);
 
-    if (inner_scheme.end() == spec_len - 1)
-      return;
+    if (inner_scheme.CheckedEnd() == url_len - 1) {
+      return parsed;
+    }
   } else {
     // No scheme found; that's not valid for filesystem URLs.
     // The best we can do is return "filesystem://".
-    return;
+    return parsed;
   }
 
   Parsed inner_parsed;
-
-  if (CompareSchemeComponent(spec, inner_scheme, kFileScheme)) {
-    // File URLs are special.
-    ParseFileURL(inner_spec, inner_spec_len, &inner_parsed);
-  } else if (CompareSchemeComponent(spec, inner_scheme, kFileSystemScheme)) {
+  if (CompareSchemeComponent(url, inner_scheme, kFileScheme)) {
+    // File URLs are special. The static cast is safe because we calculated the
+    // size above as the difference of two ints.
+    inner_parsed = ParseFileUrl(inner_url);
+  } else if (CompareSchemeComponent(url, inner_scheme, kFileSystemScheme)) {
     // Filesystem URLs don't nest.
-    return;
-  } else if (IsStandard(spec, inner_scheme)) {
+    return parsed;
+  } else if (IsStandard(inner_scheme.AsViewOn(url))) {
     // All "normal" URLs.
-    DoParseStandardURL(inner_spec, inner_spec_len, &inner_parsed);
+    inner_parsed = DoParseStandardUrl(inner_url);
   } else {
-    return;
+    return parsed;
   }
 
   // All members of inner_parsed need to be offset by inner_start.
@@ -447,15 +574,15 @@
   inner_parsed.path.begin += inner_start;
 
   // Query and ref move from inner_parsed to parsed.
-  parsed->query = inner_parsed.query;
+  parsed.query = inner_parsed.query;
   inner_parsed.query.reset();
-  parsed->ref = inner_parsed.ref;
+  parsed.ref = inner_parsed.ref;
   inner_parsed.ref.reset();
 
-  parsed->set_inner_parsed(inner_parsed);
+  parsed.set_inner_parsed(inner_parsed);
   if (!inner_parsed.scheme.is_valid() || !inner_parsed.path.is_valid() ||
       inner_parsed.inner_parsed()) {
-    return;
+    return parsed;
   }
 
   // The path in inner_parsed should start with a slash, then have a filesystem
@@ -463,116 +590,94 @@
   // second should be what it keeps; the rest goes to parsed.  If the path ends
   // before the second slash, it's still pretty clear what the user meant, so
   // we'll let that through.
-  if (!IsURLSlash(spec[inner_parsed.path.begin])) {
-    return;
+  if (!IsSlashOrBackslash(url[inner_parsed.path.begin])) {
+    return parsed;
   }
   int inner_path_end = inner_parsed.path.begin + 1;  // skip the leading slash
-  while (inner_path_end < spec_len && !IsURLSlash(spec[inner_path_end]))
+  while (static_cast<size_t>(inner_path_end) < url_len &&
+         !IsSlashOrBackslash(url[inner_path_end])) {
     ++inner_path_end;
-  parsed->path.begin = inner_path_end;
+  }
+  parsed.path.begin = inner_path_end;
   int new_inner_path_length = inner_path_end - inner_parsed.path.begin;
-  parsed->path.len = inner_parsed.path.len - new_inner_path_length;
-  parsed->inner_parsed()->path.len = new_inner_path_length;
+  parsed.path.len = inner_parsed.path.len - new_inner_path_length;
+  parsed.inner_parsed()->path.len = new_inner_path_length;
+  return parsed;
 }
 
 // Initializes a path URL which is merely a scheme followed by a path. Examples
 // include "about:foo" and "javascript:alert('bar');"
-template <typename CHAR>
-void DoParsePathURL(const CHAR* spec,
-                    int spec_len,
-                    bool trim_path_end,
-                    Parsed* parsed) {
-  // Get the non-path and non-scheme parts of the URL out of the way, we never
-  // use them.
-  parsed->username.reset();
-  parsed->password.reset();
-  parsed->host.reset();
-  parsed->port.reset();
-  parsed->path.reset();
-  parsed->query.reset();
-  parsed->ref.reset();
-
+template <typename CharT>
+Parsed DoParsePathUrl(std::basic_string_view<CharT> url, bool trim_path_end) {
   // Strip leading & trailing spaces and control characters.
-  int scheme_begin = 0;
-  TrimURL(spec, &scheme_begin, &spec_len, trim_path_end);
+  auto [scheme_begin, url_len] = TrimUrl(url, trim_path_end);
 
   // Handle empty specs or ones that contain only whitespace or control chars.
-  if (scheme_begin == spec_len) {
-    parsed->scheme.reset();
-    parsed->path.reset();
-    return;
+  if (scheme_begin == url_len) {
+    return {};
   }
 
-  int path_begin;
+  size_t path_begin;
+  Parsed parsed;
   // Extract the scheme, with the path being everything following. We also
   // handle the case where there is no scheme.
-  if (ExtractScheme(&spec[scheme_begin], spec_len - scheme_begin,
-                    &parsed->scheme)) {
+  if (ExtractScheme(url.substr(scheme_begin, url_len - scheme_begin),
+                    &parsed.scheme)) {
     // Offset the results since we gave ExtractScheme a substring.
-    parsed->scheme.begin += scheme_begin;
-    path_begin = parsed->scheme.end() + 1;
+    parsed.scheme.OffsetBy(scheme_begin);
+    path_begin = parsed.scheme.CheckedEnd() + 1;
   } else {
     // No scheme case.
-    parsed->scheme.reset();
+    parsed.scheme.reset();
     path_begin = scheme_begin;
   }
 
-  if (path_begin == spec_len)
-    return;
-  GURL_DCHECK_LT(path_begin, spec_len);
+  if (path_begin == url_len) {
+    return parsed;
+  }
+  GURL_DCHECK_LT(path_begin, url_len);
 
-  ParsePath(spec, MakeRange(path_begin, spec_len), &parsed->path,
-            &parsed->query, &parsed->ref);
+  ParsePath(url.data(), MakeRange(path_begin, url_len), &parsed.path,
+            &parsed.query, &parsed.ref);
+  return parsed;
 }
 
-template <typename CHAR>
-void DoParseMailtoURL(const CHAR* spec, int spec_len, Parsed* parsed) {
-  GURL_DCHECK(spec_len >= 0);
-
-  // Get the non-path and non-scheme parts of the URL out of the way, we never
-  // use them.
-  parsed->username.reset();
-  parsed->password.reset();
-  parsed->host.reset();
-  parsed->port.reset();
-  parsed->ref.reset();
-  parsed->query.reset();  // May use this; reset for convenience.
-
+template <typename CharT>
+Parsed DoParseMailtoUrl(std::basic_string_view<CharT> url) {
   // Strip leading & trailing spaces and control characters.
-  int begin = 0;
-  TrimURL(spec, &begin, &spec_len);
+  auto [begin, url_len] = TrimUrl(url);
 
   // Handle empty specs or ones that contain only whitespace or control chars.
-  if (begin == spec_len) {
-    parsed->scheme.reset();
-    parsed->path.reset();
-    return;
+  if (begin == url_len) {
+    return {};
   }
 
-  int path_begin = -1;
-  int path_end = -1;
+  size_t path_begin = std::basic_string_view<CharT>::npos;
+  size_t path_end = std::basic_string_view<CharT>::npos;
 
   // Extract the scheme, with the path being everything following. We also
   // handle the case where there is no scheme.
-  if (ExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) {
+  Parsed parsed;
+  if (ExtractScheme(url.substr(begin, url_len - begin), &parsed.scheme)) {
     // Offset the results since we gave ExtractScheme a substring.
-    parsed->scheme.begin += begin;
+    parsed.scheme.OffsetBy(begin);
 
-    if (parsed->scheme.end() != spec_len - 1) {
-      path_begin = parsed->scheme.end() + 1;
-      path_end = spec_len;
+    size_t scheme_end = parsed.scheme.CheckedEnd();
+    if (scheme_end != url_len - 1) {
+      path_begin = scheme_end + 1;
+      path_end = url_len;
     }
   } else {
     // No scheme found, just path.
-    parsed->scheme.reset();
+    parsed.scheme.reset();
     path_begin = begin;
-    path_end = spec_len;
+    path_end = url_len;
   }
 
   // Split [path_begin, path_end) into a path + query.
-  for (int i = path_begin; i < path_end; ++i) {
-    if (spec[i] == '?') {
-      parsed->query = MakeRange(i + 1, path_end);
+  for (size_t i = path_begin; i < path_end; ++i) {
+    if (url[i] == '?') {
+      parsed.query = MakeRange(i + 1, path_end);
       path_end = i;
       break;
     }
@@ -581,18 +686,19 @@
   // For compatability with the standard URL parser, treat no path as
   // -1, rather than having a length of 0
   if (path_begin == path_end) {
-    parsed->path.reset();
+    parsed.path.reset();
   } else {
-    parsed->path = MakeRange(path_begin, path_end);
+    parsed.path = MakeRange(path_begin, path_end);
   }
+  return parsed;
 }
 
-// Converts a port number in a string to an integer. We'd like to just call
-// sscanf but our input is not NULL-terminated, which sscanf requires. Instead,
-// we copy the digits to a small stack buffer (since we know the maximum number
-// of digits in a valid port number) that we can NULL terminate.
+// Converts a port number in a string to an integer. C++ does not have a simple
+// way to convert strings to numbers that works for both `char` and `char16_t`.
+// We copy the digits to a small stack buffer (since we know the maximum number
+// of digits in a valid port number) that we can use atoi().
 template <typename CHAR>
-int DoParsePort(const CHAR* spec, const Component& component) {
+int DoParsePort(std::basic_string_view<CHAR> spec, const Component& component) {
   // Easy success case when there is no port.
   const int kMaxDigits = 5;
   if (component.is_empty())
@@ -635,7 +741,7 @@
 }
 
 template <typename CHAR>
-void DoExtractFileName(const CHAR* spec,
+void DoExtractFileName(std::basic_string_view<CHAR> spec,
                        const Component& path,
                        Component* file_name) {
   // Handle empty paths: they have no file names.
@@ -650,7 +756,7 @@
   for (int i = path.end() - 1; i >= path.begin; i--) {
     if (spec[i] == ';') {
       file_end = i;
-    } else if (IsURLSlash(spec[i])) {
+    } else if (IsSlashOrBackslash(spec[i])) {
       // File name is everything following this character to the end
       *file_name = MakeRange(i + 1, file_end);
       return;
@@ -663,8 +769,8 @@
   return;
 }
 
-template <typename CHAR>
-bool DoExtractQueryKeyValue(const CHAR* spec,
+template <typename CharT>
+bool DoExtractQueryKeyValue(std::basic_string_view<CharT> spec,
                             Component* query,
                             Component* key,
                             Component* value) {
@@ -708,7 +814,7 @@
   return os << '{' << component.begin << ", " << component.len << "}";
 }
 
-Parsed::Parsed() : potentially_dangling_markup(false), inner_parsed_(NULL) {}
+Parsed::Parsed() = default;
 
 Parsed::Parsed(const Parsed& other)
     : scheme(other.scheme),
@@ -720,7 +826,7 @@
       query(other.query),
       ref(other.ref),
       potentially_dangling_markup(other.potentially_dangling_markup),
-      inner_parsed_(NULL) {
+      has_opaque_path(other.has_opaque_path) {
   if (other.inner_parsed_)
     set_inner_parsed(*other.inner_parsed_);
 }
@@ -736,6 +842,7 @@
     query = other.query;
     ref = other.ref;
     potentially_dangling_markup = other.potentially_dangling_markup;
+    has_opaque_path = other.has_opaque_path;
     if (other.inner_parsed_)
       set_inner_parsed(*other.inner_parsed_);
     else
@@ -826,40 +933,52 @@
   return len ? Component(begin, len) : Component();
 }
 
+bool ExtractScheme(std::string_view url, Component* scheme) {
+  return DoExtractScheme(url, scheme);
+}
+
+bool ExtractScheme(std::u16string_view url, Component* scheme) {
+  return DoExtractScheme(url, scheme);
+}
+
 bool ExtractScheme(const char* url, int url_len, Component* scheme) {
-  return DoExtractScheme(url, url_len, scheme);
+  return DoExtractScheme(std::string_view(url, url_len), scheme);
 }
 
-bool ExtractScheme(const char16_t* url, int url_len, Component* scheme) {
-  return DoExtractScheme(url, url_len, scheme);
+// This handles everything that may be an authority terminator.
+//
+// URL Standard:
+// https://url.spec.whatwg.org/#authority-state
+// >> 2. Otherwise, if one of the following is true:
+// >>    - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#)
+// >>    - url is special and c is U+005C (\)
+bool IsAuthorityTerminator(char16_t ch, ParserMode parser_mode) {
+  if (parser_mode == ParserMode::kSpecialURL) {
+    return IsSlashOrBackslash(ch) || ch == '?' || ch == '#';
+  }
+  return ch == '/' || ch == '?' || ch == '#';
 }
 
-// This handles everything that may be an authority terminator, including
-// backslash. For special backslash handling see DoParseAfterScheme.
-bool IsAuthorityTerminator(char16_t ch) {
-  return IsURLSlash(ch) || ch == '?' || ch == '#';
-}
-
-void ExtractFileName(const char* url,
+void ExtractFileName(std::string_view url,
                      const Component& path,
                      Component* file_name) {
   DoExtractFileName(url, path, file_name);
 }
 
-void ExtractFileName(const char16_t* url,
+void ExtractFileName(std::u16string_view url,
                      const Component& path,
                      Component* file_name) {
   DoExtractFileName(url, path, file_name);
 }
 
-bool ExtractQueryKeyValue(const char* url,
+bool ExtractQueryKeyValue(std::string_view url,
                           Component* query,
                           Component* key,
                           Component* value) {
   return DoExtractQueryKeyValue(url, query, key, value);
 }
 
-bool ExtractQueryKeyValue(const char16_t* url,
+bool ExtractQueryKeyValue(std::u16string_view url,
                           Component* query,
                           Component* key,
                           Component* value) {
@@ -872,62 +991,109 @@
                     Component* password,
                     Component* hostname,
                     Component* port_num) {
-  DoParseAuthority(spec, auth, username, password, hostname, port_num);
+  size_t length = auth.is_valid() ? auth.end() : 0;
+  DoParseAuthority(std::string_view(spec, length), auth,
+                   ParserMode::kSpecialURL, username, password, hostname,
+                   port_num);
 }
 
-void ParseAuthority(const char16_t* spec,
+void ParseAuthority(std::string_view spec,
                     const Component& auth,
+                    ParserMode parser_mode,
                     Component* username,
                     Component* password,
                     Component* hostname,
                     Component* port_num) {
-  DoParseAuthority(spec, auth, username, password, hostname, port_num);
+  DoParseAuthority(spec, auth, parser_mode, username, password, hostname,
+                   port_num);
+}
+
+void ParseAuthority(std::u16string_view spec,
+                    const Component& auth,
+                    ParserMode parser_mode,
+                    Component* username,
+                    Component* password,
+                    Component* hostname,
+                    Component* port_num) {
+  DoParseAuthority(spec, auth, parser_mode, username, password, hostname,
+                   port_num);
 }
 
 int ParsePort(const char* url, const Component& port) {
+  return port.is_empty()
+             ? PORT_UNSPECIFIED
+             : DoParsePort(
+                   std::string_view(url, static_cast<size_t>(port.end())),
+                   port);
+}
+
+int ParsePort(std::string_view url, const Component& port) {
   return DoParsePort(url, port);
 }
 
-int ParsePort(const char16_t* url, const Component& port) {
+int ParsePort(std::u16string_view url, const Component& port) {
   return DoParsePort(url, port);
 }
 
+Parsed ParseStandardUrl(std::string_view url) {
+  return DoParseStandardUrl(url);
+}
+
+Parsed ParseStandardUrl(std::u16string_view url) {
+  return DoParseStandardUrl(url);
+}
+
 void ParseStandardURL(const char* url, int url_len, Parsed* parsed) {
-  DoParseStandardURL(url, url_len, parsed);
+  GURL_CHECK_GE(url_len, 0);
+  *parsed = DoParseStandardUrl(std::basic_string_view(url, url_len));
 }
 
-void ParseStandardURL(const char16_t* url, int url_len, Parsed* parsed) {
-  DoParseStandardURL(url, url_len, parsed);
+Parsed ParseNonSpecialUrl(std::string_view url) {
+  return DoParseNonSpecialUrl(url, /*trim_path_end=*/true);
+}
+
+Parsed ParseNonSpecialUrl(std::u16string_view url) {
+  return DoParseNonSpecialUrl(url, /*trim_path_end=*/true);
+}
+
+Parsed ParseNonSpecialUrlInternal(std::string_view url, bool trim_path_end) {
+  return DoParseNonSpecialUrl(url, trim_path_end);
+}
+
+Parsed ParseNonSpecialUrlInternal(std::u16string_view url, bool trim_path_end) {
+  return DoParseNonSpecialUrl(url, trim_path_end);
+}
+
+Parsed ParsePathUrl(std::string_view url, bool trim_path_end) {
+  return DoParsePathUrl(url, trim_path_end);
+}
+
+Parsed ParsePathUrl(std::u16string_view url, bool trim_path_end) {
+  return DoParsePathUrl(url, trim_path_end);
 }
 
 void ParsePathURL(const char* url,
                   int url_len,
                   bool trim_path_end,
                   Parsed* parsed) {
-  DoParsePathURL(url, url_len, trim_path_end, parsed);
+  GURL_CHECK_GE(url_len, 0);
+  *parsed = ParsePathUrl(std::string_view(url, url_len), trim_path_end);
 }
 
-void ParsePathURL(const char16_t* url,
-                  int url_len,
-                  bool trim_path_end,
-                  Parsed* parsed) {
-  DoParsePathURL(url, url_len, trim_path_end, parsed);
+Parsed ParseFileSystemUrl(std::string_view url) {
+  return DoParseFileSystemUrl(url);
 }
 
-void ParseFileSystemURL(const char* url, int url_len, Parsed* parsed) {
-  DoParseFileSystemURL(url, url_len, parsed);
+Parsed ParseFileSystemUrl(std::u16string_view url) {
+  return DoParseFileSystemUrl(url);
 }
 
-void ParseFileSystemURL(const char16_t* url, int url_len, Parsed* parsed) {
-  DoParseFileSystemURL(url, url_len, parsed);
+Parsed ParseMailtoUrl(std::string_view url) {
+  return DoParseMailtoUrl(url);
 }
 
-void ParseMailtoURL(const char* url, int url_len, Parsed* parsed) {
-  DoParseMailtoURL(url, url_len, parsed);
-}
-
-void ParseMailtoURL(const char16_t* url, int url_len, Parsed* parsed) {
-  DoParseMailtoURL(url, url_len, parsed);
+Parsed ParseMailtoUrl(std::u16string_view url) {
+  return DoParseMailtoUrl(url);
 }
 
 void ParsePathInternal(const char* spec,
@@ -946,18 +1112,28 @@
   ParsePath(spec, path, filepath, query, ref);
 }
 
-void ParseAfterScheme(const char* spec,
-                      int spec_len,
-                      int after_scheme,
-                      Parsed* parsed) {
-  DoParseAfterScheme(spec, spec_len, after_scheme, parsed);
+void ParseAfterSpecialScheme(std::string_view spec,
+                             int after_scheme,
+                             Parsed* parsed) {
+  DoParseAfterSpecialScheme(spec, after_scheme, parsed);
 }
 
-void ParseAfterScheme(const char16_t* spec,
-                      int spec_len,
-                      int after_scheme,
-                      Parsed* parsed) {
-  DoParseAfterScheme(spec, spec_len, after_scheme, parsed);
+void ParseAfterSpecialScheme(std::u16string_view spec,
+                             int after_scheme,
+                             Parsed* parsed) {
+  DoParseAfterSpecialScheme(spec, after_scheme, parsed);
+}
+
+void ParseAfterNonSpecialScheme(std::string_view spec,
+                                int after_scheme,
+                                Parsed* parsed) {
+  DoParseAfterNonSpecialScheme(spec, after_scheme, parsed);
+}
+
+void ParseAfterNonSpecialScheme(std::u16string_view spec,
+                                int after_scheme,
+                                Parsed* parsed) {
+  DoParseAfterNonSpecialScheme(spec, after_scheme, parsed);
 }
 
 }  // namespace url
diff --git a/url/third_party/mozilla/url_parse.h b/url/third_party/mozilla/url_parse.h
index d44e20a..0e8b942 100644
--- a/url/third_party/mozilla/url_parse.h
+++ b/url/third_party/mozilla/url_parse.h
@@ -6,34 +6,63 @@
 #define URL_THIRD_PARTY_MOZILLA_URL_PARSE_H_
 
 #include <iosfwd>
+#include <optional>
+#include <string>
+#include <string_view>
 
+#include "polyfills/base/check.h"
 #include "polyfills/base/component_export.h"
+#include "base/numerics/safe_conversions.h"
 
 namespace url {
 
+// Represents the different behavior between parsing special URLs
+// (https://url.spec.whatwg.org/#is-special) and parsing URLs which are not
+// special.
+//
+// Examples:
+// - Special URLs: "https://host/path", "ftp://host/path"
+// - Non Special URLs: "about:blank", "data:xxx", "git://host/path"
+enum class ParserMode { kSpecialURL, kNonSpecialURL };
+
 // Component ------------------------------------------------------------------
 
 // Represents a substring for URL parsing.
 struct Component {
-  Component() : begin(0), len(-1) {}
+  constexpr Component() : begin(0), len(-1) {}
 
   // Normal constructor: takes an offset and a length.
-  Component(int b, int l) : begin(b), len(l) {}
+  constexpr Component(int b, int l) : begin(b), len(l) {}
 
-  int end() const {
-    return begin + len;
+  // Construct a Component covering the whole `view`.
+  template <typename CharT>
+  explicit Component(std::basic_string_view<CharT> view)
+      : begin(0), len(gurl_base::checked_cast<int>(view.size())) {}
+
+  // Adjusts the beginning of the component by the given offset. This is useful
+  // for adjusting component offsets when they are relative to a substring
+  // rather than the original string. Crashes if `begin + offset` overflows.
+  void OffsetBy(size_t offset) {
+    size_t new_begin = static_cast<size_t>(begin) + offset;
+    begin = gurl_base::checked_cast<int>(new_begin);
   }
 
+  constexpr int end() const { return begin + len; }
+
+  // Returns the `end` value in size_t. This crashes if this object is
+  // not valid.
+  size_t CheckedEnd() const { return gurl_base::checked_cast<size_t>(end()); }
+
   // Returns true if this component is valid, meaning the length is given.
   // Valid components may be empty to record the fact that they exist.
-  bool is_valid() const { return len >= 0; }
+  constexpr bool is_valid() const { return len >= 0; }
 
   // Determine if the component is empty or not. Empty means the length is
   // zero or the component is invalid.
-  bool is_empty() const { return len <= 0; }
-  bool is_nonempty() const { return len > 0; }
+  constexpr bool is_empty() const { return len <= 0; }
+  constexpr bool is_nonempty() const { return len > 0; }
 
-  void reset() {
+  constexpr void reset() {
     begin = 0;
     len = -1;
   }
@@ -42,6 +71,58 @@
     return begin == other.begin && len == other.len;
   }
 
+  // Returns a string_view using `source` as a backend.
+  template <typename CharT>
+  std::basic_string_view<CharT> as_string_view_on(const CharT* source) const {
+    GURL_DCHECK(is_valid());
+    return std::basic_string_view(&source[begin], static_cast<size_t>(len));
+  }
+
+  // Returns a string_view using `source` as a backend.
+  template <typename CharT>
+  std::basic_string_view<CharT> AsViewOn(
+      std::basic_string_view<CharT> source) const {
+    GURL_DCHECK(is_valid());
+    return source.substr(static_cast<size_t>(begin), static_cast<size_t>(len));
+  }
+
+  // Returns a string_view using `source` as a backend.
+  template <typename CharT>
+  std::basic_string_view<CharT> AsViewOn(
+      const std::basic_string<CharT>& source) const {
+    return AsViewOn(std::basic_string_view<CharT>(source));
+  }
+
+  // Returns a std::optional<string_view> using `source` as a backend.
+  // Returns std::nullopt if the component is invalid.
+  template <typename CharT>
+  std::optional<std::basic_string_view<CharT>> maybe_as_string_view_on(
+      const CharT* source) const {
+    if (!is_valid()) {
+      return std::nullopt;
+    }
+    return std::basic_string_view(&source[begin], len);
+  }
+
+  // Returns a std::optional<string_view> using `source` as a backend.
+  // Returns std::nullopt if the component is invalid.
+  template <typename CharT>
+  std::optional<std::basic_string_view<CharT>> MaybeAsViewOn(
+      std::basic_string_view<CharT> source) const {
+    if (!is_valid()) {
+      return std::nullopt;
+    }
+    return source.substr(static_cast<size_t>(begin), static_cast<size_t>(len));
+  }
+
+  // Returns a std::optional<string_view> using `source` as a backend.
+  // Returns std::nullopt if the component is invalid.
+  template <typename CharT>
+  std::optional<std::basic_string_view<CharT>> MaybeAsViewOn(
+      const std::basic_string<CharT>& source) const {
+    return MaybeAsViewOn(std::basic_string_view<CharT>(source));
+  }
+
   int begin;  // Byte offset in the string of this component.
   int len;    // Will be -1 if the component is unspecified.
 };
@@ -55,6 +136,10 @@
 inline Component MakeRange(int begin, int end) {
   return Component(begin, end - begin);
 }
+// Helper that returns a component created with the given begin and ending
+// points. The ending point is non-inclusive.
+// This function crashes if an argument is greater than INT_MAX.
+COMPONENT_EXPORT(URL) Component MakeRange(size_t begin, size_t end);
 
 // Parsed ---------------------------------------------------------------------
 
@@ -149,6 +234,19 @@
   Component password;
 
   // Host name.
+  //
+  // For non-special URLs, the length will be -1 unless "//" (two consecutive
+  // slashes) follows the scheme part. This corresponds to "url's host is null"
+  // in URL Standard (https://url.spec.whatwg.org/#concept-url-host).
+  //
+  // Examples:
+  // - "git:/path" => The length is -1.
+  //
+  // The length can be 0 for non-special URLs when a host is the empty string,
+  // but not null.
+  //
+  // Examples:
+  // - "git:///path" => The length is 0.
   Component host;
 
   // Port number.
@@ -160,6 +258,10 @@
   // "/asdf". As a result, it is impossible to have a 0 length path, it will
   // be -1 in cases like "http://host?foo".
   // Note that we treat backslashes the same as slashes.
+  //
+  // For non-special URLs which have an empty path, e.g. "git://host", or an
+  // empty opaque path, e.g. "git:", path will be -1. See
+  // https://crbug.com/1416006.
   Component path;
 
   // Stuff between the ? and the # after the path. This does not include the
@@ -184,7 +286,17 @@
   //
   // TODO(mkwst): Link this to something in a spec if
   // https://github.com/whatwg/url/pull/284 lands.
-  bool potentially_dangling_markup;
+  bool potentially_dangling_markup = false;
+
+  // True if the URL has an opaque path. See
+  // https://url.spec.whatwg.org/#url-opaque-path.
+  // Only non-special URLs can have an opaque path.
+  //
+  // Examples: "data:xxx", "custom:opaque path"
+  //
+  // Note: Non-special URLs like "data:/xxx" and "custom://host/path" don't have
+  // an opaque path because '/' (slash) character follows "scheme:" part.
+  bool has_opaque_path = false;
 
   // This is used for nested URL types, currently only filesystem.  If you
   // parse a filesystem URL, the resulting Parsed will have a nested
@@ -209,9 +321,14 @@
   }
 
  private:
-  Parsed* inner_parsed_;  // This object is owned and managed by this struct.
+  // This object is owned and managed by this struct.
+  Parsed* inner_parsed_ = nullptr;
 };
 
+// Permits printing `Parsed` in gtest.
+COMPONENT_EXPORT(URL)
+std::ostream& operator<<(std::ostream& os, const Parsed& parsed);
+
 // Initialization functions ---------------------------------------------------
 //
 // These functions parse the given URL, filling in all of the structure's
@@ -226,47 +343,60 @@
 //
 // The 8-bit versions require UTF-8 encoding.
 
-// StandardURL is for when the scheme is known to be one that has an
-// authority (host) like "http". This function will not handle weird ones
-// like "about:" and "javascript:", or do the right thing for "file:" URLs.
+// ParseStandardUrl is for when the scheme is known, such as "https:", "ftp:".
+// This is defined as "special" in URL Standard.
+// See https://url.spec.whatwg.org/#is-special
+COMPONENT_EXPORT(URL) Parsed ParseStandardUrl(std::string_view url);
+COMPONENT_EXPORT(URL) Parsed ParseStandardUrl(std::u16string_view url);
+// TODO(crbug.com/325408566): Remove once all third-party libraries use the
+// overloads above.
 COMPONENT_EXPORT(URL)
 void ParseStandardURL(const char* url, int url_len, Parsed* parsed);
-COMPONENT_EXPORT(URL)
-void ParseStandardURL(const char16_t* url, int url_len, Parsed* parsed);
+
+// Non-special URL is for when the scheme is not special, such as "about:",
+// "javascript:". See https://url.spec.whatwg.org/#is-not-special
+COMPONENT_EXPORT(URL) Parsed ParseNonSpecialUrl(std::string_view url);
+COMPONENT_EXPORT(URL) Parsed ParseNonSpecialUrl(std::u16string_view url);
 
 // PathURL is for when the scheme is known not to have an authority (host)
 // section but that aren't file URLs either. The scheme is parsed, and
 // everything after the scheme is considered as the path. This is used for
 // things like "about:" and "javascript:"
+//
+// TODO: Replace ParsePathUrl() with ParseNonSpecialUrl(), ensuring it works
+// with the android:// escape hatch introduced in crrev.com/c/5515685.
+COMPONENT_EXPORT(URL)
+Parsed ParsePathUrl(std::string_view url, bool trim_path_end);
+COMPONENT_EXPORT(URL)
+Parsed ParsePathUrl(std::u16string_view url, bool trim_path_end);
+// TODO(crbug.com/325408566): Remove once openscreen starts using
+// ParseNonSpecialUrl(), now that kStandardCompliantNonSpecialSchemeURLParsing
+// has been launched. This is non-trivial because it involves:
+//
+// 1. Adding ParseNonSpecialUrl() into
+// https://quiche.googlesource.com/googleurl/+/refs/heads/master/url/third_party/mozilla/url_parse.h.
+// 2. Rolling quiche into openscreen, and making the change in openscreen to use
+// ParseNonSpecialUrl() instead of ParsePathURL().
+// 3. Removing all traces of ParsePathURL() from
+// url/third_party/mozilla/url_parse here in chromium.
 COMPONENT_EXPORT(URL)
 void ParsePathURL(const char* url,
                   int url_len,
                   bool trim_path_end,
                   Parsed* parsed);
-COMPONENT_EXPORT(URL)
-void ParsePathURL(const char16_t* url,
-                  int url_len,
-                  bool trim_path_end,
-                  Parsed* parsed);
 
-// FileURL is for file URLs. There are some special rules for interpreting
+// ParseFileUrl is for file URLs. There are some special rules for interpreting
 // these.
-COMPONENT_EXPORT(URL)
-void ParseFileURL(const char* url, int url_len, Parsed* parsed);
-COMPONENT_EXPORT(URL)
-void ParseFileURL(const char16_t* url, int url_len, Parsed* parsed);
+COMPONENT_EXPORT(URL) Parsed ParseFileUrl(std::string_view url);
+COMPONENT_EXPORT(URL) Parsed ParseFileUrl(std::u16string_view url);
 
 // Filesystem URLs are structured differently than other URLs.
-COMPONENT_EXPORT(URL)
-void ParseFileSystemURL(const char* url, int url_len, Parsed* parsed);
-COMPONENT_EXPORT(URL)
-void ParseFileSystemURL(const char16_t* url, int url_len, Parsed* parsed);
+COMPONENT_EXPORT(URL) Parsed ParseFileSystemUrl(std::string_view url);
+COMPONENT_EXPORT(URL) Parsed ParseFileSystemUrl(std::u16string_view url);
 
-// MailtoURL is for mailto: urls. They are made up scheme,path,query
-COMPONENT_EXPORT(URL)
-void ParseMailtoURL(const char* url, int url_len, Parsed* parsed);
-COMPONENT_EXPORT(URL)
-void ParseMailtoURL(const char16_t* url, int url_len, Parsed* parsed);
+// ParseMailtoUrl is for mailto: urls. They are made up scheme,path,query
+COMPONENT_EXPORT(URL) Parsed ParseMailtoUrl(std::string_view url);
+COMPONENT_EXPORT(URL) Parsed ParseMailtoUrl(std::u16string_view url);
 
 // Helper functions -----------------------------------------------------------
 
@@ -291,16 +421,22 @@
 //
 // The 8-bit version requires UTF-8 encoding.
 COMPONENT_EXPORT(URL)
-bool ExtractScheme(const char* url, int url_len, Component* scheme);
+bool ExtractScheme(std::string_view url, Component* scheme);
 COMPONENT_EXPORT(URL)
-bool ExtractScheme(const char16_t* url, int url_len, Component* scheme);
+bool ExtractScheme(std::u16string_view url, Component* scheme);
+// Deprecated (crbug.com/325408566): Prefer using the overloads above.
+COMPONENT_EXPORT(URL)
+bool ExtractScheme(const char* url, int url_len, Component* scheme);
 
 // Returns true if ch is a character that terminates the authority segment
 // of a URL.
-COMPONENT_EXPORT(URL) bool IsAuthorityTerminator(char16_t ch);
+COMPONENT_EXPORT(URL)
+bool IsAuthorityTerminator(char16_t ch, ParserMode parser_mode);
 
-// Does a best effort parse of input |spec|, in range |auth|. If a particular
-// component is not found, it will be set to invalid.
+// Deprecated. Please pass `ParserMode` explicitly.
+//
+// These functions are also used in net/third_party code. So removing these
+// functions requires several steps.
 COMPONENT_EXPORT(URL)
 void ParseAuthority(const char* spec,
                     const Component& auth,
@@ -308,9 +444,23 @@
                     Component* password,
                     Component* hostname,
                     Component* port_num);
+
+// Does a best effort parse of input `spec`, in range `auth`. If a particular
+// component is not found, it will be set to invalid. `ParserMode` is used to
+// determine the appropriate authority terminator. See `IsAuthorityTerminator`
+// for details.
 COMPONENT_EXPORT(URL)
-void ParseAuthority(const char16_t* spec,
+void ParseAuthority(std::string_view spec,
                     const Component& auth,
+                    ParserMode parser_mode,
+                    Component* username,
+                    Component* password,
+                    Component* hostname,
+                    Component* port_num);
+COMPONENT_EXPORT(URL)
+void ParseAuthority(std::u16string_view spec,
+                    const Component& auth,
+                    ParserMode parser_mode,
                     Component* username,
                     Component* password,
                     Component* hostname,
@@ -322,10 +472,15 @@
 //
 // The return value will be a positive integer between 0 and 64K, or one of
 // the two special values below.
+//
+// Overloads for `const char*` and `const char16_t*` are deprecated. Use an
+// overload for `std::string_view` or `std::u16string_view` instead.
 enum SpecialPort { PORT_UNSPECIFIED = -1, PORT_INVALID = -2 };
 COMPONENT_EXPORT(URL) int ParsePort(const char* url, const Component& port);
 COMPONENT_EXPORT(URL)
-int ParsePort(const char16_t* url, const Component& port);
+int ParsePort(std::string_view url, const Component& port);
+COMPONENT_EXPORT(URL)
+int ParsePort(std::u16string_view url, const Component& port);
 
 // Extracts the range of the file name in the given url. The path must
 // already have been computed by the parse function, and the matching URL
@@ -338,11 +493,11 @@
 //
 // The 8-bit version requires UTF-8 encoding.
 COMPONENT_EXPORT(URL)
-void ExtractFileName(const char* url,
+void ExtractFileName(std::string_view url,
                      const Component& path,
                      Component* file_name);
 COMPONENT_EXPORT(URL)
-void ExtractFileName(const char16_t* url,
+void ExtractFileName(std::u16string_view url,
                      const Component& path,
                      Component* file_name);
 
@@ -361,13 +516,14 @@
 //
 // If no key/value are found |*key| and |*value| will be unchanged and it will
 // return false.
+
 COMPONENT_EXPORT(URL)
-bool ExtractQueryKeyValue(const char* url,
+bool ExtractQueryKeyValue(std::string_view url,
                           Component* query,
                           Component* key,
                           Component* value);
 COMPONENT_EXPORT(URL)
-bool ExtractQueryKeyValue(const char16_t* url,
+bool ExtractQueryKeyValue(std::u16string_view url,
                           Component* query,
                           Component* key,
                           Component* value);
diff --git a/url/url_canon.h b/url/url_canon.h
index 0af495a..bc40cab 100644
--- a/url/url_canon.h
+++ b/url/url_canon.h
@@ -2,23 +2,48 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #ifndef URL_URL_CANON_H_
 #define URL_URL_CANON_H_
 
 #include <stdlib.h>
 #include <string.h>
 
+#include <array>
+#include <optional>
 #include <string_view>
 
+#include "polyfills/base/check_op.h"
+#include "base/compiler_specific.h"
 #include "polyfills/base/component_export.h"
+#include "base/containers/span.h"
 #include "polyfills/base/export_template.h"
 #include "polyfills/base/memory/raw_ptr_exclusion.h"
+#include "base/memory/stack_allocated.h"
 #include "base/numerics/clamped_math.h"
 #include "url/third_party/mozilla/url_parse.h"
 
 namespace url {
 
-// Canonicalizer output -------------------------------------------------------
+// Represents the different behavior between canonicalizing special URLs
+// (https://url.spec.whatwg.org/#is-special) and canonicalizing URLs which are
+// not special.
+//
+// Examples:
+// - Special URLs: "https://host/path", "ftp://host/path"
+// - Non Special URLs: "about:blank", "data:xxx", "git://host/path"
+//
+// kFileURL (file://) is a special case of kSpecialURL that allows space
+// characters but otherwise behaves identically to kSpecialURL.
+// See crbug.com/40256677
+enum class CanonMode { kSpecialURL, kNonSpecialURL, kFileURL };
+
+// Canonicalizer output
+// -------------------------------------------------------
 
 // Base class for the canonicalizer output, this maintains a buffer and
 // supports simple resizing and append operations on it.
@@ -117,6 +142,16 @@
       Resize((gurl_base::ClampedNumeric<size_t>(estimated_size) + 8).RawValue());
   }
 
+  // Insert `str` at `pos`. Used for post-processing non-special URL's pathname.
+  // Since this takes O(N), don't use this unless there is a strong reason.
+  void Insert(size_t pos, std::basic_string_view<T> str) {
+    GURL_DCHECK_LE(pos, cur_len_);
+    std::basic_string<T> copy(view().substr(pos));
+    set_length(pos);
+    Append(str);
+    Append(copy);
+  }
+
  protected:
   // Grows the given buffer so that it can fit at least |min_additional|
   // characters. Returns true if the buffer could be resized, false on OOM.
@@ -132,8 +167,8 @@
     return true;
   }
 
-  // `buffer_` is not a raw_ptr<...> for performance reasons (based on analysis
-  // of sampling profiler data).
+  // RAW_PTR_EXCLUSION: Performance (based on analysis of sampling profiler
+  // data).
   RAW_PTR_EXCLUSION T* buffer_ = nullptr;
   size_t buffer_len_ = 0;
 
@@ -211,8 +246,7 @@
   // decimal, (such as "&#20320;") with escaping of the ampersand, number
   // sign, and semicolon (in the previous example it would be
   // "%26%2320320%3B"). This rule is based on what IE does in this situation.
-  virtual void ConvertFromUTF16(const char16_t* input,
-                                int input_len,
+  virtual void ConvertFromUTF16(std::u16string_view input,
                                 CanonOutput* output) = 0;
 };
 
@@ -245,35 +279,31 @@
 
 // Searches for whitespace that should be removed from the middle of URLs, and
 // removes it. Removed whitespace are tabs and newlines, but NOT spaces. Spaces
-// are preserved, which is what most browsers do. A pointer to the output will
-// be returned, and the length of that output will be in |output_len|.
+// are preserved, which is what most browsers do. A string_view pointing to the
+// output will be returned.
 //
 // This should be called before parsing if whitespace removal is desired (which
 // it normally is when you are canonicalizing).
 //
 // If no whitespace is removed, this function will not use the buffer and will
-// return a pointer to the input, to avoid the extra copy. If modification is
-// required, the given |buffer| will be used and the returned pointer will
+// return the input string_view, to avoid the extra copy. If modification is
+// required, the given |buffer| will be used and the returned string_view will
 // point to the beginning of the buffer.
 //
 // Therefore, callers should not use the buffer, since it may actually be empty,
-// use the computed pointer and |*output_len| instead.
+// use the returned string_view instead.
 //
 // If |input| contained both removable whitespace and a raw `<` character,
 // |potentially_dangling_markup| will be set to `true`. Otherwise, it will be
 // left untouched.
 COMPONENT_EXPORT(URL)
-const char* RemoveURLWhitespace(const char* input,
-                                int input_len,
-                                CanonOutputT<char>* buffer,
-                                int* output_len,
-                                bool* potentially_dangling_markup);
+std::string_view RemoveUrlWhitespace(std::string_view input,
+                                     CanonOutputT<char>* buffer,
+                                     bool* potentially_dangling_markup);
 COMPONENT_EXPORT(URL)
-const char16_t* RemoveURLWhitespace(const char16_t* input,
-                                    int input_len,
-                                    CanonOutputT<char16_t>* buffer,
-                                    int* output_len,
-                                    bool* potentially_dangling_markup);
+std::u16string_view RemoveUrlWhitespace(std::u16string_view input,
+                                        CanonOutputT<char16_t>* buffer,
+                                        bool* potentially_dangling_markup);
 
 // IDN ------------------------------------------------------------------------
 
@@ -314,13 +344,11 @@
 //
 // The 8-bit version requires UTF-8 encoding.
 COMPONENT_EXPORT(URL)
-bool CanonicalizeScheme(const char* spec,
-                        const Component& scheme,
+bool CanonicalizeScheme(std::optional<std::string_view> input,
                         CanonOutput* output,
                         Component* out_scheme);
 COMPONENT_EXPORT(URL)
-bool CanonicalizeScheme(const char16_t* spec,
-                        const Component& scheme,
+bool CanonicalizeScheme(std::optional<std::u16string_view> input,
                         CanonOutput* output,
                         Component* out_scheme);
 
@@ -335,18 +363,14 @@
 //
 // The 8-bit version requires UTF-8 encoding.
 COMPONENT_EXPORT(URL)
-bool CanonicalizeUserInfo(const char* username_source,
-                          const Component& username,
-                          const char* password_source,
-                          const Component& password,
+bool CanonicalizeUserInfo(std::optional<std::string_view> username,
+                          std::optional<std::string_view> password,
                           CanonOutput* output,
                           Component* out_username,
                           Component* out_password);
 COMPONENT_EXPORT(URL)
-bool CanonicalizeUserInfo(const char16_t* username_source,
-                          const Component& username,
-                          const char16_t* password_source,
-                          const Component& password,
+bool CanonicalizeUserInfo(std::optional<std::u16string_view> username,
+                          std::optional<std::u16string_view> password,
                           CanonOutput* output,
                           Component* out_username,
                           Component* out_password);
@@ -387,44 +411,115 @@
   // |address| contains the parsed IP Address (if any) in its first
   // AddressLength() bytes, in network order. If IsIPAddress() is false
   // AddressLength() will return zero and the content of |address| is undefined.
-  unsigned char address[16];
+  std::array<uint8_t, 16> address;
 
   // Convenience function to calculate the length of an IP address corresponding
   // to the current IP version in |family|, if any. For use with |address|.
   int AddressLength() const {
     return family == IPV4 ? 4 : (family == IPV6 ? 16 : 0);
   }
+
+  // Returns a span pointing a valid range of `address`. The size of the
+  // resultant span is 4, 16, or 0.
+  gurl_base::span<const uint8_t> AddressSpan() const LIFETIME_BOUND {
+    return gurl_base::span(address).first(static_cast<size_t>(AddressLength()));
+  }
 };
 
-// Host.
+// Deprecated. Please call either CanonicalizeSpecialHost or
+// CanonicalizeNonSpecialHost.
+//
+// TODO(crbug.com/40063064): Check the callers of these functions.
+COMPONENT_EXPORT(URL)
+bool CanonicalizeHost(std::string_view spec,
+                      const Component& host,
+                      CanonOutput* output,
+                      Component* out_host);
+COMPONENT_EXPORT(URL)
+bool CanonicalizeHost(std::u16string_view spec,
+                      const Component& host,
+                      CanonOutput* output,
+                      Component* out_host);
+
+// Host in special URLs.
 //
 // The 8-bit version requires UTF-8 encoding. Use this version when you only
 // need to know whether canonicalization succeeded.
 COMPONENT_EXPORT(URL)
-bool CanonicalizeHost(const char* spec,
-                      const Component& host,
-                      CanonOutput* output,
-                      Component* out_host);
+bool CanonicalizeSpecialHost(std::string_view spec,
+                             const Component& host,
+                             CanonOutput& output,
+                             Component& out_host);
 COMPONENT_EXPORT(URL)
-bool CanonicalizeHost(const char16_t* spec,
-                      const Component& host,
-                      CanonOutput* output,
-                      Component* out_host);
+bool CanonicalizeSpecialHost(std::u16string_view spec,
+                             const Component& host,
+                             CanonOutput& output,
+                             Component& out_host);
 
-// Extended version of CanonicalizeHost, which returns additional information.
-// Use this when you need to know whether the hostname was an IP address.
-// A successful return is indicated by host_info->family != BROKEN. See the
-// definition of CanonHostInfo above for details.
+// Host in special URLs.
+//
+// The 8-bit version requires UTF-8 encoding. Use this version when you only
+// need to know whether canonicalization succeeded.
+COMPONENT_EXPORT(URL)
+bool CanonicalizeFileHost(std::string_view spec,
+                          const Component& host,
+                          CanonOutput& output,
+                          Component& out_host);
+COMPONENT_EXPORT(URL)
+bool CanonicalizeFileHost(std::u16string_view spec,
+                          const Component& host,
+                          CanonOutput& output,
+                          Component& out_host);
+
+// Deprecated. Please call either CanonicalizeSpecialHostVerbose or
+// CanonicalizeNonSpecialHostVerbose.
+//
+// TODO(crbug.com/40063064): Check the callers of these functions.
 COMPONENT_EXPORT(URL)
 void CanonicalizeHostVerbose(const char* spec,
                              const Component& host,
                              CanonOutput* output,
                              CanonHostInfo* host_info);
 COMPONENT_EXPORT(URL)
-void CanonicalizeHostVerbose(const char16_t* spec,
+void CanonicalizeHostVerbose(std::string_view spec,
                              const Component& host,
                              CanonOutput* output,
                              CanonHostInfo* host_info);
+COMPONENT_EXPORT(URL)
+void CanonicalizeHostVerbose(std::u16string_view spec,
+                             const Component& host,
+                             CanonOutput* output,
+                             CanonHostInfo* host_info);
+
+// Extended version of CanonicalizeSpecialHost, which returns additional
+// information. Use this when you need to know whether the hostname was an IP
+// address. A successful return is indicated by host_info->family != BROKEN. See
+// the definition of CanonHostInfo above for details.
+COMPONENT_EXPORT(URL)
+void CanonicalizeSpecialHostVerbose(std::string_view spec,
+                                    const Component& host,
+                                    CanonOutput& output,
+                                    CanonHostInfo& host_info);
+COMPONENT_EXPORT(URL)
+void CanonicalizeSpecialHostVerbose(std::u16string_view spec,
+                                    const Component& host,
+                                    CanonOutput& output,
+                                    CanonHostInfo& host_info);
+
+// Extended version of CanonicalizeFileHost, which returns additional
+// information. Use this when you need to know whether the hostname was an IP
+// address. A successful return is indicated by host_info->family != BROKEN. See
+// the definition of CanonHostInfo above for details.
+COMPONENT_EXPORT(URL)
+void CanonicalizeFileHostVerbose(std::string_view spec,
+                                 const Component& host,
+                                 CanonOutput& output,
+                                 CanonHostInfo& host_info);
+COMPONENT_EXPORT(URL)
+void CanonicalizeFileHostVerbose(std::u16string_view spec,
+                                 const Component& host,
+                                 CanonOutput& output,
+                                 CanonHostInfo& host_info);
 
 // Canonicalizes a string according to the host canonicalization rules. Unlike
 // CanonicalizeHost, this will not check for IP addresses which can change the
@@ -447,14 +542,36 @@
 // host as valid (because it's designed to be used for substrings) while the
 // full version above will mark empty hosts as broken.
 COMPONENT_EXPORT(URL)
-bool CanonicalizeHostSubstring(const char* spec,
-                               const Component& host,
-                               CanonOutput* output);
+bool CanonicalizeHostSubstring(std::string_view host_view, CanonOutput* output);
 COMPONENT_EXPORT(URL)
-bool CanonicalizeHostSubstring(const char16_t* spec,
-                               const Component& host,
+bool CanonicalizeHostSubstring(std::u16string_view host_view,
                                CanonOutput* output);
 
+// Host in non-special URLs.
+COMPONENT_EXPORT(URL)
+bool CanonicalizeNonSpecialHost(std::string_view spec,
+                                const Component& host,
+                                CanonOutput& output,
+                                Component& out_host);
+COMPONENT_EXPORT(URL)
+bool CanonicalizeNonSpecialHost(std::u16string_view spec,
+                                const Component& host,
+                                CanonOutput& output,
+                                Component& out_host);
+
+// Extended version of CanonicalizeNonSpecialHost, which returns additional
+// information. See CanonicalizeSpecialHost for details.
+COMPONENT_EXPORT(URL)
+void CanonicalizeNonSpecialHostVerbose(std::string_view spec,
+                                       const Component& host,
+                                       CanonOutput& output,
+                                       CanonHostInfo& host_info);
+COMPONENT_EXPORT(URL)
+void CanonicalizeNonSpecialHostVerbose(std::u16string_view spec,
+                                       const Component& host,
+                                       CanonOutput& output,
+                                       CanonHostInfo& host_info);
+
 // IP addresses.
 //
 // Tries to interpret the given host name as an IPv4 or IPv6 address. If it is
@@ -466,30 +583,37 @@
 // the input is unescaped and name-prepped, etc. It should not normally be
 // necessary or wise to call this directly.
 COMPONENT_EXPORT(URL)
-void CanonicalizeIPAddress(const char* spec,
-                           const Component& host,
+void CanonicalizeIPAddress(std::string_view host_view,
                            CanonOutput* output,
                            CanonHostInfo* host_info);
 COMPONENT_EXPORT(URL)
-void CanonicalizeIPAddress(const char16_t* spec,
-                           const Component& host,
+void CanonicalizeIPAddress(std::u16string_view host_view,
                            CanonOutput* output,
                            CanonHostInfo* host_info);
 
+// Similar to CanonicalizeIPAddress, but supports only IPv6 address.
+COMPONENT_EXPORT(URL)
+void CanonicalizeIPv6Address(std::string_view host_view,
+                             CanonOutput& output,
+                             CanonHostInfo& host_info);
+
+COMPONENT_EXPORT(URL)
+void CanonicalizeIPv6Address(std::string_view host_view,
+                             CanonOutput& output,
+                             CanonHostInfo& host_info);
+
 // Port: this function will add the colon for the port if a port is present.
 // The caller can pass PORT_UNSPECIFIED as the
 // default_port_for_scheme argument if there is no default port.
 //
 // The 8-bit version requires UTF-8 encoding.
 COMPONENT_EXPORT(URL)
-bool CanonicalizePort(const char* spec,
-                      const Component& port,
+bool CanonicalizePort(std::optional<std::string_view> port_view,
                       int default_port_for_scheme,
                       CanonOutput* output,
                       Component* out_port);
 COMPONENT_EXPORT(URL)
-bool CanonicalizePort(const char16_t* spec,
-                      const Component& port,
+bool CanonicalizePort(std::optional<std::u16string_view> port_view,
                       int default_port_for_scheme,
                       CanonOutput* output,
                       Component* out_port);
@@ -497,7 +621,7 @@
 // Returns the default port for the given canonical scheme, or PORT_UNSPECIFIED
 // if the scheme is unknown. Based on https://url.spec.whatwg.org/#default-port
 COMPONENT_EXPORT(URL)
-int DefaultPortForScheme(const char* scheme, int scheme_len);
+int DefaultPortForScheme(std::string_view scheme);
 
 // Path. If the input does not begin in a slash (including if the input is
 // empty), we'll prepend a slash to the path to make it canonical.
@@ -509,26 +633,44 @@
 // the path that the server expects (we'll escape high-bit characters), so
 // if something is invalid, it's their problem.
 COMPONENT_EXPORT(URL)
+bool CanonicalizePath(std::optional<std::string_view> path,
+                      CanonMode canon_mode,
+                      CanonOutput* output,
+                      Component* out_path);
+COMPONENT_EXPORT(URL)
+bool CanonicalizePath(std::optional<std::u16string_view> path,
+                      CanonMode canon_mode,
+                      CanonOutput* output,
+                      Component* out_path);
+
+// Deprecated. Please pass CanonMode explicitly.
+//
+// These functions are also used in net/third_party code. So removing these
+// functions requires several steps.
+// TODO(crbug.com/422740114): Remove this after `//net/third_party/quiche` is
+// not depending on it.
+COMPONENT_EXPORT(URL)
 bool CanonicalizePath(const char* spec,
                       const Component& path,
                       CanonOutput* output,
                       Component* out_path);
 COMPONENT_EXPORT(URL)
-bool CanonicalizePath(const char16_t* spec,
-                      const Component& path,
+bool CanonicalizePath(std::optional<std::string_view> path,
+                      CanonOutput* output,
+                      Component* out_path);
+COMPONENT_EXPORT(URL)
+bool CanonicalizePath(std::optional<std::u16string_view> path,
                       CanonOutput* output,
                       Component* out_path);
 
 // Like CanonicalizePath(), but does not assume that its operating on the
 // entire path.  It therefore does not prepend a slash, etc.
 COMPONENT_EXPORT(URL)
-bool CanonicalizePartialPath(const char* spec,
-                             const Component& path,
+bool CanonicalizePartialPath(std::optional<std::string_view> path,
                              CanonOutput* output,
                              Component* out_path);
 COMPONENT_EXPORT(URL)
-bool CanonicalizePartialPath(const char16_t* spec,
-                             const Component& path,
+bool CanonicalizePartialPath(std::optional<std::u16string_view> path,
                              CanonOutput* output,
                              Component* out_path);
 
@@ -539,13 +681,11 @@
 //
 // The 8-bit version requires UTF-8 encoding.
 COMPONENT_EXPORT(URL)
-bool FileCanonicalizePath(const char* spec,
-                          const Component& path,
+bool FileCanonicalizePath(std::optional<std::string_view> path,
                           CanonOutput* output,
                           Component* out_path);
 COMPONENT_EXPORT(URL)
-bool FileCanonicalizePath(const char16_t* spec,
-                          const Component& path,
+bool FileCanonicalizePath(std::optional<std::u16string_view> path,
                           CanonOutput* output,
                           Component* out_path);
 
@@ -562,14 +702,12 @@
 //
 // The converter can be NULL. In this case, the output encoding will be UTF-8.
 COMPONENT_EXPORT(URL)
-void CanonicalizeQuery(const char* spec,
-                       const Component& query,
+void CanonicalizeQuery(std::optional<std::string_view> input,
                        CharsetConverter* converter,
                        CanonOutput* output,
                        Component* out_query);
 COMPONENT_EXPORT(URL)
-void CanonicalizeQuery(const char16_t* spec,
-                       const Component& query,
+void CanonicalizeQuery(std::optional<std::u16string_view> input,
                        CharsetConverter* converter,
                        CanonOutput* output,
                        Component* out_query);
@@ -581,13 +719,11 @@
 // This function will not fail. If the input is invalid UTF-8/UTF-16, we'll use
 // the "Unicode replacement character" for the confusing bits and copy the rest.
 COMPONENT_EXPORT(URL)
-void CanonicalizeRef(const char* spec,
-                     const Component& path,
+void CanonicalizeRef(std::optional<std::string_view> spec,
                      CanonOutput* output,
                      Component* out_path);
 COMPONENT_EXPORT(URL)
-void CanonicalizeRef(const char16_t* spec,
-                     const Component& path,
+void CanonicalizeRef(std::optional<std::u16string_view> spec,
                      CanonOutput* output,
                      Component* out_path);
 
@@ -603,33 +739,43 @@
 
 // Use for standard URLs with authorities and paths.
 COMPONENT_EXPORT(URL)
-bool CanonicalizeStandardURL(const char* spec,
-                             int spec_len,
+bool CanonicalizeStandardUrl(std::string_view spec,
                              const Parsed& parsed,
                              SchemeType scheme_type,
                              CharsetConverter* query_converter,
                              CanonOutput* output,
                              Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool CanonicalizeStandardURL(const char16_t* spec,
-                             int spec_len,
+bool CanonicalizeStandardUrl(std::u16string_view spec,
                              const Parsed& parsed,
                              SchemeType scheme_type,
                              CharsetConverter* query_converter,
                              CanonOutput* output,
                              Parsed* new_parsed);
 
+// Use for non-special URLs.
+COMPONENT_EXPORT(URL)
+bool CanonicalizeNonSpecialUrl(std::string_view spec,
+                               const Parsed& parsed,
+                               CharsetConverter* query_converter,
+                               CanonOutput& output,
+                               Parsed& new_parsed);
+COMPONENT_EXPORT(URL)
+bool CanonicalizeNonSpecialUrl(std::u16string_view spec,
+                               const Parsed& parsed,
+                               CharsetConverter* query_converter,
+                               CanonOutput& output,
+                               Parsed& new_parsed);
+
 // Use for file URLs.
 COMPONENT_EXPORT(URL)
-bool CanonicalizeFileURL(const char* spec,
-                         int spec_len,
+bool CanonicalizeFileUrl(std::string_view spec,
                          const Parsed& parsed,
                          CharsetConverter* query_converter,
                          CanonOutput* output,
                          Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool CanonicalizeFileURL(const char16_t* spec,
-                         int spec_len,
+bool CanonicalizeFileUrl(std::u16string_view spec,
                          const Parsed& parsed,
                          CharsetConverter* query_converter,
                          CanonOutput* output,
@@ -637,15 +783,13 @@
 
 // Use for filesystem URLs.
 COMPONENT_EXPORT(URL)
-bool CanonicalizeFileSystemURL(const char* spec,
-                               int spec_len,
+bool CanonicalizeFileSystemUrl(std::string_view spec,
                                const Parsed& parsed,
                                CharsetConverter* query_converter,
                                CanonOutput* output,
                                Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool CanonicalizeFileSystemURL(const char16_t* spec,
-                               int spec_len,
+bool CanonicalizeFileSystemUrl(std::u16string_view spec,
                                const Parsed& parsed,
                                CharsetConverter* query_converter,
                                CanonOutput* output,
@@ -654,14 +798,12 @@
 // Use for path URLs such as javascript. This does not modify the path in any
 // way, for example, by escaping it.
 COMPONENT_EXPORT(URL)
-bool CanonicalizePathURL(const char* spec,
-                         int spec_len,
+bool CanonicalizePathUrl(std::string_view spec,
                          const Parsed& parsed,
                          CanonOutput* output,
                          Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool CanonicalizePathURL(const char16_t* spec,
-                         int spec_len,
+bool CanonicalizePathUrl(std::u16string_view spec,
                          const Parsed& parsed,
                          CanonOutput* output,
                          Parsed* new_parsed);
@@ -669,13 +811,11 @@
 // Use to canonicalize just the path component of a "path" URL; e.g. the
 // path of a javascript URL.
 COMPONENT_EXPORT(URL)
-void CanonicalizePathURLPath(const char* source,
-                             const Component& component,
+void CanonicalizePathUrlPath(std::optional<std::string_view> source,
                              CanonOutput* output,
                              Component* new_component);
 COMPONENT_EXPORT(URL)
-void CanonicalizePathURLPath(const char16_t* source,
-                             const Component& component,
+void CanonicalizePathUrlPath(std::optional<std::u16string_view> source,
                              CanonOutput* output,
                              Component* new_component);
 
@@ -685,14 +825,12 @@
 // really intended for an external mail program, and the encoding of a page,
 // etc. which would influence a query encoding normally are irrelevant.
 COMPONENT_EXPORT(URL)
-bool CanonicalizeMailtoURL(const char* spec,
-                           int spec_len,
+bool CanonicalizeMailtoUrl(std::string_view spec,
                            const Parsed& parsed,
                            CanonOutput* output,
                            Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool CanonicalizeMailtoURL(const char16_t* spec,
-                           int spec_len,
+bool CanonicalizeMailtoUrl(std::u16string_view spec,
                            const Parsed& parsed,
                            CanonOutput* output,
                            Parsed* new_parsed);
@@ -714,6 +852,9 @@
 // modified.
 template <typename CHAR>
 struct URLComponentSource {
+  STACK_ALLOCATED();
+
+ public:
   // Constructor normally used by callers wishing to replace components. This
   // will make them all NULL, which is no replacement. The caller would then
   // override the components they want to replace.
@@ -739,30 +880,14 @@
         query(default_value),
         ref(default_value) {}
 
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const CHAR* scheme;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const CHAR* username;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const CHAR* password;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const CHAR* host;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const CHAR* port;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const CHAR* path;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const CHAR* query;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const CHAR* ref;
+  const CHAR* scheme;
+  const CHAR* username;
+  const CHAR* password;
+  const CHAR* host;
+  const CHAR* port;
+  const CHAR* path;
+  const CHAR* query;
+  const CHAR* ref;
 };
 
 // This structure encapsulates information on modifying a URL. Each component
@@ -894,7 +1019,7 @@
 
 // The base must be an 8-bit canonical URL.
 COMPONENT_EXPORT(URL)
-bool ReplaceStandardURL(const char* base,
+bool ReplaceStandardUrl(std::string_view base,
                         const Parsed& base_parsed,
                         const Replacements<char>& replacements,
                         SchemeType scheme_type,
@@ -902,7 +1027,7 @@
                         CanonOutput* output,
                         Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool ReplaceStandardURL(const char* base,
+bool ReplaceStandardUrl(std::string_view base,
                         const Parsed& base_parsed,
                         const Replacements<char16_t>& replacements,
                         SchemeType scheme_type,
@@ -910,17 +1035,33 @@
                         CanonOutput* output,
                         Parsed* new_parsed);
 
+// For non-special URLs.
+COMPONENT_EXPORT(URL)
+bool ReplaceNonSpecialUrl(std::string_view base,
+                          const Parsed& base_parsed,
+                          const Replacements<char>& replacements,
+                          CharsetConverter* query_converter,
+                          CanonOutput& output,
+                          Parsed& new_parsed);
+COMPONENT_EXPORT(URL)
+bool ReplaceNonSpecialUrl(std::string_view base,
+                          const Parsed& base_parsed,
+                          const Replacements<char16_t>& replacements,
+                          CharsetConverter* query_converter,
+                          CanonOutput& output,
+                          Parsed& new_parsed);
+
 // Filesystem URLs can only have the path, query, or ref replaced.
 // All other components will be ignored.
 COMPONENT_EXPORT(URL)
-bool ReplaceFileSystemURL(const char* base,
+bool ReplaceFileSystemUrl(std::string_view base,
                           const Parsed& base_parsed,
                           const Replacements<char>& replacements,
                           CharsetConverter* query_converter,
                           CanonOutput* output,
                           Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool ReplaceFileSystemURL(const char* base,
+bool ReplaceFileSystemUrl(std::string_view base,
                           const Parsed& base_parsed,
                           const Replacements<char16_t>& replacements,
                           CharsetConverter* query_converter,
@@ -930,14 +1071,14 @@
 // Replacing some parts of a file URL is not permitted. Everything except
 // the host, path, query, and ref will be ignored.
 COMPONENT_EXPORT(URL)
-bool ReplaceFileURL(const char* base,
+bool ReplaceFileUrl(std::string_view base,
                     const Parsed& base_parsed,
                     const Replacements<char>& replacements,
                     CharsetConverter* query_converter,
                     CanonOutput* output,
                     Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool ReplaceFileURL(const char* base,
+bool ReplaceFileUrl(std::string_view base,
                     const Parsed& base_parsed,
                     const Replacements<char16_t>& replacements,
                     CharsetConverter* query_converter,
@@ -947,13 +1088,13 @@
 // Path URLs can only have the scheme and path replaced. All other components
 // will be ignored.
 COMPONENT_EXPORT(URL)
-bool ReplacePathURL(const char* base,
+bool ReplacePathUrl(std::string_view base,
                     const Parsed& base_parsed,
                     const Replacements<char>& replacements,
                     CanonOutput* output,
                     Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool ReplacePathURL(const char* base,
+bool ReplacePathUrl(std::string_view base,
                     const Parsed& base_parsed,
                     const Replacements<char16_t>& replacements,
                     CanonOutput* output,
@@ -962,13 +1103,13 @@
 // Mailto URLs can only have the scheme, path, and query replaced.
 // All other components will be ignored.
 COMPONENT_EXPORT(URL)
-bool ReplaceMailtoURL(const char* base,
+bool ReplaceMailtoUrl(std::string_view base,
                       const Parsed& base_parsed,
                       const Replacements<char>& replacements,
                       CanonOutput* output,
                       Parsed* new_parsed);
 COMPONENT_EXPORT(URL)
-bool ReplaceMailtoURL(const char* base,
+bool ReplaceMailtoUrl(std::string_view base,
                       const Parsed& base_parsed,
                       const Replacements<char16_t>& replacements,
                       CanonOutput* output,
@@ -980,7 +1121,7 @@
 // relative or absolute URL and places the result into |*is_relative|. If it is
 // relative, the relevant portion of the URL will be placed into
 // |*relative_component| (there may have been trimmed whitespace, for example).
-// This value is passed to ResolveRelativeURL. If the input is not relative,
+// This value is passed to ResolveRelativeUrl. If the input is not relative,
 // this value is UNDEFINED (it may be changed by the function).
 //
 // Returns true on success (we successfully determined the URL is relative or
@@ -988,25 +1129,23 @@
 //
 // The base URL should always be canonical, therefore is ASCII.
 COMPONENT_EXPORT(URL)
-bool IsRelativeURL(const char* base,
+bool IsRelativeUrl(std::string_view base,
                    const Parsed& base_parsed,
-                   const char* fragment,
-                   int fragment_len,
+                   std::string_view fragment,
                    bool is_base_hierarchical,
                    bool* is_relative,
                    Component* relative_component);
 COMPONENT_EXPORT(URL)
-bool IsRelativeURL(const char* base,
+bool IsRelativeUrl(std::string_view base,
                    const Parsed& base_parsed,
-                   const char16_t* fragment,
-                   int fragment_len,
+                   std::u16string_view fragment,
                    bool is_base_hierarchical,
                    bool* is_relative,
                    Component* relative_component);
 
 // Given a canonical parsed source URL, a URL fragment known to be relative,
 // and the identified relevant portion of the relative URL (computed by
-// IsRelativeURL), this produces a new parsed canonical URL in |output| and
+// IsRelativeUrl), this produces a new parsed canonical URL in |output| and
 // |out_parsed|.
 //
 // It also requires a flag indicating whether the base URL is a file: URL
@@ -1023,19 +1162,19 @@
 // reasonable" that will be consistent and valid, just probably not what
 // was intended by the web page author or caller.
 COMPONENT_EXPORT(URL)
-bool ResolveRelativeURL(const char* base_url,
+bool ResolveRelativeUrl(std::string_view base_url,
                         const Parsed& base_parsed,
                         bool base_is_file,
-                        const char* relative_url,
+                        std::string_view relative_url,
                         const Component& relative_component,
                         CharsetConverter* query_converter,
                         CanonOutput* output,
                         Parsed* out_parsed);
 COMPONENT_EXPORT(URL)
-bool ResolveRelativeURL(const char* base_url,
+bool ResolveRelativeUrl(std::string_view base_url,
                         const Parsed& base_parsed,
                         bool base_is_file,
-                        const char16_t* relative_url,
+                        std::u16string_view relative_url,
                         const Component& relative_component,
                         CharsetConverter* query_converter,
                         CanonOutput* output,
diff --git a/url/url_canon_etc.cc b/url/url_canon_etc.cc
index cfe3fe8..4cbce27 100644
--- a/url/url_canon_etc.cc
+++ b/url/url_canon_etc.cc
@@ -2,10 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <array>
+#include <string_view>
+
 // Canonicalizers for random bits that aren't big enough for their own files.
 
 #include <string.h>
 
+#include "url/third_party/mozilla/url_parse.h"
 #include "url/url_canon.h"
 #include "url/url_canon_internal.h"
 
@@ -23,27 +27,29 @@
 // It sucks that we have to do this, since this takes about 13% of the total URL
 // canonicalization time.
 template <typename CHAR>
-const CHAR* DoRemoveURLWhitespace(const CHAR* input,
-                                  int input_len,
-                                  CanonOutputT<CHAR>* buffer,
-                                  int* output_len,
-                                  bool* potentially_dangling_markup) {
+std::basic_string_view<CHAR> DoRemoveUrlWhitespace(
+    std::basic_string_view<CHAR> input,
+    CanonOutputT<CHAR>* buffer,
+    bool* potentially_dangling_markup) {
   // Fast verification that there's nothing that needs removal. This is the 99%
   // case, so we want it to be fast and don't care about impacting the speed
   // when we do find whitespace.
   bool found_whitespace = false;
-  if (sizeof(*input) == 1 && input_len >= kMinimumLengthForSIMD) {
+  if (sizeof(CHAR) == 1 && input.length() >= kMinimumLengthForSIMD) {
     // For large strings, memchr is much faster than any scalar code we can
     // write, even if we need to run it three times. (If this turns out to still
     // be a bottleneck, we could write our own vector code, but given that
     // memchr is so fast, it's unlikely to be relevant.)
-    found_whitespace = memchr(input, '\n', input_len) != nullptr ||
-                       memchr(input, '\r', input_len) != nullptr ||
-                       memchr(input, '\t', input_len) != nullptr;
+    const CHAR* data = input.data();
+    size_t input_len = input.length();
+    found_whitespace = UNSAFE_TODO(memchr(data, '\n', input_len)) != nullptr ||
+                       UNSAFE_TODO(memchr(data, '\r', input_len)) != nullptr ||
+                       UNSAFE_TODO(memchr(data, '\t', input_len)) != nullptr;
   } else {
-    for (int i = 0; i < input_len; i++) {
-      if (!IsRemovableURLWhitespace(input[i]))
+    for (const CHAR ch : input) {
+      if (!IsRemovableURLWhitespace(ch)) {
         continue;
+      }
       found_whitespace = true;
       break;
     }
@@ -52,7 +58,6 @@
   if (!found_whitespace) {
     // Didn't find any whitespace, we don't need to do anything. We can just
     // return the input as the output.
-    *output_len = input_len;
     return input;
   }
 
@@ -61,29 +66,28 @@
   // TODO(mkwst): Ideally, this would use something like `gurl_base::StartsWith`, but
   // that turns out to be difficult to do correctly given this function's
   // character type templating.
-  if (input_len > 5 && input[0] == 'd' && input[1] == 'a' && input[2] == 't' &&
-      input[3] == 'a' && input[4] == ':') {
-    *output_len = input_len;
+  if (input.length() > 5 && input[0] == 'd' && input[1] == 'a' &&
+      input[2] == 't' && input[3] == 'a' && input[4] == ':') {
     return input;
   }
 
   // Remove the whitespace into the new buffer and return it.
-  for (int i = 0; i < input_len; i++) {
-    if (!IsRemovableURLWhitespace(input[i])) {
-      if (potentially_dangling_markup && input[i] == 0x3C)
+  for (const CHAR ch : input) {
+    if (!IsRemovableURLWhitespace(ch)) {
+      if (potentially_dangling_markup && ch == 0x3C) {
         *potentially_dangling_markup = true;
-      buffer->push_back(input[i]);
+      }
+      buffer->push_back(ch);
     }
   }
-  *output_len = buffer->length();
-  return buffer->data();
+  return buffer->view();
 }
 
 // Contains the canonical version of each possible input letter in the scheme
 // (basically, lower-cased). The corresponding entry will be 0 if the letter
 // is not allowed in a scheme.
 // clang-format off
-const char kSchemeCanonical[0x80] = {
+const std::array<char, 0x80> kSchemeCanonical = {
 // 00-1f: all are invalid
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
@@ -109,17 +113,18 @@
 }
 
 template <typename CHAR, typename UCHAR>
-bool DoScheme(const CHAR* spec,
-              const Component& scheme,
+bool DoScheme(std::optional<std::basic_string_view<CHAR>> input,
               CanonOutput* output,
               Component* out_scheme) {
-  if (scheme.is_empty()) {
+  if (!input.has_value() || input->empty()) {
     // Scheme is unspecified or empty, convert to empty by appending a colon.
     *out_scheme = Component(output->length(), 0);
     output->push_back(':');
     return false;
   }
 
+  auto input_value = input.value();
+
   // The output scheme starts from the current position.
   out_scheme->begin = output->length();
 
@@ -129,13 +134,11 @@
   // FindAndCompareScheme, which could cause some security checks on
   // schemes to be incorrect.
   bool success = true;
-  size_t begin = static_cast<size_t>(scheme.begin);
-  size_t end = static_cast<size_t>(scheme.end());
-  for (size_t i = begin; i < end; i++) {
-    UCHAR ch = static_cast<UCHAR>(spec[i]);
+  for (size_t i = 0; i < input_value.length(); i++) {
+    UCHAR ch = static_cast<UCHAR>(input_value[i]);
     char replacement = 0;
     if (ch < 0x80) {
-      if (i == begin) {
+      if (i == 0) {
         // Need to do a special check for the first letter of the scheme.
         if (IsSchemeFirstChar(static_cast<unsigned char>(ch)))
           replacement = kSchemeCanonical[ch];
@@ -158,7 +161,8 @@
 
       // This will escape the output and also handle encoding issues.
       // Ignore the return value since we already failed.
-      AppendUTF8EscapedChar(spec, &i, end, output);
+      AppendUTF8EscapedChar(input_value.data(), &i, input_value.length(),
+                            output);
     }
   }
 
@@ -174,14 +178,13 @@
 // canonicalizing a single source string), but may be different when
 // replacing components.
 template <typename CHAR, typename UCHAR>
-bool DoUserInfo(const CHAR* username_spec,
-                const Component& username,
-                const CHAR* password_spec,
-                const Component& password,
+bool DoUserInfo(std::optional<std::basic_string_view<CHAR>> username,
+                std::optional<std::basic_string_view<CHAR>> password,
                 CanonOutput* output,
                 Component* out_username,
                 Component* out_password) {
-  if (username.is_empty() && password.is_empty()) {
+  if ((!username.has_value() || username->empty()) &&
+      (!password.has_value() || password->empty())) {
     // Common case: no user info. We strip empty username/passwords.
     *out_username = Component();
     *out_password = Component();
@@ -190,22 +193,18 @@
 
   // Write the username.
   out_username->begin = output->length();
-  if (username.is_nonempty()) {
+  if (username.has_value() && !username->empty()) {
     // This will escape characters not valid for the username.
-    AppendStringOfType(&username_spec[username.begin],
-                       static_cast<size_t>(username.len), CHAR_USERINFO,
-                       output);
+    AppendStringOfType(username.value(), CHAR_USERINFO, output);
   }
   out_username->len = output->length() - out_username->begin;
 
   // When there is a password, we need the separator. Note that we strip
   // empty but specified passwords.
-  if (password.is_nonempty()) {
+  if (password.has_value() && !password->empty()) {
     output->push_back(':');
     out_password->begin = output->length();
-    AppendStringOfType(&password_spec[password.begin],
-                       static_cast<size_t>(password.len), CHAR_USERINFO,
-                       output);
+    AppendStringOfType(password.value(), CHAR_USERINFO, output);
     out_password->len = output->length() - out_password->begin;
   } else {
     *out_password = Component();
@@ -222,12 +221,15 @@
 
 // This function will prepend the colon if there will be a port.
 template <typename CHAR, typename UCHAR>
-bool DoPort(const CHAR* spec,
-            const Component& port,
+bool DoPort(std::optional<std::basic_string_view<CHAR>> port_view,
             int default_port_for_scheme,
             CanonOutput* output,
             Component* out_port) {
-  int port_num = ParsePort(spec, port);
+  if (!port_view) {
+    *out_port = Component();
+    return true;  // Leave port empty.
+  }
+  int port_num = ParsePort(*port_view, Component(*port_view));
   if (port_num == PORT_UNSPECIFIED || port_num == default_port_for_scheme) {
     *out_port = Component();
     return true;  // Leave port empty.
@@ -238,8 +240,7 @@
     // what the error was, and mark the URL as invalid by returning false.
     output->push_back(':');
     out_port->begin = output->length();
-    AppendInvalidNarrowString(spec, static_cast<size_t>(port.begin),
-                              static_cast<size_t>(port.end()), output);
+    AppendInvalidNarrowString(*port_view, output);
     out_port->len = output->length() - out_port->begin;
     return false;
   }
@@ -247,8 +248,8 @@
   // Convert port number back to an integer. Max port value is 5 digits, and
   // the Parsed::ExtractPort will have made sure the integer is in range.
   const int buf_size = 6;
-  char buf[buf_size];
-  WritePortInt(buf, buf_size, port_num);
+  std::array<char, buf_size> buf;
+  WritePortInt(buf.data(), buf_size, port_num);
 
   // Append the port number to the output, preceded by a colon.
   output->push_back(':');
@@ -263,7 +264,7 @@
 // clang-format off
 //   Percent-escape all characters from the fragment percent-encode set
 //   https://url.spec.whatwg.org/#fragment-percent-encode-set
-const bool kShouldEscapeCharInFragment[0x80] = {
+const std::array<bool, 0x80> kShouldEscapeCharInFragment = {
 //  Control characters (0x00-0x1F)
     true,  true,  true,  true,  true,  true,  true,  true,
     true,  true,  true,  true,  true,  true,  true,  true,
@@ -297,15 +298,15 @@
 // clang-format on
 
 template <typename CHAR, typename UCHAR>
-void DoCanonicalizeRef(const CHAR* spec,
-                       const Component& ref,
+void DoCanonicalizeRef(std::optional<std::basic_string_view<CHAR>> input,
                        CanonOutput* output,
                        Component* out_ref) {
-  if (!ref.is_valid()) {
+  if (!input.has_value()) {
     // Common case of no ref.
     *out_ref = Component();
     return;
   }
+  auto input_value = input.value();
 
   // Append the ref separator. Note that we need to do this even when the ref
   // is empty but present.
@@ -313,40 +314,33 @@
   out_ref->begin = output->length();
 
   // Now iterate through all the characters, converting to UTF-8 and validating.
-  size_t end = static_cast<size_t>(ref.end());
-  for (size_t i = static_cast<size_t>(ref.begin); i < end; i++) {
-    UCHAR current_char = static_cast<UCHAR>(spec[i]);
+  for (size_t i = 0; i < input_value.length(); ++i) {
+    UCHAR current_char = static_cast<UCHAR>(input.value()[i]);
     if (current_char < 0x80) {
       if (kShouldEscapeCharInFragment[current_char])
-        AppendEscapedChar(static_cast<unsigned char>(spec[i]), output);
+        AppendEscapedChar(static_cast<unsigned char>(input_value[i]), output);
       else
-        output->push_back(static_cast<char>(spec[i]));
+        output->push_back(static_cast<char>(input_value[i]));
     } else {
-      AppendUTF8EscapedChar(spec, &i, end, output);
+      AppendUTF8EscapedChar(input_value.data(), &i, input_value.length(),
+                            output);
     }
   }
-
   out_ref->len = output->length() - out_ref->begin;
 }
 
 }  // namespace
 
-const char* RemoveURLWhitespace(const char* input,
-                                int input_len,
-                                CanonOutputT<char>* buffer,
-                                int* output_len,
-                                bool* potentially_dangling_markup) {
-  return DoRemoveURLWhitespace(input, input_len, buffer, output_len,
-                               potentially_dangling_markup);
+std::string_view RemoveUrlWhitespace(std::string_view input,
+                                     CanonOutputT<char>* buffer,
+                                     bool* potentially_dangling_markup) {
+  return DoRemoveUrlWhitespace(input, buffer, potentially_dangling_markup);
 }
 
-const char16_t* RemoveURLWhitespace(const char16_t* input,
-                                    int input_len,
-                                    CanonOutputT<char16_t>* buffer,
-                                    int* output_len,
-                                    bool* potentially_dangling_markup) {
-  return DoRemoveURLWhitespace(input, input_len, buffer, output_len,
-                               potentially_dangling_markup);
+std::u16string_view RemoveUrlWhitespace(std::u16string_view input,
+                                        CanonOutputT<char16_t>* buffer,
+                                        bool* potentially_dangling_markup) {
+  return DoRemoveUrlWhitespace(input, buffer, potentially_dangling_markup);
 }
 
 char CanonicalSchemeChar(char16_t ch) {
@@ -355,74 +349,62 @@
   return kSchemeCanonical[ch];
 }
 
-bool CanonicalizeScheme(const char* spec,
-                        const Component& scheme,
+bool CanonicalizeScheme(std::optional<std::string_view> input,
                         CanonOutput* output,
                         Component* out_scheme) {
-  return DoScheme<char, unsigned char>(spec, scheme, output, out_scheme);
+  return DoScheme<char, unsigned char>(input, output, out_scheme);
 }
 
-bool CanonicalizeScheme(const char16_t* spec,
-                        const Component& scheme,
+bool CanonicalizeScheme(std::optional<std::u16string_view> input,
                         CanonOutput* output,
                         Component* out_scheme) {
-  return DoScheme<char16_t, char16_t>(spec, scheme, output, out_scheme);
+  return DoScheme<char16_t, char16_t>(input, output, out_scheme);
 }
 
-bool CanonicalizeUserInfo(const char* username_source,
-                          const Component& username,
-                          const char* password_source,
-                          const Component& password,
+bool CanonicalizeUserInfo(std::optional<std::string_view> username,
+                          std::optional<std::string_view> password,
                           CanonOutput* output,
                           Component* out_username,
                           Component* out_password) {
-  return DoUserInfo<char, unsigned char>(username_source, username,
-                                         password_source, password, output,
+  return DoUserInfo<char, unsigned char>(username, password, output,
                                          out_username, out_password);
 }
 
-bool CanonicalizeUserInfo(const char16_t* username_source,
-                          const Component& username,
-                          const char16_t* password_source,
-                          const Component& password,
+bool CanonicalizeUserInfo(std::optional<std::u16string_view> username,
+                          std::optional<std::u16string_view> password,
                           CanonOutput* output,
                           Component* out_username,
                           Component* out_password) {
-  return DoUserInfo<char16_t, char16_t>(username_source, username,
-                                        password_source, password, output,
+  return DoUserInfo<char16_t, char16_t>(username, password, output,
                                         out_username, out_password);
 }
 
-bool CanonicalizePort(const char* spec,
-                      const Component& port,
+bool CanonicalizePort(std::optional<std::string_view> port_view,
                       int default_port_for_scheme,
                       CanonOutput* output,
                       Component* out_port) {
-  return DoPort<char, unsigned char>(spec, port, default_port_for_scheme,
-                                     output, out_port);
+  return DoPort<char, unsigned char>(port_view, default_port_for_scheme, output,
+                                     out_port);
 }
 
-bool CanonicalizePort(const char16_t* spec,
-                      const Component& port,
+bool CanonicalizePort(std::optional<std::u16string_view> port_view,
                       int default_port_for_scheme,
                       CanonOutput* output,
                       Component* out_port) {
-  return DoPort<char16_t, char16_t>(spec, port, default_port_for_scheme, output,
+  return DoPort<char16_t, char16_t>(port_view, default_port_for_scheme, output,
                                     out_port);
 }
 
-void CanonicalizeRef(const char* spec,
-                     const Component& ref,
+void CanonicalizeRef(std::optional<std::string_view> input,
                      CanonOutput* output,
                      Component* out_ref) {
-  DoCanonicalizeRef<char, unsigned char>(spec, ref, output, out_ref);
+  DoCanonicalizeRef<char, unsigned char>(input, output, out_ref);
 }
 
-void CanonicalizeRef(const char16_t* spec,
-                     const Component& ref,
+void CanonicalizeRef(std::optional<std::u16string_view> input,
                      CanonOutput* output,
                      Component* out_ref) {
-  DoCanonicalizeRef<char16_t, char16_t>(spec, ref, output, out_ref);
+  DoCanonicalizeRef<char16_t, char16_t>(input, output, out_ref);
 }
 
 }  // namespace url
diff --git a/url/url_canon_filesystemurl.cc b/url/url_canon_filesystemurl.cc
index f1a9f1c..849dc8b 100644
--- a/url/url_canon_filesystemurl.cc
+++ b/url/url_canon_filesystemurl.cc
@@ -4,6 +4,8 @@
 
 // Functions for canonicalizing "filesystem:file:" URLs.
 
+#include <optional>
+
 #include "url/url_canon.h"
 #include "url/url_canon_internal.h"
 #include "url/url_file.h"
@@ -17,8 +19,8 @@
 
 // We use the URLComponentSource for the outer URL, as it can have replacements,
 // whereas the inner_url can't, so it uses spec.
-template<typename CHAR, typename UCHAR>
-bool DoCanonicalizeFileSystemURL(const CHAR* spec,
+template <typename CHAR>
+bool DoCanonicalizeFileSystemUrl(std::basic_string_view<CHAR> spec,
                                  const URLComponentSource<CHAR>& source,
                                  const Parsed& parsed,
                                  CharsetConverter* charset_converter,
@@ -48,17 +50,17 @@
     new_inner_parsed.scheme.begin = output->length();
     output->Append("file://");
     new_inner_parsed.scheme.len = 4;
-    success &= CanonicalizePath(spec, inner_parsed->path, output,
+    success &= CanonicalizePath(inner_parsed->path.MaybeAsViewOn(spec), output,
                                 &new_inner_parsed.path);
-  } else if (GetStandardSchemeType(spec, inner_parsed->scheme,
+  } else if (GetStandardSchemeType(inner_parsed->scheme.AsViewOn(spec),
                                    &inner_scheme_type)) {
     if (inner_scheme_type == SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION) {
       // Strip out the user information from the inner URL, if any.
       inner_scheme_type = SCHEME_WITH_HOST_AND_PORT;
     }
-    success = CanonicalizeStandardURL(
-        spec, inner_parsed->Length(), *inner_parsed, inner_scheme_type,
-        charset_converter, output, &new_inner_parsed);
+    success =
+        CanonicalizeStandardUrl(spec, *inner_parsed, inner_scheme_type,
+                                charset_converter, output, &new_inner_parsed);
   } else {
     // TODO(ericu): The URL is wrong, but should we try to output more of what
     // we were given?  Echoing back filesystem:mailto etc. doesn't seem all that
@@ -68,13 +70,14 @@
   // The filesystem type must be more than just a leading slash for validity.
   success &= new_inner_parsed.path.len > 1;
 
-  success &= CanonicalizePath(source.path, parsed.path, output,
-                              &new_parsed->path);
+  success &= CanonicalizePath(parsed.path.maybe_as_string_view_on(source.path),
+                              output, &new_parsed->path);
 
   // Ignore failures for query/ref since the URL can probably still be loaded.
-  CanonicalizeQuery(source.query, parsed.query, charset_converter,
-                    output, &new_parsed->query);
-  CanonicalizeRef(source.ref, parsed.ref, output, &new_parsed->ref);
+  CanonicalizeQuery(parsed.query.maybe_as_string_view_on(source.query),
+                    charset_converter, output, &new_parsed->query);
+  CanonicalizeRef(parsed.ref.maybe_as_string_view_on(source.ref), output,
+                  &new_parsed->ref);
   if (success)
     new_parsed->set_inner_parsed(new_inner_parsed);
 
@@ -83,53 +86,52 @@
 
 }  // namespace
 
-bool CanonicalizeFileSystemURL(const char* spec,
-                               int spec_len,
+bool CanonicalizeFileSystemUrl(std::string_view spec,
                                const Parsed& parsed,
                                CharsetConverter* charset_converter,
                                CanonOutput* output,
                                Parsed* new_parsed) {
-  return DoCanonicalizeFileSystemURL<char, unsigned char>(
-      spec, URLComponentSource<char>(spec), parsed, charset_converter, output,
-      new_parsed);
+  return DoCanonicalizeFileSystemUrl(spec, URLComponentSource(spec.data()),
+                                     parsed, charset_converter, output,
+                                     new_parsed);
 }
 
-bool CanonicalizeFileSystemURL(const char16_t* spec,
-                               int spec_len,
+bool CanonicalizeFileSystemUrl(std::u16string_view spec,
                                const Parsed& parsed,
                                CharsetConverter* charset_converter,
                                CanonOutput* output,
                                Parsed* new_parsed) {
-  return DoCanonicalizeFileSystemURL<char16_t, char16_t>(
-      spec, URLComponentSource<char16_t>(spec), parsed, charset_converter,
-      output, new_parsed);
+  return DoCanonicalizeFileSystemUrl(spec, URLComponentSource(spec.data()),
+                                     parsed, charset_converter, output,
+                                     new_parsed);
 }
 
-bool ReplaceFileSystemURL(const char* base,
+bool ReplaceFileSystemUrl(std::string_view base,
                           const Parsed& base_parsed,
                           const Replacements<char>& replacements,
                           CharsetConverter* charset_converter,
                           CanonOutput* output,
                           Parsed* new_parsed) {
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupOverrideComponents(base, replacements, &source, &parsed);
-  return DoCanonicalizeFileSystemURL<char, unsigned char>(
-      base, source, parsed, charset_converter, output, new_parsed);
+  SetupOverrideComponents(base.data(), replacements, &source, &parsed);
+  return DoCanonicalizeFileSystemUrl(base, source, parsed, charset_converter,
+                                     output, new_parsed);
 }
 
-bool ReplaceFileSystemURL(const char* base,
+bool ReplaceFileSystemUrl(std::string_view base,
                           const Parsed& base_parsed,
                           const Replacements<char16_t>& replacements,
                           CharsetConverter* charset_converter,
                           CanonOutput* output,
                           Parsed* new_parsed) {
   RawCanonOutput<1024> utf8;
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupUTF16OverrideComponents(base, replacements, &utf8, &source, &parsed);
-  return DoCanonicalizeFileSystemURL<char, unsigned char>(
-      base, source, parsed, charset_converter, output, new_parsed);
+  SetupUTF16OverrideComponents(base.data(), replacements, &utf8, &source,
+                               &parsed);
+  return DoCanonicalizeFileSystemUrl(base, source, parsed, charset_converter,
+                                     output, new_parsed);
 }
 
 }  // namespace url
diff --git a/url/url_canon_fileurl.cc b/url/url_canon_fileurl.cc
index 5c243f6..66367f0 100644
--- a/url/url_canon_fileurl.cc
+++ b/url/url_canon_fileurl.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 // Functions for canonicalizing "file:" URLs.
 
 #include <string_view>
@@ -46,7 +51,8 @@
   Component sub_path = MakeRange(begin, drive_letter_pos);
   RawCanonOutput<1024> output;
   Component output_path;
-  bool success = CanonicalizePath(spec, sub_path, &output, &output_path);
+  bool success = CanonicalizePath(sub_path.maybe_as_string_view_on(spec),
+                                  &output, &output_path);
   if (!success || output_path.len != 1 || output.at(output_path.begin) != '/') {
     return -1;
   }
@@ -56,16 +62,18 @@
 
 #ifdef WIN32
 
-// Given a pointer into the spec, this copies and canonicalizes the drive
-// letter and colon to the output, if one is found. If there is not a drive
-// spec, it won't do anything. The index of the next character in the input
-// spec is returned (after the colon when a drive spec is found, the begin
-// offset if one is not).
+// Given a path string, this copies and canonicalizes the drive letter and
+// colon to the `output`, if one is found. If there is not a drive spec, it
+// won't do anything. The index of the next character in the input string
+// is returned (after the colon when a drive spec is found, zero if one is
+// not).
 template <typename CHAR>
-int FileDoDriveSpec(const CHAR* spec, int begin, int end, CanonOutput* output) {
-  int drive_letter_pos = FindWindowsDriveLetter(spec, begin, end);
-  if (drive_letter_pos < begin)
-    return begin;
+size_t FileDoDriveSpec(std::basic_string_view<CHAR> path, CanonOutput* output) {
+  int drive_letter_pos = FindWindowsDriveLetter(
+      path.data(), 0, gurl_base::checked_cast<int>(path.length()));
+  if (drive_letter_pos < 0) {
+    return 0;
+  }
 
   // By now, a valid drive letter is confirmed at position drive_letter_pos,
   // followed by a valid drive letter separator (a colon or a pipe).
@@ -73,46 +81,44 @@
   output->push_back('/');
 
   // Normalize Windows drive letters to uppercase.
-  if (gurl_base::IsAsciiLower(spec[drive_letter_pos]))
-    output->push_back(static_cast<char>(spec[drive_letter_pos] - 'a' + 'A'));
-  else
-    output->push_back(static_cast<char>(spec[drive_letter_pos]));
+  output->push_back(
+      static_cast<char>(gurl_base::ToUpperASCII(path[drive_letter_pos])));
 
   // Normalize the character following it to a colon rather than pipe.
   output->push_back(':');
-  return drive_letter_pos + 2;
+  return static_cast<size_t>(drive_letter_pos + 2);
 }
 
 #endif  // WIN32
 
-template<typename CHAR, typename UCHAR>
-bool DoFileCanonicalizePath(const CHAR* spec,
-                            const Component& path,
+template <typename CHAR, typename UCHAR>
+bool DoFileCanonicalizePath(std::optional<std::basic_string_view<CHAR>> path,
                             CanonOutput* output,
                             Component* out_path) {
   // Copies and normalizes the "c:" at the beginning, if present.
   out_path->begin = output->length();
-  int after_drive;
+  size_t after_drive = 0;
 #ifdef WIN32
-  after_drive = FileDoDriveSpec(spec, path.begin, path.end(), output);
-#else
-  after_drive = path.begin;
+  if (path) {
+    after_drive = FileDoDriveSpec(*path, output);
+  }
 #endif
 
   // Copies the rest of the path, starting from the slash following the
   // drive colon (if any, Windows only), or the first slash of the path.
   bool success = true;
-  if (after_drive < path.end()) {
+  if (path && after_drive < path->length()) {
     // Use the regular path canonicalizer to canonicalize the rest of the path
     // after the drive.
     //
     // Give it a fake output component to write into, since we will be
     // calculating the out_path ourselves (consisting of both the drive and the
     // path we canonicalize here).
-    Component sub_path = MakeRange(after_drive, path.end());
     Component fake_output_path;
-    success = CanonicalizePath(spec, sub_path, output, &fake_output_path);
-  } else if (after_drive == path.begin) {
+    success = CanonicalizePath(
+        path->substr(after_drive, path->length() - after_drive), output,
+        &fake_output_path);
+  } else if (after_drive == 0) {
     // No input path and no drive spec, canonicalize to a slash.
     output->push_back('/');
   }
@@ -121,12 +127,14 @@
   return success;
 }
 
-template<typename CHAR, typename UCHAR>
-bool DoCanonicalizeFileURL(const URLComponentSource<CHAR>& source,
+template <typename CHAR, typename UCHAR>
+bool DoCanonicalizeFileUrl(const URLComponentSource<CHAR>& source,
                            const Parsed& parsed,
                            CharsetConverter* query_converter,
                            CanonOutput* output,
                            Parsed* new_parsed) {
+  GURL_DCHECK(!parsed.has_opaque_path);
+
   // Things we don't set in file: URLs.
   new_parsed->username = Component();
   new_parsed->password = Component();
@@ -144,7 +152,7 @@
   //
   // Note: we do this on every platform per URL Standard, not just Windows.
   //
-  // TODO(https://crbug.com/688961): According to the latest URL spec, this
+  // TODO(crbug.com/41299821): According to the latest URL spec, this
   // transformation should be done regardless of the path.
   Component host_range = parsed.host;
   if (IsLocalhost(source.host, host_range.begin, host_range.end()) &&
@@ -158,14 +166,18 @@
   // TODO(brettw) This doesn't do any checking for host name validity. We
   // should probably handle validity checking of UNC hosts differently than
   // for regular IP hosts.
-  bool success =
-      CanonicalizeHost(source.host, host_range, output, &new_parsed->host);
-  success &= DoFileCanonicalizePath<CHAR, UCHAR>(source.path, parsed.path,
-                                    output, &new_parsed->path);
+  bool success = CanonicalizeFileHost(
+      std::basic_string_view<CHAR>(
+          source.host, host_range.is_valid() ? host_range.end() : 0),
+      host_range, *output, new_parsed->host);
+  success &= DoFileCanonicalizePath<CHAR, UCHAR>(
+      parsed.path.maybe_as_string_view_on(source.path), output,
+      &new_parsed->path);
 
-  CanonicalizeQuery(source.query, parsed.query, query_converter,
-                    output, &new_parsed->query);
-  CanonicalizeRef(source.ref, parsed.ref, output, &new_parsed->ref);
+  CanonicalizeQuery(parsed.query.maybe_as_string_view_on(source.query),
+                    query_converter, output, &new_parsed->query);
+  CanonicalizeRef(parsed.ref.maybe_as_string_view_on(source.ref), output,
+                  &new_parsed->ref);
 
   return success;
 }
@@ -180,68 +192,63 @@
   return DoFindWindowsDriveLetter(spec, begin, end);
 }
 
-bool CanonicalizeFileURL(const char* spec,
-                         int spec_len,
+bool CanonicalizeFileUrl(std::string_view spec,
                          const Parsed& parsed,
                          CharsetConverter* query_converter,
                          CanonOutput* output,
                          Parsed* new_parsed) {
-  return DoCanonicalizeFileURL<char, unsigned char>(
-      URLComponentSource<char>(spec), parsed, query_converter,
-      output, new_parsed);
-}
-
-bool CanonicalizeFileURL(const char16_t* spec,
-                         int spec_len,
-                         const Parsed& parsed,
-                         CharsetConverter* query_converter,
-                         CanonOutput* output,
-                         Parsed* new_parsed) {
-  return DoCanonicalizeFileURL<char16_t, char16_t>(
-      URLComponentSource<char16_t>(spec), parsed, query_converter, output,
+  return DoCanonicalizeFileUrl<char, unsigned char>(
+      URLComponentSource<char>(spec.data()), parsed, query_converter, output,
       new_parsed);
 }
 
-bool FileCanonicalizePath(const char* spec,
-                          const Component& path,
-                          CanonOutput* output,
-                          Component* out_path) {
-  return DoFileCanonicalizePath<char, unsigned char>(spec, path,
-                                                     output, out_path);
+bool CanonicalizeFileUrl(std::u16string_view spec,
+                         const Parsed& parsed,
+                         CharsetConverter* query_converter,
+                         CanonOutput* output,
+                         Parsed* new_parsed) {
+  return DoCanonicalizeFileUrl<char16_t, char16_t>(
+      URLComponentSource<char16_t>(spec.data()), parsed, query_converter,
+      output, new_parsed);
 }
 
-bool FileCanonicalizePath(const char16_t* spec,
-                          const Component& path,
+bool FileCanonicalizePath(std::optional<std::string_view> path,
                           CanonOutput* output,
                           Component* out_path) {
-  return DoFileCanonicalizePath<char16_t, char16_t>(spec, path, output,
-                                                    out_path);
+  return DoFileCanonicalizePath<char, unsigned char>(path, output, out_path);
 }
 
-bool ReplaceFileURL(const char* base,
+bool FileCanonicalizePath(std::optional<std::u16string_view> path,
+                          CanonOutput* output,
+                          Component* out_path) {
+  return DoFileCanonicalizePath<char16_t, char16_t>(path, output, out_path);
+}
+
+bool ReplaceFileUrl(std::string_view base,
                     const Parsed& base_parsed,
                     const Replacements<char>& replacements,
                     CharsetConverter* query_converter,
                     CanonOutput* output,
                     Parsed* new_parsed) {
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupOverrideComponents(base, replacements, &source, &parsed);
-  return DoCanonicalizeFileURL<char, unsigned char>(
+  SetupOverrideComponents(base.data(), replacements, &source, &parsed);
+  return DoCanonicalizeFileUrl<char, unsigned char>(
       source, parsed, query_converter, output, new_parsed);
 }
 
-bool ReplaceFileURL(const char* base,
+bool ReplaceFileUrl(std::string_view base,
                     const Parsed& base_parsed,
                     const Replacements<char16_t>& replacements,
                     CharsetConverter* query_converter,
                     CanonOutput* output,
                     Parsed* new_parsed) {
   RawCanonOutput<1024> utf8;
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupUTF16OverrideComponents(base, replacements, &utf8, &source, &parsed);
-  return DoCanonicalizeFileURL<char, unsigned char>(
+  SetupUTF16OverrideComponents(base.data(), replacements, &utf8, &source,
+                               &parsed);
+  return DoCanonicalizeFileUrl<char, unsigned char>(
       source, parsed, query_converter, output, new_parsed);
 }
 
diff --git a/url/url_canon_host.cc b/url/url_canon_host.cc
index 65118b2..9956adb 100644
--- a/url/url_canon_host.cc
+++ b/url/url_canon_host.cc
@@ -2,8 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string>
+#include <string_view>
+
 #include "polyfills/base/check.h"
-#include "polyfills/base/cpu_reduction_experiment.h"
+#include "base/compiler_specific.h"
 #include "url/url_canon.h"
 #include "url/url_canon_internal.h"
 #include "url/url_features.h"
@@ -12,69 +15,18 @@
 
 namespace {
 
-// clang-format off
-//
-// For reference, here's what IE supports:
-// Key: 0 (disallowed: failure if present in the input)
-//      + (allowed either escaped or unescaped, and unmodified)
-//      U (allowed escaped or unescaped but always unescaped if present in
-//         escaped form)
-//      E (allowed escaped or unescaped but always escaped if present in
-//         unescaped form)
-//      % (only allowed escaped in the input, will be unmodified).
-//      I left blank alpha numeric characters.
-//
-//    00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
-//    -----------------------------------------------
-// 0   0  E  E  E  E  E  E  E  E  E  E  E  E  E  E  E
-// 1   E  E  E  E  E  E  E  E  E  E  E  E  E  E  E  E
-// 2   E  +  E  E  +  E  +  +  +  +  +  +  +  U  U  0
-// 3                                 %  %  E  +  E  0  <-- Those are  : ; < = > ?
-// 4   %
-// 5                                    U  0  U  U  U  <-- Those are  [ \ ] ^ _
-// 6   E                                               <-- That's  `
-// 7                                    E  E  E  U  E  <-- Those are { | } ~ (UNPRINTABLE)
-//
-// NOTE: I didn't actually test all the control characters. Some may be
-// disallowed in the input, but they are all accepted escaped except for 0.
-// I also didn't test if characters affecting HTML parsing are allowed
-// unescaped, e.g. (") or (#), which would indicate the beginning of the path.
-// Surprisingly, space is accepted in the input and always escaped.
-//
-// TODO(https://crbug.com/1416013): Remove the above historical reference
-// information once we are 100% standard compliant to the URL Standard.
-//
 // This table lists the canonical version of all characters we allow in the
-// input, with 0 indicating it is disallowed. We use the magic kEscapedHostChar
-// value to indicate that this character should be escaped. We are a little more
-// restrictive than IE, but less restrictive than Firefox.
-//
+// input, with 0 indicating it is disallowed. We use the magic kEsc value to
+// indicate that this character should be escaped. At present, ' ' (SPACE) and
+// '*' (asterisk) are still non-compliant to the URL Standard. See
+// https://crbug.com/1416013 for details.
 const unsigned char kEsc = 0xff;
+// clang-format off
 const unsigned char kHostCharLookup[0x80] = {
 // 00-1f: all are invalid
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 //  ' '   !    "    #    $    %    &    '    (    )    *    +    ,    -    .    /
-   kEsc,kEsc,kEsc,kEsc,kEsc,  0, kEsc,kEsc,kEsc,kEsc,kEsc, '+',kEsc, '-', '.',  0,
-//   0    1    2    3    4    5    6    7    8    9    :    ;    <    =    >    ?
-    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':',  0 ,kEsc,kEsc,kEsc,  0 ,
-//   @    A    B    C    D    E    F    G    H    I    J    K    L    M    N    O
-   kEsc, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
-//   P    Q    R    S    T    U    V    W    X    Y    Z    [    \    ]    ^    _
-    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '[',  0 , ']',  0 , '_',
-//   `    a    b    c    d    e    f    g    h    i    j    k    l    m    n    o
-   kEsc, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
-//   p    q    r    s    t    u    v    w    x    y    z    {    |    }    ~
-    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',kEsc,kEsc,kEsc,  0 ,  0 };
-
-// The following table is used when kStandardCompliantHostCharLookup feature is
-// enabled. See https://crbug.com/1416013 for details. At present, ' ' (SPACE)
-// and '*' (asterisk) are still non-compliant to the URL Standard.
-const unsigned char kStandardCompliantHostCharLookup[0x80] = {
-// 00-1f: all are invalid
-     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-//  ' '   !    "    #    $    %    &    '    (    )    *    +    ,    -    .    /
     kEsc,'!', '"',  0,  '$',  0,  '&', '\'','(', ')', kEsc, '+', ',', '-', '.',  0,
 //   0    1    2    3    4    5    6    7    8    9    :    ;    <    =    >    ?
     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';' , 0,  '=',  0,   0,
@@ -88,6 +40,149 @@
     'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{',  0, '}',  '~',  0 };
 // clang-format on
 
+// https://url.spec.whatwg.org/#forbidden-host-code-point
+const uint8_t kForbiddenHost = 0x1;
+
+// TODO(crbug.com/40063064): Merge other lookup tables into this table. That can
+// be probably done after https://crbug.com/1416013 is resolved.
+//
+// This table is currently only used for an opaque-host in non-special URLs.
+const uint8_t kHostCharacterTable[128] = {
+    kForbiddenHost,  // 0x00 (NUL)
+    0,               // 0x01
+    0,               // 0x02
+    0,               // 0x03
+    0,               // 0x04
+    0,               // 0x05
+    0,               // 0x06
+    0,               // 0x07
+    0,               // 0x08
+    kForbiddenHost,  // 0x09 (TAB)
+    kForbiddenHost,  // 0x0A (LF)
+    0,               // 0x0B
+    0,               // 0x0C
+    kForbiddenHost,  // 0x0D (CR)
+    0,               // 0x0E
+    0,               // 0x0F
+    0,               // 0x10
+    0,               // 0x11
+    0,               // 0x12
+    0,               // 0x13
+    0,               // 0x14
+    0,               // 0x15
+    0,               // 0x16
+    0,               // 0x17
+    0,               // 0x18
+    0,               // 0x19
+    0,               // 0x1A
+    0,               // 0x1B
+    0,               // 0x1C
+    0,               // 0x1D
+    0,               // 0x1E
+    0,               // 0x1F
+    kForbiddenHost,  // ' '
+    0,               // '!'
+    0,               // '"'
+    kForbiddenHost,  // '#'
+    0,               // '$'
+    0,               // '%'
+    0,               // '&'
+    0,               // '\''
+    0,               // '('
+    0,               // ')'
+    0,               // '*'
+    0,               // '+'
+    0,               // ','
+    0,               // '-'
+    0,               // '.'
+    kForbiddenHost,  // '/'
+    0,               // '0'
+    0,               // '1'
+    0,               // '2'
+    0,               // '3'
+    0,               // '4'
+    0,               // '5'
+    0,               // '6'
+    0,               // '7'
+    0,               // '8'
+    0,               // '9'
+    kForbiddenHost,  // ':'
+    0,               // ';'
+    kForbiddenHost,  // '<'
+    0,               // '='
+    kForbiddenHost,  // '>'
+    kForbiddenHost,  // '?'
+    kForbiddenHost,  // '@'
+    0,               // 'A'
+    0,               // 'B'
+    0,               // 'C'
+    0,               // 'D'
+    0,               // 'E'
+    0,               // 'F'
+    0,               // 'G'
+    0,               // 'H'
+    0,               // 'I'
+    0,               // 'J'
+    0,               // 'K'
+    0,               // 'L'
+    0,               // 'M'
+    0,               // 'N'
+    0,               // 'O'
+    0,               // 'P'
+    0,               // 'Q'
+    0,               // 'R'
+    0,               // 'S'
+    0,               // 'T'
+    0,               // 'U'
+    0,               // 'V'
+    0,               // 'W'
+    0,               // 'X'
+    0,               // 'Y'
+    0,               // 'Z'
+    kForbiddenHost,  // '['
+    kForbiddenHost,  // '\\'
+    kForbiddenHost,  // ']'
+    kForbiddenHost,  // '^'
+    0,               // '_'
+    0,               // '`'
+    0,               // 'a'
+    0,               // 'b'
+    0,               // 'c'
+    0,               // 'd'
+    0,               // 'e'
+    0,               // 'f'
+    0,               // 'g'
+    0,               // 'h'
+    0,               // 'i'
+    0,               // 'j'
+    0,               // 'k'
+    0,               // 'l'
+    0,               // 'm'
+    0,               // 'n'
+    0,               // 'o'
+    0,               // 'p'
+    0,               // 'q'
+    0,               // 'r'
+    0,               // 's'
+    0,               // 't'
+    0,               // 'u'
+    0,               // 'v'
+    0,               // 'w'
+    0,               // 'x'
+    0,               // 'y'
+    0,               // 'z'
+    0,               // '{'
+    kForbiddenHost,  // '|'
+    0,               // '}'
+    0,               // '~'
+    0,               // 0x7F (DEL)
+};
+// clang-format on
+
+bool IsForbiddenHostCodePoint(uint8_t ch) {
+  return ch <= 0x7F && (UNSAFE_TODO(kHostCharacterTable[ch]) & kForbiddenHost);
+}
+
 // RFC1034 maximum FQDN length.
 constexpr size_t kMaxHostLength = 253;
 
@@ -106,19 +201,18 @@
 // Scans a host name and fills in the output flags according to what we find.
 // |has_non_ascii| will be true if there are any non-7-bit characters, and
 // |has_escaped| will be true if there is a percent sign.
-template<typename CHAR, typename UCHAR>
-void ScanHostname(const CHAR* spec,
-                  const Component& host,
+template <typename CHAR, typename UCHAR>
+void ScanHostname(std::basic_string_view<CHAR> host,
                   bool* has_non_ascii,
                   bool* has_escaped) {
-  int end = host.end();
   *has_non_ascii = false;
   *has_escaped = false;
-  for (int i = host.begin; i < end; i++) {
-    if (static_cast<UCHAR>(spec[i]) >= 0x80)
+  for (const CHAR ch : host) {
+    if (static_cast<UCHAR>(ch) >= 0x80) {
       *has_non_ascii = true;
-    else if (spec[i] == '%')
+    } else if (ch == '%') {
       *has_escaped = true;
+    }
   }
 }
 
@@ -143,20 +237,19 @@
 //    |*has_non_ascii| flag.
 //
 // The return value indicates if the output is a potentially valid host name.
-template <typename INCHAR, typename OUTCHAR>
-bool DoSimpleHost(const INCHAR* host,
-                  size_t host_len,
+template <CanonMode canon_mode, typename INCHAR, typename OUTCHAR>
+bool DoSimpleHost(std::basic_string_view<INCHAR> host,
                   CanonOutputT<OUTCHAR>* output,
                   bool* has_non_ascii) {
   *has_non_ascii = false;
 
   bool success = true;
-  for (size_t i = 0; i < host_len; ++i) {
+  for (size_t i = 0; i < host.length(); ++i) {
     unsigned int source = host[i];
     if (source == '%') {
       // Unescape first, if possible.
       // Source will be used only if decode operation was successful.
-      if (!DecodeEscaped(host, &i, host_len,
+      if (!DecodeEscaped(host.data(), &i, host.length(),
                          reinterpret_cast<unsigned char*>(&source))) {
         // Invalid escaped character. There is nothing that can make this
         // host valid. We append an escaped percent so the URL looks reasonable
@@ -169,12 +262,7 @@
 
     if (source < 0x80) {
       // We have ASCII input, we can use our lookup table.
-      unsigned char replacement;
-      if (url::IsUsingStandardCompliantHostCharacters()) {
-        replacement = kStandardCompliantHostCharLookup[source];
-      } else {
-        replacement = kHostCharLookup[source];
-      }
+      unsigned char replacement = UNSAFE_TODO(kHostCharLookup[source]);
       if (!replacement) {
         // Invalid character, add it as percent-escaped and mark as failed.
         AppendEscapedChar(source, output);
@@ -182,6 +270,11 @@
       } else if (replacement == kEsc) {
         // This character is valid but should be escaped.
         AppendEscapedChar(source, output);
+        if (source == ' ' &&
+            url::IsDisallowingSpaceCharacterInURLHostParsing() &&
+            canon_mode != CanonMode::kFileURL) {
+          success = false;
+        }
       } else {
         // Common case, the given character is valid in a hostname, the lookup
         // table tells us the canonical representation of that character (lower
@@ -200,16 +293,18 @@
 }
 
 // Canonicalizes a host that requires IDN conversion. Returns true on success
+template <CanonMode canon_mode>
 bool DoIDNHost(const char16_t* src, size_t src_len, CanonOutput* output) {
+  std::u16string_view host_view(src, src_len);
   int original_output_len = output->length();  // So we can rewind below.
 
   // We need to escape URL before doing IDN conversion, since punicode strings
   // cannot be escaped after they are created.
   RawCanonOutputW<kTempHostBufferLen> url_escaped_host;
   bool has_non_ascii;
-  DoSimpleHost(src, src_len, &url_escaped_host, &has_non_ascii);
+  DoSimpleHost<canon_mode>(host_view, &url_escaped_host, &has_non_ascii);
   if (url_escaped_host.length() > kMaxHostBufferLength) {
-    AppendInvalidNarrowString(src, 0, src_len, output);
+    AppendInvalidNarrowString(host_view, output);
     return false;
   }
 
@@ -217,15 +312,15 @@
   if (!IDNToASCII(url_escaped_host.view(), &wide_output)) {
     // Some error, give up. This will write some reasonable looking
     // representation of the string to the output.
-    AppendInvalidNarrowString(src, 0, src_len, output);
+    AppendInvalidNarrowString(host_view, output);
     return false;
   }
 
   // Now we check the ASCII output like a normal host. It will also handle
   // unescaping. Although we unescaped everything before this function call, if
   // somebody does %00 as fullwidth, ICU will convert this to ASCII.
-  bool success = DoSimpleHost(wide_output.data(), wide_output.length(), output,
-                              &has_non_ascii);
+  bool success =
+      DoSimpleHost<canon_mode>(wide_output.view(), output, &has_non_ascii);
   if (has_non_ascii) {
     // ICU generated something that DoSimpleHost didn't think looked like
     // ASCII. This is quite rare, but ICU might convert some characters to
@@ -242,8 +337,7 @@
     // ASCII isn't strictly necessary, but DoSimpleHost handles this case
     // anyway so we handle it/
     output->set_length(original_output_len);
-    AppendInvalidNarrowString(wide_output.data(), 0, wide_output.length(),
-                              output);
+    AppendInvalidNarrowString(wide_output.view(), output);
     return false;
   }
   return success;
@@ -252,8 +346,8 @@
 // 8-bit convert host to its ASCII version: this converts the UTF-8 input to
 // UTF-16. The has_escaped flag should be set if the input string requires
 // unescaping.
-bool DoComplexHost(const char* host,
-                   size_t host_len,
+template <CanonMode canon_mode>
+bool DoComplexHost(std::string_view host,
                    bool has_non_ascii,
                    bool has_escaped,
                    CanonOutput* output) {
@@ -263,8 +357,7 @@
 
   // Points to the UTF-8 data we want to convert. This will either be the
   // input or the unescaped version written to |*output| if necessary.
-  const char* utf8_source;
-  size_t utf8_source_len;
+  std::string_view utf8_source;
   bool are_all_escaped_valid = true;
   if (has_escaped) {
     // Unescape before converting to UTF-16 for IDN. We write this into the
@@ -272,7 +365,7 @@
     // save another huge stack buffer. It will be replaced below if it requires
     // IDN. This will also update our non-ASCII flag so we know whether the
     // unescaped input requires IDN.
-    if (!DoSimpleHost(host, host_len, output, &has_non_ascii)) {
+    if (!DoSimpleHost<canon_mode>(host, output, &has_non_ascii)) {
       // Error with some escape sequence. We'll call the current output
       // complete. DoSimpleHost will have written some "reasonable" output
       // for the invalid escapes, but the output could be non-ASCII and
@@ -288,42 +381,41 @@
 
     // Save the pointer into the data was just converted (it may be appended to
     // other data in the output buffer).
-    utf8_source = &output->data()[begin_length];
-    utf8_source_len = output->length() - begin_length;
+    utf8_source = output->view().substr(begin_length);
   } else {
     // We don't need to unescape, use input for IDNization later. (We know the
     // input has non-ASCII, or the simple version would have been called
     // instead of us.)
     utf8_source = host;
-    utf8_source_len = host_len;
   }
 
   // Non-ASCII input requires IDN, convert to UTF-16 and do the IDN conversion.
   // Above, we may have used the output to write the unescaped values to, so
   // we have to rewind it to where we started after we convert it to UTF-16.
   StackBufferW utf16;
-  if (!ConvertUTF8ToUTF16(utf8_source, utf8_source_len, &utf16)) {
+  if (!ConvertUTF8ToUTF16(utf8_source, &utf16)) {
     // In this error case, the input may or may not be the output.
     StackBuffer utf8;
-    for (size_t i = 0; i < utf8_source_len; i++)
+    for (size_t i = 0; i < utf8_source.length(); i++) {
       utf8.push_back(utf8_source[i]);
+    }
     output->set_length(begin_length);
-    AppendInvalidNarrowString(utf8.data(), 0, utf8.length(), output);
+    AppendInvalidNarrowString(utf8.view(), output);
     return false;
   }
   output->set_length(begin_length);
 
   // This will call DoSimpleHost which will do normal ASCII canonicalization
   // and also check for IP addresses in the outpt.
-  return DoIDNHost(utf16.data(), utf16.length(), output) &&
+  return DoIDNHost<canon_mode>(utf16.data(), utf16.length(), output) &&
          are_all_escaped_valid;
 }
 
 // UTF-16 convert host to its ASCII version. The set up is already ready for
 // the backend, so we just pass through. The has_escaped flag should be set if
 // the input string requires unescaping.
-bool DoComplexHost(const char16_t* host,
-                   size_t host_len,
+template <CanonMode canon_mode>
+bool DoComplexHost(std::u16string_view host,
                    bool has_non_ascii,
                    bool has_escaped,
                    CanonOutput* output) {
@@ -337,102 +429,229 @@
     // very rare that host names have escaped characters, and it is relatively
     // fast to do the conversion anyway.
     StackBuffer utf8;
-    if (!ConvertUTF16ToUTF8(host, host_len, &utf8)) {
-      AppendInvalidNarrowString(host, 0, host_len, output);
+    if (!ConvertUTF16ToUTF8(host, &utf8)) {
+      AppendInvalidNarrowString(host, output);
       return false;
     }
 
     // Once we convert to UTF-8, we can use the 8-bit version of the complex
     // host handling code above.
-    return DoComplexHost(utf8.data(), utf8.length(), has_non_ascii, has_escaped,
-                         output);
+    return DoComplexHost<canon_mode>(utf8.view(), has_non_ascii, has_escaped,
+                                     output);
   }
 
   // No unescaping necessary, we can safely pass the input to ICU. This
   // function will only get called if we either have escaped or non-ascii
   // input, so it's safe to just use ICU now. Even if the input is ASCII,
   // this function will do the right thing (just slower than we could).
-  return DoIDNHost(host, host_len, output);
+  return DoIDNHost<canon_mode>(host.data(), host.length(), output);
 }
 
-template <typename CHAR, typename UCHAR>
-bool DoHostSubstring(const CHAR* spec,
-                     const Component& host,
+template <typename CHAR, typename UCHAR, CanonMode canon_mode>
+bool DoHostSubstring(const std::basic_string_view<CHAR> host,
                      CanonOutput* output) {
-  GURL_DCHECK(host.is_valid());
-
   bool has_non_ascii, has_escaped;
-  ScanHostname<CHAR, UCHAR>(spec, host, &has_non_ascii, &has_escaped);
+  ScanHostname<CHAR, UCHAR>(host, &has_non_ascii, &has_escaped);
 
   if (has_non_ascii || has_escaped) {
-    return DoComplexHost(&spec[host.begin], static_cast<size_t>(host.len),
-                         has_non_ascii, has_escaped, output);
+    return DoComplexHost<canon_mode>(host, has_non_ascii, has_escaped, output);
   }
 
-  const bool success = DoSimpleHost(
-      &spec[host.begin], static_cast<size_t>(host.len), output, &has_non_ascii);
+  const bool success = DoSimpleHost<canon_mode>(host, output, &has_non_ascii);
   GURL_DCHECK(!has_non_ascii);
   return success;
 }
 
-template <typename CHAR, typename UCHAR>
-void DoHost(const CHAR* spec,
+template <typename CharT>
+bool DoOpaqueHost(const std::basic_string_view<CharT> host,
+                  CanonOutput& output) {
+  // URL Standard: https://url.spec.whatwg.org/#concept-opaque-host-parser
+
+  size_t host_len = host.size();
+
+  for (size_t i = 0; i < host_len; ++i) {
+    char16_t ch = host[i];
+    // The characters '[', ':', and ']', are checked later in
+    // `CanonicalizeIPv6Address` function.
+    if (ch != '[' && ch != ']' && ch != ':' && IsForbiddenHostCodePoint(ch)) {
+      return false;
+    }
+
+    // Implementation note:
+    //
+    // URL Standard: Step 3 in
+    // https://url.spec.whatwg.org/#concept-opaque-host-parser
+    //
+    // > 3. If input contains a U+0025 (%) and the two code points following
+    // > it are not ASCII hex digits, invalid-URL-unit validation error.
+    //
+    // `invalid-URL-unit` is NOT marked as failure. We don't need to consider
+    // step 3 here.
+
+    // URL Standard: Step 4 in
+    // https://url.spec.whatwg.org/#concept-opaque-host-parser
+    //
+    // > 4. Return the result of running UTF-8 percent-encode on input using
+    // > the C0 control percent-encode set.
+    if (IsInC0ControlPercentEncodeSet(ch)) {
+      AppendUTF8EscapedChar(host.data(), &i, host_len, &output);
+    } else {
+      output.push_back(ch);
+    }
+  }
+  return true;
+}
+
+template <typename CHAR, typename UCHAR, CanonMode canon_mode>
+void DoHost(std::basic_string_view<CHAR> spec,
             const Component& host,
-            CanonOutput* output,
-            CanonHostInfo* host_info) {
+            CanonOutput& output,
+            CanonHostInfo& host_info) {
+  // URL Standard: https://url.spec.whatwg.org/#host-parsing
+
+  // Keep track of output's initial length, so we can rewind later.
+  const size_t output_begin = output.length();
+
   if (host.is_empty()) {
     // Empty hosts don't need anything.
-    host_info->family = CanonHostInfo::NEUTRAL;
-    host_info->out_host = Component();
+    host_info.family = CanonHostInfo::NEUTRAL;
+    // Carry over the valid empty host for non-special URLs.
+    //
+    // Component(0, 0) should be considered invalid here for historical reasons.
+    //
+    // TODO(crbug.com/40063064): Update the callers so that they don't pass
+    // Component(0, 0) as an invalid `host`.
+    if (host.begin != 0 && host.len == 0) {
+      host_info.out_host = Component(output_begin, 0);
+    } else {
+      host_info.out_host = Component();
+    }
     return;
   }
 
-  // Keep track of output's initial length, so we can rewind later.
-  const int output_begin = output->length();
+  std::basic_string_view<CHAR> host_view = host.AsViewOn(spec);
+  bool success;
+  if constexpr (canon_mode == CanonMode::kSpecialURL ||
+                canon_mode == CanonMode::kFileURL) {
+    success = DoHostSubstring<CHAR, UCHAR, canon_mode>(host_view, &output);
+  } else {
+    // URL Standard: https://url.spec.whatwg.org/#concept-opaque-host-parser
+    success = DoOpaqueHost(host_view, output);
+  }
 
-  if (DoHostSubstring<CHAR, UCHAR>(spec, host, output)) {
+  if (success) {
     // After all the other canonicalization, check if we ended up with an IP
     // address. IP addresses are small, so writing into this temporary buffer
     // should not cause an allocation.
     RawCanonOutput<64> canon_ip;
-    CanonicalizeIPAddress(output->data(),
-                          MakeRange(output_begin, output->length()),
-                          &canon_ip, host_info);
+
+    std::string_view output_view =
+        output.view().substr(output_begin, output.length() - output_begin);
+    if constexpr (canon_mode == CanonMode::kSpecialURL ||
+                  canon_mode == CanonMode::kFileURL) {
+      CanonicalizeIPAddress(output_view, &canon_ip, &host_info);
+    } else {
+      // Non-special URLs support only IPv6.
+      CanonicalizeIPv6Address(output_view, canon_ip, host_info);
+    }
 
     // If we got an IPv4/IPv6 address, copy the canonical form back to the
     // real buffer. Otherwise, it's a hostname or broken IP, in which case
     // we just leave it in place.
-    if (host_info->IsIPAddress()) {
-      output->set_length(output_begin);
-      output->Append(canon_ip.view());
+    if (host_info.IsIPAddress()) {
+      output.set_length(output_begin);
+      output.Append(canon_ip.view());
     }
   } else {
     // Canonicalization failed. Set BROKEN to notify the caller.
-    host_info->family = CanonHostInfo::BROKEN;
+    host_info.family = CanonHostInfo::BROKEN;
   }
-
-  host_info->out_host = MakeRange(output_begin, output->length());
+  host_info.out_host = MakeRange(output_begin, output.length());
 }
 
 }  // namespace
 
-bool CanonicalizeHost(const char* spec,
+bool CanonicalizeHost(std::string_view spec,
                       const Component& host,
                       CanonOutput* output,
                       Component* out_host) {
+  GURL_DCHECK(output);
+  GURL_DCHECK(out_host);
+  return CanonicalizeSpecialHost(spec, host, *output, *out_host);
+}
+
+bool CanonicalizeHost(std::u16string_view spec,
+                      const Component& host,
+                      CanonOutput* output,
+                      Component* out_host) {
+  GURL_DCHECK(output);
+  GURL_DCHECK(out_host);
+  return CanonicalizeSpecialHost(spec, host, *output, *out_host);
+}
+
+bool CanonicalizeSpecialHost(std::string_view spec,
+                             const Component& host,
+                             CanonOutput& output,
+                             Component& out_host) {
   CanonHostInfo host_info;
-  DoHost<char, unsigned char>(spec, host, output, &host_info);
-  *out_host = host_info.out_host;
+  DoHost<char, unsigned char, CanonMode::kSpecialURL>(spec, host, output,
+                                                      host_info);
+  out_host = host_info.out_host;
   return (host_info.family != CanonHostInfo::BROKEN);
 }
 
-bool CanonicalizeHost(const char16_t* spec,
-                      const Component& host,
-                      CanonOutput* output,
-                      Component* out_host) {
+bool CanonicalizeSpecialHost(std::u16string_view spec,
+                             const Component& host,
+                             CanonOutput& output,
+                             Component& out_host) {
   CanonHostInfo host_info;
-  DoHost<char16_t, char16_t>(spec, host, output, &host_info);
-  *out_host = host_info.out_host;
+  DoHost<char16_t, char16_t, CanonMode::kSpecialURL>(spec, host, output,
+                                                     host_info);
+  out_host = host_info.out_host;
+  return (host_info.family != CanonHostInfo::BROKEN);
+}
+
+bool CanonicalizeFileHost(std::string_view spec,
+                          const Component& host,
+                          CanonOutput& output,
+                          Component& out_host) {
+  CanonHostInfo host_info;
+  DoHost<char, unsigned char, CanonMode::kFileURL>(spec, host, output,
+                                                   host_info);
+  out_host = host_info.out_host;
+  return (host_info.family != CanonHostInfo::BROKEN);
+}
+
+bool CanonicalizeFileHost(std::u16string_view spec,
+                          const Component& host,
+                          CanonOutput& output,
+                          Component& out_host) {
+  CanonHostInfo host_info;
+  DoHost<char16_t, char16_t, CanonMode::kFileURL>(spec, host, output,
+                                                  host_info);
+  out_host = host_info.out_host;
+  return (host_info.family != CanonHostInfo::BROKEN);
+}
+
+bool CanonicalizeNonSpecialHost(std::string_view spec,
+                                const Component& host,
+                                CanonOutput& output,
+                                Component& out_host) {
+  CanonHostInfo host_info;
+  DoHost<char, unsigned char, CanonMode::kNonSpecialURL>(spec, host, output,
+                                                         host_info);
+  out_host = host_info.out_host;
+  return (host_info.family != CanonHostInfo::BROKEN);
+}
+
+bool CanonicalizeNonSpecialHost(std::u16string_view spec,
+                                const Component& host,
+                                CanonOutput& output,
+                                Component& out_host) {
+  CanonHostInfo host_info;
+  DoHost<char16_t, char16_t, CanonMode::kNonSpecialURL>(spec, host, output,
+                                                        host_info);
+  out_host = host_info.out_host;
   return (host_info.family != CanonHostInfo::BROKEN);
 }
 
@@ -440,26 +659,87 @@
                              const Component& host,
                              CanonOutput* output,
                              CanonHostInfo* host_info) {
-  DoHost<char, unsigned char>(spec, host, output, host_info);
+  CanonicalizeHostVerbose(
+      std::string_view(spec, host.is_valid() ? host.end() : 0), host, output,
+      host_info);
 }
 
-void CanonicalizeHostVerbose(const char16_t* spec,
+void CanonicalizeHostVerbose(std::string_view spec,
                              const Component& host,
                              CanonOutput* output,
                              CanonHostInfo* host_info) {
-  DoHost<char16_t, char16_t>(spec, host, output, host_info);
+  GURL_DCHECK(output);
+  GURL_DCHECK(host_info);
+  CanonicalizeSpecialHostVerbose(spec, host, *output, *host_info);
 }
 
-bool CanonicalizeHostSubstring(const char* spec,
-                               const Component& host,
-                               CanonOutput* output) {
-  return DoHostSubstring<char, unsigned char>(spec, host, output);
+void CanonicalizeHostVerbose(std::u16string_view spec,
+                             const Component& host,
+                             CanonOutput* output,
+                             CanonHostInfo* host_info) {
+  GURL_DCHECK(output);
+  GURL_DCHECK(host_info);
+  CanonicalizeSpecialHostVerbose(spec, host, *output, *host_info);
 }
 
-bool CanonicalizeHostSubstring(const char16_t* spec,
-                               const Component& host,
+void CanonicalizeSpecialHostVerbose(std::string_view spec,
+                                    const Component& host,
+                                    CanonOutput& output,
+                                    CanonHostInfo& host_info) {
+  DoHost<char, unsigned char, CanonMode::kSpecialURL>(spec, host, output,
+                                                      host_info);
+}
+
+void CanonicalizeSpecialHostVerbose(std::u16string_view spec,
+                                    const Component& host,
+                                    CanonOutput& output,
+                                    CanonHostInfo& host_info) {
+  DoHost<char16_t, char16_t, CanonMode::kSpecialURL>(spec, host, output,
+                                                     host_info);
+}
+
+void CanonicalizeFileHostVerbose(std::string_view spec,
+                                 const Component& host,
+                                 CanonOutput& output,
+                                 CanonHostInfo& host_info) {
+  DoHost<char, unsigned char, CanonMode::kFileURL>(spec, host, output,
+                                                   host_info);
+}
+
+void CanonicalizeFileHostVerbose(std::u16string_view spec,
+                                 const Component& host,
+                                 CanonOutput& output,
+                                 CanonHostInfo& host_info) {
+  DoHost<char16_t, char16_t, CanonMode::kFileURL>(spec, host, output,
+                                                  host_info);
+}
+
+bool CanonicalizeHostSubstring(std::string_view host_view,
                                CanonOutput* output) {
-  return DoHostSubstring<char16_t, char16_t>(spec, host, output);
+  return DoHostSubstring<char, unsigned char, CanonMode::kSpecialURL>(host_view,
+                                                                      output);
+}
+
+bool CanonicalizeHostSubstring(std::u16string_view host_view,
+                               CanonOutput* output) {
+  return DoHostSubstring<char16_t, char16_t, CanonMode::kSpecialURL>(host_view,
+                                                                     output);
+}
+
+void CanonicalizeNonSpecialHostVerbose(std::string_view spec,
+                                       const Component& host,
+                                       CanonOutput& output,
+                                       CanonHostInfo& host_info) {
+  DoHost<char, unsigned char, CanonMode::kNonSpecialURL>(spec, host, output,
+                                                         host_info);
+}
+
+void CanonicalizeNonSpecialHostVerbose(std::u16string_view spec,
+                                       const Component& host,
+                                       CanonOutput& output,
+                                       CanonHostInfo& host_info) {
+  DoHost<char16_t, char16_t, CanonMode::kNonSpecialURL>(spec, host, output,
+                                                        host_info);
 }
 
 }  // namespace url
diff --git a/url/url_canon_icu.cc b/url/url_canon_icu.cc
index b5ebf9a..7c5edd9 100644
--- a/url/url_canon_icu.cc
+++ b/url/url_canon_icu.cc
@@ -4,17 +4,19 @@
 
 // ICU-based character set converter.
 
+#include "url/url_canon_icu.h"
+
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "polyfills/base/check.h"
-#include "polyfills/base/memory/raw_ptr.h"
-#include "polyfills/base/memory/raw_ptr_exclusion.h"
+#include "base/compiler_specific.h"
+#include "base/memory/stack_allocated.h"
+#include "base/numerics/safe_conversions.h"
 #include <unicode/ucnv.h>
 #include <unicode/ucnv_cb.h>
 #include <unicode/utypes.h>
-#include "url/url_canon_icu.h"
 #include "url/url_canon_internal.h"  // for _itoa_s
 
 namespace url {
@@ -53,6 +55,8 @@
 
 // A class for scoping the installation of the invalid character callback.
 class AppendHandlerInstaller {
+  STACK_ALLOCATED();
+
  public:
   // The owner of this object must ensure that the converter is alive for the
   // duration of this object's lifetime.
@@ -68,12 +72,10 @@
   }
 
  private:
-  raw_ptr<UConverter> converter_;
+  UConverter* converter_;
 
   UConverterFromUCallback old_callback_;
-  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
-  // #addr-of
-  RAW_PTR_EXCLUSION const void* old_context_;
+  const void* old_context_;
 };
 
 }  // namespace
@@ -84,8 +86,7 @@
 
 ICUCharsetConverter::~ICUCharsetConverter() = default;
 
-void ICUCharsetConverter::ConvertFromUTF16(const char16_t* input,
-                                           int input_len,
+void ICUCharsetConverter::ConvertFromUTF16(std::u16string_view input,
                                            CanonOutput* output) {
   // Install our error handler. It will be called for character that can not
   // be represented in the destination character set.
@@ -97,9 +98,10 @@
 
   do {
     UErrorCode err = U_ZERO_ERROR;
-    char* dest = &output->data()[begin_offset];
-    int required_capacity = ucnv_fromUChars(converter_, dest, dest_capacity,
-                                            input, input_len, &err);
+    char* dest = &UNSAFE_TODO(output->data()[begin_offset]);
+    int required_capacity =
+        ucnv_fromUChars(converter_, dest, dest_capacity, input.data(),
+                        gurl_base::checked_cast<int32_t>(input.size()), &err);
     if (err != U_BUFFER_OVERFLOW_ERROR) {
       output->set_length(begin_offset + required_capacity);
       return;
diff --git a/url/url_canon_icu.h b/url/url_canon_icu.h
index 9b64722..3360be3 100644
--- a/url/url_canon_icu.h
+++ b/url/url_canon_icu.h
@@ -27,8 +27,7 @@
 
   ~ICUCharsetConverter() override;
 
-  void ConvertFromUTF16(const char16_t* input,
-                        int input_len,
+  void ConvertFromUTF16(std::u16string_view input,
                         CanonOutput* output) override;
 
  private:
diff --git a/url/url_canon_icu_test_helpers.h b/url/url_canon_icu_test_helpers.h
new file mode 100644
index 0000000..a19cd8c
--- /dev/null
+++ b/url/url_canon_icu_test_helpers.h
@@ -0,0 +1,41 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef URL_URL_CANON_ICU_TEST_HELPERS_H_
+#define URL_URL_CANON_ICU_TEST_HELPERS_H_
+
+#include "polyfills/base/logging.h"
+#include <unicode/ucnv.h>
+#include "url/url_canon.h"
+
+namespace url::test {
+
+// Wrapper around a UConverter object that managers creation and destruction.
+class UConvScoper {
+ public:
+  explicit UConvScoper(const char* charset_name) {
+    UErrorCode err = U_ZERO_ERROR;
+    converter_ = ucnv_open(charset_name, &err);
+    if (!converter_) {
+      GURL_LOG(ERROR) << "Failed to open charset " << charset_name << ": "
+                 << u_errorName(err);
+    }
+  }
+
+  ~UConvScoper() {
+    if (converter_) {
+      ucnv_close(converter_.ExtractAsDangling());
+    }
+  }
+
+  // Returns the converter object, may be NULL.
+  UConverter* converter() const { return converter_; }
+
+ private:
+  raw_ptr<UConverter> converter_;
+};
+
+}  // namespace url::test
+
+#endif  // URL_URL_CANON_ICU_TEST_HELPERS_H_
diff --git a/url/url_canon_icu_unittest.cc b/url/url_canon_icu_unittest.cc
index 46b7ae0..fd59010 100644
--- a/url/url_canon_icu_unittest.cc
+++ b/url/url_canon_icu_unittest.cc
@@ -6,11 +6,14 @@
 
 #include <stddef.h>
 
-#include "polyfills/base/logging.h"
+#include <array>
+
 #include "polyfills/base/memory/raw_ptr.h"
+#include "base/strings/string_view_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include <unicode/ucnv.h>
 #include "url/url_canon.h"
+#include "url/url_canon_icu_test_helpers.h"
 #include "url/url_canon_stdstring.h"
 #include "url/url_test_utils.h"
 
@@ -18,50 +21,27 @@
 
 namespace {
 
-// Wrapper around a UConverter object that managers creation and destruction.
-class UConvScoper {
- public:
-  explicit UConvScoper(const char* charset_name) {
-    UErrorCode err = U_ZERO_ERROR;
-    converter_ = ucnv_open(charset_name, &err);
-    if (!converter_) {
-      GURL_LOG(ERROR) << "Failed to open charset " << charset_name << ": "
-                 << u_errorName(err);
-    }
-  }
-
-  ~UConvScoper() {
-    if (converter_)
-      ucnv_close(converter_.ExtractAsDangling());
-  }
-
-  // Returns the converter object, may be NULL.
-  UConverter* converter() const { return converter_; }
-
- private:
-  raw_ptr<UConverter> converter_;
-};
-
 TEST(URLCanonIcuTest, ICUCharsetConverter) {
   struct ICUCase {
     const wchar_t* input;
     const char* encoding;
     const char* expected;
-  } icu_cases[] = {
-      // UTF-8.
-    {L"Hello, world", "utf-8", "Hello, world"},
-    {L"\x4f60\x597d", "utf-8", "\xe4\xbd\xa0\xe5\xa5\xbd"},
-      // Non-BMP UTF-8.
-    {L"!\xd800\xdf00!", "utf-8", "!\xf0\x90\x8c\x80!"},
-      // Big5
-    {L"\x4f60\x597d", "big5", "\xa7\x41\xa6\x6e"},
-      // Unrepresentable character in the destination set.
-    {L"hello\x4f60\x06de\x597dworld", "big5",
-      "hello\xa7\x41%26%231758%3B\xa6\x6eworld"},
   };
+  auto icu_cases = std::to_array<ICUCase>({
+      // UTF-8.
+      {L"Hello, world", "utf-8", "Hello, world"},
+      {L"\x4f60\x597d", "utf-8", "\xe4\xbd\xa0\xe5\xa5\xbd"},
+      // Non-BMP UTF-8.
+      {L"!\xd800\xdf00!", "utf-8", "!\xf0\x90\x8c\x80!"},
+      // Big5
+      {L"\x4f60\x597d", "big5", "\xa7\x41\xa6\x6e"},
+      // Unrepresentable character in the destination set.
+      {L"hello\x4f60\x06de\x597dworld", "big5",
+       "hello\xa7\x41%26%231758%3B\xa6\x6eworld"},
+  });
 
   for (size_t i = 0; i < std::size(icu_cases); i++) {
-    UConvScoper conv(icu_cases[i].encoding);
+    test::UConvScoper conv(icu_cases[i].encoding);
     ASSERT_TRUE(conv.converter() != NULL);
     ICUCharsetConverter converter(conv.converter());
 
@@ -70,8 +50,7 @@
 
     std::u16string input_str(
         test_utils::TruncateWStringToUTF16(icu_cases[i].input));
-    int input_len = static_cast<int>(input_str.length());
-    converter.ConvertFromUTF16(input_str.c_str(), input_len, &output);
+    converter.ConvertFromUTF16(input_str, &output);
     output.Complete();
 
     EXPECT_STREQ(icu_cases[i].expected, str.c_str());
@@ -80,7 +59,7 @@
   // Test string sizes around the resize boundary for the output to make sure
   // the converter resizes as needed.
   const int static_size = 16;
-  UConvScoper conv("utf-8");
+  test::UConvScoper conv("utf-8");
   ASSERT_TRUE(conv.converter());
   ICUCharsetConverter converter(conv.converter());
   for (int i = static_size - 2; i <= static_size + 2; i++) {
@@ -90,8 +69,7 @@
       input.push_back('a');
 
     RawCanonOutput<static_size> output;
-    converter.ConvertFromUTF16(input.c_str(), static_cast<int>(input.length()),
-                               &output);
+    converter.ConvertFromUTF16(input, &output);
     EXPECT_EQ(input.length(), output.length());
   }
 }
@@ -102,26 +80,27 @@
     const wchar_t* input16;
     const char* encoding;
     const char* expected;
-  } query_cases[] = {
+  };
+  auto query_cases = std::to_array<QueryCase>({
       // Regular ASCII case in some different encodings.
-    {"foo=bar", L"foo=bar", "utf-8", "?foo=bar"},
-    {"foo=bar", L"foo=bar", "shift_jis", "?foo=bar"},
-    {"foo=bar", L"foo=bar", "gb2312", "?foo=bar"},
+      {"foo=bar", L"foo=bar", "utf-8", "?foo=bar"},
+      {"foo=bar", L"foo=bar", "shift_jis", "?foo=bar"},
+      {"foo=bar", L"foo=bar", "gb2312", "?foo=bar"},
       // Chinese input/output
-    {"q=\xe4\xbd\xa0\xe5\xa5\xbd", L"q=\x4f60\x597d", "gb2312",
-      "?q=%C4%E3%BA%C3"},
-    {"q=\xe4\xbd\xa0\xe5\xa5\xbd", L"q=\x4f60\x597d", "big5", "?q=%A7A%A6n"},
+      {"q=\xe4\xbd\xa0\xe5\xa5\xbd", L"q=\x4f60\x597d", "gb2312",
+       "?q=%C4%E3%BA%C3"},
+      {"q=\xe4\xbd\xa0\xe5\xa5\xbd", L"q=\x4f60\x597d", "big5", "?q=%A7A%A6n"},
       // Unencodable character in the destination character set should be
       // escaped. The escape sequence unescapes to be the entity name:
       // "?q=&#20320;"
-    {"q=Chinese\xef\xbc\xa7", L"q=Chinese\xff27", "iso-8859-1",
-      "?q=Chinese%26%2365319%3B"},
-  };
+      {"q=Chinese\xef\xbc\xa7", L"q=Chinese\xff27", "iso-8859-1",
+       "?q=Chinese%26%2365319%3B"},
+  });
 
   for (size_t i = 0; i < std::size(query_cases); i++) {
     Component out_comp;
 
-    UConvScoper conv(query_cases[i].encoding);
+    test::UConvScoper conv(query_cases[i].encoding);
     ASSERT_TRUE(!query_cases[i].encoding || conv.converter());
     ICUCharsetConverter converter(conv.converter());
 
@@ -131,8 +110,8 @@
       std::string out_str;
 
       StdStringCanonOutput output(&out_str);
-      CanonicalizeQuery(query_cases[i].input8, in_comp, &converter, &output,
-                        &out_comp);
+      CanonicalizeQuery(in_comp.as_string_view_on(query_cases[i].input8),
+                        &converter, &output, &out_comp);
       output.Complete();
 
       EXPECT_EQ(query_cases[i].expected, out_str);
@@ -146,7 +125,7 @@
       std::string out_str;
 
       StdStringCanonOutput output(&out_str);
-      CanonicalizeQuery(input16.c_str(), in_comp, &converter, &output,
+      CanonicalizeQuery(in_comp.AsViewOn(input16), &converter, &output,
                         &out_comp);
       output.Complete();
 
@@ -158,7 +137,8 @@
   std::string out_str;
   StdStringCanonOutput output(&out_str);
   Component out_comp;
-  CanonicalizeQuery("a \x00z\x01", Component(0, 5), NULL, &output, &out_comp);
+  CanonicalizeQuery(gurl_base::MakeStringViewWithNulChars("a \x00z\x01"), nullptr,
+                    &output, &out_comp);
   output.Complete();
   EXPECT_EQ("?a%20%00z%01", out_str);
 }
diff --git a/url/url_canon_internal.cc b/url/url_canon_internal.cc
index 76a45d0..1c0d667 100644
--- a/url/url_canon_internal.cc
+++ b/url/url_canon_internal.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "url/url_canon_internal.h"
 
 #include <errno.h>
@@ -19,6 +24,7 @@
 #include "base/bits.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/utf_string_conversion_utils.h"
+#include "url/url_features.h"
 
 namespace url {
 
@@ -33,13 +39,14 @@
 //
 // This has some startup cost to load the constants and such, so it's
 // usually not worth it for short strings.
-size_t FindInitialQuerySafeString(const char* source, size_t length) {
+size_t FindInitialQuerySafeString(std::string_view source) {
 #if defined(__SSE2__) || defined(__aarch64__)
   constexpr size_t kChunkSize = 16;
   size_t i;
-  for (i = 0; i < gurl_base::bits::AlignDown(length, kChunkSize); i += kChunkSize) {
+  for (i = 0; i < gurl_base::bits::AlignDown(source.length(), kChunkSize);
+       i += kChunkSize) {
     char b __attribute__((vector_size(16)));
-    memcpy(&b, source + i, sizeof(b));
+    memcpy(&b, source.data() + i, sizeof(b));
 
     // Compare each element with the ranges for CHAR_QUERY
     // (see kSharedCharTypeTable), vectorized so that it creates
@@ -66,17 +73,17 @@
 }
 
 template <typename CHAR, typename UCHAR>
-void DoAppendStringOfType(const CHAR* source,
-                          size_t length,
+void DoAppendStringOfType(std::basic_string_view<CHAR> source,
                           SharedCharTypes type,
                           CanonOutput* output) {
   size_t i = 0;
+  size_t length = source.length();
   // We only instantiate this for char, to avoid a Clang crash
   // (and because Append() does not support converting).
   if constexpr (sizeof(CHAR) == 1) {
     if (type == CHAR_QUERY && length >= kMinimumLengthForSIMD) {
-      i = FindInitialQuerySafeString(source, length);
-      output->Append(source, i);
+      i = FindInitialQuerySafeString(source);
+      output->Append(source.data(), i);
     }
   }
   for (; i < length; i++) {
@@ -85,7 +92,7 @@
       // kUnicodeReplacementCharacter when the input is invalid, which is what
       // we want.
       base_icu::UChar32 code_point;
-      ReadUTFCharLossy(source, &i, length, &code_point);
+      ReadUTFCharLossy(source.data(), &i, length, &code_point);
       AppendUTF8EscapedValue(code_point, output);
     } else {
       // Just append the 7-bit character, possibly escaping it.
@@ -101,16 +108,15 @@
 // This function assumes the input values are all contained in 8-bit,
 // although it allows any type. Returns true if input is valid, false if not.
 template <typename CHAR, typename UCHAR>
-void DoAppendInvalidNarrowString(const CHAR* spec,
-                                 size_t begin,
-                                 size_t end,
+void DoAppendInvalidNarrowString(std::basic_string_view<CHAR> input,
                                  CanonOutput* output) {
-  for (size_t i = begin; i < end; i++) {
-    UCHAR uch = static_cast<UCHAR>(spec[i]);
+  size_t end = input.length();
+  for (size_t i = 0; i < end; ++i) {
+    UCHAR uch = static_cast<UCHAR>(input[i]);
     if (uch >= 0x80) {
       // Handle UTF-8/16 encodings. This call will correctly handle the error
       // case by appending the invalid character.
-      AppendUTF8EscapedChar(spec, &i, end, output);
+      AppendUTF8EscapedChar(input.data(), &i, end, output);
     } else if (uch <= ' ' || uch == 0x7f) {
       // This function is for error handling, so we escape all control
       // characters and spaces, but not anything else since we lack
@@ -146,141 +152,32 @@
 // may get resized while we're overriding a subsequent component. Instead, the
 // caller should use the beginning of the |utf8_buffer| as the string pointer
 // for all components once all overrides have been prepared.
-bool PrepareUTF16OverrideComponent(const char16_t* override_source,
-                                   const Component& override_component,
-                                   CanonOutput* utf8_buffer,
-                                   Component* dest_component) {
+bool PrepareUTF16OverrideComponent(
+    bool should_override,
+    std::optional<std::u16string_view> override_source,
+    CanonOutput* utf8_buffer,
+    Component* dest_component) {
   bool success = true;
-  if (override_source) {
-    if (!override_component.is_valid()) {
+
+  if (should_override) {
+    if (!override_source.has_value()) {
       // Non-"valid" component (means delete), so we need to preserve that.
       *dest_component = Component();
     } else {
+      auto override_source_value = override_source.value();
+
       // Convert to UTF-8.
       dest_component->begin = utf8_buffer->length();
-      success = ConvertUTF16ToUTF8(&override_source[override_component.begin],
-                                   static_cast<size_t>(override_component.len),
-                                   utf8_buffer);
+      success = ConvertUTF16ToUTF8(override_source_value, utf8_buffer);
       dest_component->len = utf8_buffer->length() - dest_component->begin;
     }
   }
+
   return success;
 }
 
 }  // namespace
 
-// See the header file for this array's declaration.
-// clang-format off
-const unsigned char kSharedCharTypeTable[0x100] = {
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x00 - 0x0f
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x10 - 0x1f
-    0,                           // 0x20  ' ' (escape spaces in queries)
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x21  !
-    0,                           // 0x22  "
-    0,                           // 0x23  #  (invalid in query since it marks the ref)
-    CHAR_QUERY | CHAR_USERINFO,  // 0x24  $
-    CHAR_QUERY | CHAR_USERINFO,  // 0x25  %
-    CHAR_QUERY | CHAR_USERINFO,  // 0x26  &
-    0,                           // 0x27  '  (Try to prevent XSS.)
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x28  (
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x29  )
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x2a  *
-    CHAR_QUERY | CHAR_USERINFO,  // 0x2b  +
-    CHAR_QUERY | CHAR_USERINFO,  // 0x2c  ,
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x2d  -
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_COMPONENT,  // 0x2e  .
-    CHAR_QUERY,                  // 0x2f  /
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x30  0
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x31  1
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x32  2
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x33  3
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x34  4
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x35  5
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x36  6
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x37  7
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_COMPONENT,             // 0x38  8
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_COMPONENT,             // 0x39  9
-    CHAR_QUERY,  // 0x3a  :
-    CHAR_QUERY,  // 0x3b  ;
-    0,           // 0x3c  <  (Try to prevent certain types of XSS.)
-    CHAR_QUERY,  // 0x3d  =
-    0,           // 0x3e  >  (Try to prevent certain types of XSS.)
-    CHAR_QUERY,  // 0x3f  ?
-    CHAR_QUERY,  // 0x40  @
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x41  A
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x42  B
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x43  C
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x44  D
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x45  E
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x46  F
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x47  G
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x48  H
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x49  I
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4a  J
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4b  K
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4c  L
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4d  M
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4e  N
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4f  O
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x50  P
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x51  Q
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x52  R
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x53  S
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x54  T
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x55  U
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x56  V
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x57  W
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_COMPONENT, // 0x58  X
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x59  Y
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x5a  Z
-    CHAR_QUERY,  // 0x5b  [
-    CHAR_QUERY,  // 0x5c  '\'
-    CHAR_QUERY,  // 0x5d  ]
-    CHAR_QUERY,  // 0x5e  ^
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x5f  _
-    CHAR_QUERY,  // 0x60  `
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x61  a
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x62  b
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x63  c
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x64  d
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x65  e
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x66  f
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x67  g
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x68  h
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x69  i
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6a  j
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6b  k
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6c  l
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6d  m
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6e  n
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6f  o
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x70  p
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x71  q
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x72  r
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x73  s
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x74  t
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x75  u
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x76  v
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x77  w
-    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_COMPONENT,  // 0x78  x
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x79  y
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x7a  z
-    CHAR_QUERY,  // 0x7b  {
-    CHAR_QUERY,  // 0x7c  |
-    CHAR_QUERY,  // 0x7d  }
-    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x7e  ~
-    0,           // 0x7f
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x80 - 0x8f
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x90 - 0x9f
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xa0 - 0xaf
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xb0 - 0xbf
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xc0 - 0xcf
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xd0 - 0xdf
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xe0 - 0xef
-    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xf0 - 0xff
-};
-// clang-format on
-
 const char kCharToHexLookup[8] = {
     0,         // 0x00 - 0x1f
     '0',       // 0x20 - 0x3f: digits 0 - 9 are 0x30 - 0x39
@@ -294,18 +191,16 @@
 
 const base_icu::UChar32 kUnicodeReplacementCharacter = 0xfffd;
 
-void AppendStringOfType(const char* source,
-                        size_t length,
+void AppendStringOfType(std::string_view source,
                         SharedCharTypes type,
                         CanonOutput* output) {
-  DoAppendStringOfType<char, unsigned char>(source, length, type, output);
+  DoAppendStringOfType<char, unsigned char>(source, type, output);
 }
 
-void AppendStringOfType(const char16_t* source,
-                        size_t length,
+void AppendStringOfType(std::u16string_view source,
                         SharedCharTypes type,
                         CanonOutput* output) {
-  DoAppendStringOfType<char16_t, char16_t>(source, length, type, output);
+  DoAppendStringOfType<char16_t, char16_t>(source, type, output);
 }
 
 bool ReadUTFCharLossy(const char* str,
@@ -330,39 +225,30 @@
   return true;
 }
 
-void AppendInvalidNarrowString(const char* spec,
-                               size_t begin,
-                               size_t end,
-                               CanonOutput* output) {
-  DoAppendInvalidNarrowString<char, unsigned char>(spec, begin, end, output);
+void AppendInvalidNarrowString(std::string_view input, CanonOutput* output) {
+  DoAppendInvalidNarrowString<char, unsigned char>(input, output);
 }
 
-void AppendInvalidNarrowString(const char16_t* spec,
-                               size_t begin,
-                               size_t end,
-                               CanonOutput* output) {
-  DoAppendInvalidNarrowString<char16_t, char16_t>(spec, begin, end, output);
+void AppendInvalidNarrowString(std::u16string_view input, CanonOutput* output) {
+  DoAppendInvalidNarrowString<char16_t, char16_t>(input, output);
 }
 
-bool ConvertUTF16ToUTF8(const char16_t* input,
-                        size_t input_len,
-                        CanonOutput* output) {
+bool ConvertUTF16ToUTF8(std::u16string_view input, CanonOutput* output) {
   bool success = true;
-  for (size_t i = 0; i < input_len; i++) {
+  for (size_t i = 0; i < input.length(); i++) {
     base_icu::UChar32 code_point;
-    success &= ReadUTFCharLossy(input, &i, input_len, &code_point);
+    success &= ReadUTFCharLossy(input.data(), &i, input.length(), &code_point);
     AppendUTF8Value(code_point, output);
   }
   return success;
 }
 
-bool ConvertUTF8ToUTF16(const char* input,
-                        size_t input_len,
+bool ConvertUTF8ToUTF16(std::string_view input,
                         CanonOutputT<char16_t>* output) {
   bool success = true;
-  for (size_t i = 0; i < input_len; i++) {
+  for (size_t i = 0; i < input.length(); i++) {
     base_icu::UChar32 code_point;
-    success &= ReadUTFCharLossy(input, &i, input_len, &code_point);
+    success &= ReadUTFCharLossy(input.data(), &i, input.length(), &code_point);
     AppendUTF16Value(code_point, output);
   }
   return success;
@@ -382,13 +268,8 @@
                       &source->username, &parsed->username);
   DoOverrideComponent(repl_source.password, repl_parsed.password,
                       &source->password, &parsed->password);
-
-  // Our host should be empty if not present, so override the default setup.
   DoOverrideComponent(repl_source.host, repl_parsed.host, &source->host,
                       &parsed->host);
-  if (parsed->host.len == -1)
-    parsed->host.len = 0;
-
   DoOverrideComponent(repl_source.port, repl_parsed.port, &source->port,
                       &parsed->port);
   DoOverrideComponent(repl_source.path, repl_parsed.path, &source->path,
@@ -411,23 +292,37 @@
   const Parsed& repl_parsed = repl.components();
 
   success &= PrepareUTF16OverrideComponent(
-      repl_source.scheme, repl_parsed.scheme, utf8_buffer, &parsed->scheme);
-  success &=
-      PrepareUTF16OverrideComponent(repl_source.username, repl_parsed.username,
-                                    utf8_buffer, &parsed->username);
-  success &=
-      PrepareUTF16OverrideComponent(repl_source.password, repl_parsed.password,
-                                    utf8_buffer, &parsed->password);
-  success &= PrepareUTF16OverrideComponent(repl_source.host, repl_parsed.host,
-                                           utf8_buffer, &parsed->host);
-  success &= PrepareUTF16OverrideComponent(repl_source.port, repl_parsed.port,
-                                           utf8_buffer, &parsed->port);
-  success &= PrepareUTF16OverrideComponent(repl_source.path, repl_parsed.path,
-                                           utf8_buffer, &parsed->path);
-  success &= PrepareUTF16OverrideComponent(repl_source.query, repl_parsed.query,
-                                           utf8_buffer, &parsed->query);
-  success &= PrepareUTF16OverrideComponent(repl_source.ref, repl_parsed.ref,
-                                           utf8_buffer, &parsed->ref);
+      repl_source.scheme != nullptr,
+      repl_parsed.scheme.maybe_as_string_view_on(repl_source.scheme),
+      utf8_buffer, &parsed->scheme);
+  success &= PrepareUTF16OverrideComponent(
+      repl_source.username != nullptr,
+      repl_parsed.username.maybe_as_string_view_on(repl_source.username),
+      utf8_buffer, &parsed->username);
+  success &= PrepareUTF16OverrideComponent(
+      repl_source.password != nullptr,
+      repl_parsed.password.maybe_as_string_view_on(repl_source.password),
+      utf8_buffer, &parsed->password);
+  success &= PrepareUTF16OverrideComponent(
+      repl_source.host != nullptr,
+      repl_parsed.host.maybe_as_string_view_on(repl_source.host), utf8_buffer,
+      &parsed->host);
+  success &= PrepareUTF16OverrideComponent(
+      repl_source.port != nullptr,
+      repl_parsed.port.maybe_as_string_view_on(repl_source.port), utf8_buffer,
+      &parsed->port);
+  success &= PrepareUTF16OverrideComponent(
+      repl_source.path != nullptr,
+      repl_parsed.path.maybe_as_string_view_on(repl_source.path), utf8_buffer,
+      &parsed->path);
+  success &= PrepareUTF16OverrideComponent(
+      repl_source.query != nullptr,
+      repl_parsed.query.maybe_as_string_view_on(repl_source.query), utf8_buffer,
+      &parsed->query);
+  success &= PrepareUTF16OverrideComponent(
+      repl_source.ref != nullptr,
+      repl_parsed.ref.maybe_as_string_view_on(repl_source.ref), utf8_buffer,
+      &parsed->ref);
 
   // PrepareUTF16OverrideComponent will not have set the data pointer since the
   // buffer could be resized, invalidating the pointers. We set the data
@@ -471,26 +366,6 @@
   return 0;
 }
 
-int _itow_s(int value, char16_t* buffer, size_t size_in_chars, int radix) {
-  if (radix != 10)
-    return EINVAL;
-
-  // No more than 12 characters will be required for a 32-bit integer.
-  // Add an extra byte for the terminating null.
-  char temp[13];
-  int written = snprintf(temp, sizeof(temp), "%d", value);
-  if (static_cast<size_t>(written) >= size_in_chars) {
-    // Output was truncated, or written was negative.
-    return EINVAL;
-  }
-
-  for (int i = 0; i < written; ++i) {
-    buffer[i] = static_cast<char16_t>(temp[i]);
-  }
-  buffer[written] = '\0';
-  return 0;
-}
-
 #endif  // !WIN32
 
 }  // namespace url
diff --git a/url/url_canon_internal.h b/url/url_canon_internal.h
index 1452e82..4a3f6e0 100644
--- a/url/url_canon_internal.h
+++ b/url/url_canon_internal.h
@@ -10,9 +10,14 @@
 // template bloat because everything is inlined when anybody calls any of our
 // functions.
 
-#include <stddef.h>
-#include <stdlib.h>
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
 
+#include <stddef.h>
+
+#include <array>
 #include <string>
 
 #include "polyfills/base/component_export.h"
@@ -59,33 +64,140 @@
 //
 // Using an unsigned char type has a small but measurable performance benefit
 // over using a 32-bit number.
-extern const unsigned char kSharedCharTypeTable[0x100];
+// clang-format off
+inline constexpr std::array<uint8_t, 0x100> kSharedCharTypeTable = {
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x00 - 0x0f
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x10 - 0x1f
+    0,                           // 0x20  ' ' (escape spaces in queries)
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x21  !
+    0,                           // 0x22  "
+    0,                           // 0x23  #  (invalid in query since it marks the ref)
+    CHAR_QUERY | CHAR_USERINFO,  // 0x24  $
+    CHAR_QUERY | CHAR_USERINFO,  // 0x25  %
+    CHAR_QUERY | CHAR_USERINFO,  // 0x26  &
+    0,                           // 0x27  '  (Try to prevent XSS.)
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x28  (
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x29  )
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x2a  *
+    CHAR_QUERY | CHAR_USERINFO,  // 0x2b  +
+    CHAR_QUERY | CHAR_USERINFO,  // 0x2c  ,
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x2d  -
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_COMPONENT,  // 0x2e  .
+    CHAR_QUERY,                  // 0x2f  /
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x30  0
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x31  1
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x32  2
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x33  3
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x34  4
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x35  5
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x36  6
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_OCT | CHAR_COMPONENT,  // 0x37  7
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_COMPONENT,             // 0x38  8
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_DEC | CHAR_COMPONENT,             // 0x39  9
+    CHAR_QUERY,  // 0x3a  :
+    CHAR_QUERY,  // 0x3b  ;
+    0,           // 0x3c  <  (Try to prevent certain types of XSS.)
+    CHAR_QUERY,  // 0x3d  =
+    0,           // 0x3e  >  (Try to prevent certain types of XSS.)
+    CHAR_QUERY,  // 0x3f  ?
+    CHAR_QUERY,  // 0x40  @
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x41  A
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x42  B
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x43  C
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x44  D
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x45  E
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x46  F
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x47  G
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x48  H
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x49  I
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4a  J
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4b  K
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4c  L
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4d  M
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4e  N
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x4f  O
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x50  P
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x51  Q
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x52  R
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x53  S
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x54  T
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x55  U
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x56  V
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x57  W
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_COMPONENT, // 0x58  X
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x59  Y
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x5a  Z
+    CHAR_QUERY,  // 0x5b  [
+    CHAR_QUERY,  // 0x5c  '\'
+    CHAR_QUERY,  // 0x5d  ]
+    CHAR_QUERY,  // 0x5e  ^
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x5f  _
+    CHAR_QUERY,  // 0x60  `
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x61  a
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x62  b
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x63  c
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x64  d
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x65  e
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_HEX | CHAR_COMPONENT,  // 0x66  f
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x67  g
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x68  h
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x69  i
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6a  j
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6b  k
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6c  l
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6d  m
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6e  n
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x6f  o
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x70  p
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x71  q
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x72  r
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x73  s
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x74  t
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x75  u
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x76  v
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x77  w
+    CHAR_QUERY | CHAR_USERINFO | CHAR_IPV4 | CHAR_COMPONENT,  // 0x78  x
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x79  y
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x7a  z
+    CHAR_QUERY,  // 0x7b  {
+    CHAR_QUERY,  // 0x7c  |
+    CHAR_QUERY,  // 0x7d  }
+    CHAR_QUERY | CHAR_USERINFO | CHAR_COMPONENT,  // 0x7e  ~
+    0,           // 0x7f
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x80 - 0x8f
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x90 - 0x9f
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xa0 - 0xaf
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xb0 - 0xbf
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xc0 - 0xcf
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xd0 - 0xdf
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xe0 - 0xef
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xf0 - 0xff
+};
+// clang-format on
 
 // More readable wrappers around the character type lookup table.
-inline bool IsCharOfType(unsigned char c, SharedCharTypes type) {
+constexpr bool IsCharOfType(unsigned char c, SharedCharTypes type) {
   return !!(kSharedCharTypeTable[c] & type);
 }
-inline bool IsQueryChar(unsigned char c) {
+constexpr bool IsQueryChar(unsigned char c) {
   return IsCharOfType(c, CHAR_QUERY);
 }
-inline bool IsIPv4Char(unsigned char c) {
+constexpr bool IsIPv4Char(unsigned char c) {
   return IsCharOfType(c, CHAR_IPV4);
 }
-inline bool IsHexChar(unsigned char c) {
+constexpr bool IsHexChar(unsigned char c) {
   return IsCharOfType(c, CHAR_HEX);
 }
-inline bool IsComponentChar(unsigned char c) {
+constexpr bool IsComponentChar(unsigned char c) {
   return IsCharOfType(c, CHAR_COMPONENT);
 }
 
 // Appends the given string to the output, escaping characters that do not
 // match the given |type| in SharedCharTypes.
-void AppendStringOfType(const char* source,
-                        size_t length,
+void AppendStringOfType(std::string_view source,
                         SharedCharTypes type,
                         CanonOutput* output);
-void AppendStringOfType(const char16_t* source,
-                        size_t length,
+void AppendStringOfType(std::u16string_view source,
                         SharedCharTypes type,
                         CanonOutput* output);
 
@@ -290,6 +402,12 @@
   return success;
 }
 
+// URL Standard: https://url.spec.whatwg.org/#c0-control-percent-encode-set
+template <typename CHAR>
+bool IsInC0ControlPercentEncodeSet(CHAR ch) {
+  return ch < 0x20 || ch > 0x7E;
+}
+
 // Given a '%' character at |*begin| in the string |spec|, this will decode
 // the escaped value and put it into |*unescaped_value| on success (returns
 // true). On failure, this will return false, and will not write into
@@ -332,21 +450,15 @@
   return true;
 }
 
-// Appends the given substring to the output, escaping "some" characters that
+// Appends the given string to the output, escaping "some" characters that
 // it feels may not be safe. It assumes the input values are all contained in
 // 8-bit although it allows any type.
 //
 // This is used in error cases to append invalid output so that it looks
 // approximately correct. Non-error cases should not call this function since
 // the escaping rules are not guaranteed!
-void AppendInvalidNarrowString(const char* spec,
-                               size_t begin,
-                               size_t end,
-                               CanonOutput* output);
-void AppendInvalidNarrowString(const char16_t* spec,
-                               size_t begin,
-                               size_t end,
-                               CanonOutput* output);
+void AppendInvalidNarrowString(std::string_view input, CanonOutput* output);
+void AppendInvalidNarrowString(std::u16string_view input, CanonOutput* output);
 
 // Misc canonicalization helpers ----------------------------------------------
 
@@ -359,13 +471,9 @@
 // return false in the failure case, and the caller should not continue as
 // normal.
 COMPONENT_EXPORT(URL)
-bool ConvertUTF16ToUTF8(const char16_t* input,
-                        size_t input_len,
-                        CanonOutput* output);
+bool ConvertUTF16ToUTF8(std::u16string_view input, CanonOutput* output);
 COMPONENT_EXPORT(URL)
-bool ConvertUTF8ToUTF16(const char* input,
-                        size_t input_len,
-                        CanonOutputT<char16_t>* output);
+bool ConvertUTF8ToUTF16(std::string_view input, CanonOutputT<char16_t>* output);
 
 // Converts from UTF-16 to 8-bit using the character set converter. If the
 // converter is NULL, this will use UTF-8.
@@ -412,13 +520,13 @@
 
 // Implemented in url_canon_path.cc, these are required by the relative URL
 // resolver as well, so we declare them here.
-bool CanonicalizePartialPathInternal(const char* spec,
-                                     const Component& path,
+bool CanonicalizePartialPathInternal(std::string_view path,
                                      size_t path_begin_in_output,
+                                     CanonMode canon_mode,
                                      CanonOutput* output);
-bool CanonicalizePartialPathInternal(const char16_t* spec,
-                                     const Component& path,
+bool CanonicalizePartialPathInternal(std::u16string_view path,
                                      size_t path_begin_in_output,
+                                     CanonMode canon_mode,
                                      CanonOutput* output);
 
 // Find the position of a bona fide Windows drive letter in the given path. If
@@ -432,13 +540,40 @@
 COMPONENT_EXPORT(URL)
 int FindWindowsDriveLetter(const char16_t* spec, int begin, int end);
 
+// StringToUint64WithBase is implemented separately because std::strtoull (and
+// its variants like _stroui64 on Windows) are not guaranteed to be constexpr,
+// preventing their direct use in constant expressions.  This custom
+// implementation provides a constexpr-friendly alternative for use in contexts
+// where constant evaluation is required.
+constexpr uint64_t StringToUint64WithBase(std::string_view str, uint8_t base) {
+  uint64_t result = 0;
+
+  for (const char digit : str) {
+    int value = -1;
+
+    if (digit >= '0' && digit <= '9') {
+      value = digit - '0';
+    } else if (digit >= 'A' && digit <= 'Z') {
+      value = digit - 'A' + 10;
+    } else if (digit >= 'a' && digit <= 'z') {
+      value = digit - 'a' + 10;
+    }
+
+    if (value < 0 || value >= base) {
+      break;  // Invalid character for the given base.
+    }
+
+    result = result * base + static_cast<uint64_t>(value);
+  }
+
+  return result;
+}
+
 #ifndef WIN32
 
 // Implementations of Windows' int-to-string conversions
 COMPONENT_EXPORT(URL)
 int _itoa_s(int value, char* buffer, size_t size_in_chars, int radix);
-COMPONENT_EXPORT(URL)
-int _itow_s(int value, char16_t* buffer, size_t size_in_chars, int radix);
 
 // Secure template overloads for these functions
 template <size_t N>
@@ -446,18 +581,6 @@
   return _itoa_s(value, buffer, N, radix);
 }
 
-template <size_t N>
-inline int _itow_s(int value, char16_t (&buffer)[N], int radix) {
-  return _itow_s(value, buffer, N, radix);
-}
-
-// _strtoui64 and strtoull behave the same
-inline unsigned long long _strtoui64(const char* nptr,
-                                     char** endptr,
-                                     int base) {
-  return strtoull(nptr, endptr, base);
-}
-
 #endif  // WIN32
 
 // The threshold we set to consider SIMD processing, in bytes; there is
diff --git a/url/url_canon_ip.cc b/url/url_canon_ip.cc
index 18f2c15..8f62627 100644
--- a/url/url_canon_ip.cc
+++ b/url/url_canon_ip.cc
@@ -10,6 +10,7 @@
 #include <limits>
 
 #include "polyfills/base/check.h"
+#include "base/compiler_specific.h"
 #include "url/url_canon_internal.h"
 #include "url/url_features.h"
 
@@ -17,208 +18,14 @@
 
 namespace {
 
-// Converts one of the character types that represent a numerical base to the
-// corresponding base.
-int BaseForType(SharedCharTypes type) {
-  switch (type) {
-    case CHAR_HEX:
-      return 16;
-    case CHAR_DEC:
-      return 10;
-    case CHAR_OCT:
-      return 8;
-    default:
-      return 0;
-  }
-}
-
-// Converts an IPv4 component to a 32-bit number, while checking for overflow.
-//
-// Possible return values:
-// - IPV4    - The number was valid, and did not overflow.
-// - BROKEN  - The input was numeric, but too large for a 32-bit field.
-// - NEUTRAL - Input was not numeric.
-//
-// The input is assumed to be ASCII. The components are assumed to be non-empty.
-template<typename CHAR>
-CanonHostInfo::Family IPv4ComponentToNumber(const CHAR* spec,
-                                            const Component& component,
-                                            uint32_t* number) {
-  // Empty components are considered non-numeric.
-  if (component.is_empty())
-    return CanonHostInfo::NEUTRAL;
-
-  // Figure out the base
-  SharedCharTypes base;
-  int base_prefix_len = 0;  // Size of the prefix for this base.
-  if (spec[component.begin] == '0') {
-    // Either hex or dec, or a standalone zero.
-    if (component.len == 1) {
-      base = CHAR_DEC;
-    } else if (spec[component.begin + 1] == 'X' ||
-               spec[component.begin + 1] == 'x') {
-      base = CHAR_HEX;
-      base_prefix_len = 2;
-    } else {
-      base = CHAR_OCT;
-      base_prefix_len = 1;
-    }
-  } else {
-    base = CHAR_DEC;
-  }
-
-  // Extend the prefix to consume all leading zeros.
-  while (base_prefix_len < component.len &&
-         spec[component.begin + base_prefix_len] == '0')
-    base_prefix_len++;
-
-  // Put the component, minus any base prefix, into a NULL-terminated buffer so
-  // we can call the standard library. Because leading zeros have already been
-  // discarded, filling the entire buffer is guaranteed to trigger the 32-bit
-  // overflow check.
-  const int kMaxComponentLen = 16;
-  char buf[kMaxComponentLen + 1];  // digits + '\0'
-  int dest_i = 0;
-  bool may_be_broken_octal = false;
-  for (int i = component.begin + base_prefix_len; i < component.end(); i++) {
-    if (spec[i] >= 0x80)
-      return CanonHostInfo::NEUTRAL;
-
-    // We know the input is 7-bit, so convert to narrow (if this is the wide
-    // version of the template) by casting.
-    char input = static_cast<char>(spec[i]);
-
-    // Validate that this character is OK for the given base.
-    if (!IsCharOfType(input, base)) {
-      if (IsCharOfType(input, CHAR_DEC)) {
-        // Entirely numeric components with leading 0s that aren't octal are
-        // considered broken.
-        may_be_broken_octal = true;
-      } else {
-        return CanonHostInfo::NEUTRAL;
-      }
-    }
-
-    // Fill the buffer, if there's space remaining. This check allows us to
-    // verify that all characters are numeric, even those that don't fit.
-    if (dest_i < kMaxComponentLen)
-      buf[dest_i++] = input;
-  }
-
-  if (may_be_broken_octal)
-    return CanonHostInfo::BROKEN;
-
-  buf[dest_i] = '\0';
-
-  // Use the 64-bit strtoi so we get a big number (no hex, decimal, or octal
-  // number can overflow a 64-bit number in <= 16 characters).
-  uint64_t num = _strtoui64(buf, NULL, BaseForType(base));
-
-  // Check for 32-bit overflow.
-  if (num > std::numeric_limits<uint32_t>::max())
-    return CanonHostInfo::BROKEN;
-
-  // No overflow. Success!
-  *number = static_cast<uint32_t>(num);
-  return CanonHostInfo::IPV4;
-}
-
-// See declaration of IPv4AddressToNumber for documentation.
-template <typename CHAR, typename UCHAR>
-CanonHostInfo::Family DoIPv4AddressToNumber(const CHAR* spec,
-                                            Component host,
-                                            unsigned char address[4],
-                                            int* num_ipv4_components) {
-  // Ignore terminal dot, if present.
-  if (host.is_nonempty() && spec[host.end() - 1] == '.')
-    --host.len;
-
-  // Do nothing if empty.
-  if (host.is_empty())
-    return CanonHostInfo::NEUTRAL;
-
-  // Read component values.  The first `existing_components` of them are
-  // populated front to back, with the first one corresponding to the last
-  // component, which allows for early exit if the last component isn't a
-  // number.
-  uint32_t component_values[4];
-  int existing_components = 0;
-
-  int current_component_end = host.end();
-  int current_position = current_component_end;
-  while (true) {
-    // If this is not the first character of a component, go to the next
-    // component.
-    if (current_position != host.begin && spec[current_position - 1] != '.') {
-      --current_position;
-      continue;
-    }
-
-    CanonHostInfo::Family family = IPv4ComponentToNumber(
-        spec,
-        Component(current_position, current_component_end - current_position),
-        &component_values[existing_components]);
-
-    // If `family` is NEUTRAL and this is the last component, return NEUTRAL. If
-    // `family` is NEUTRAL but not the last component, this is considered a
-    // BROKEN IPv4 address, as opposed to a non-IPv4 hostname.
-    if (family == CanonHostInfo::NEUTRAL && existing_components == 0)
-      return CanonHostInfo::NEUTRAL;
-
-    if (family != CanonHostInfo::IPV4)
-      return CanonHostInfo::BROKEN;
-
-    ++existing_components;
-
-    // If this is the final component, nothing else to do.
-    if (current_position == host.begin)
-      break;
-
-    // If there are more than 4 components, fail.
-    if (existing_components == 4)
-      return CanonHostInfo::BROKEN;
-
-    current_component_end = current_position - 1;
-    --current_position;
-  }
-
-  // Use `component_values` to fill out the 4-component IP address.
-
-  // First, process all components but the last, while making sure each fits
-  // within an 8-bit field.
-  for (int i = existing_components - 1; i > 0; i--) {
-    if (component_values[i] > std::numeric_limits<uint8_t>::max())
-      return CanonHostInfo::BROKEN;
-    address[existing_components - i - 1] =
-        static_cast<unsigned char>(component_values[i]);
-  }
-
-  uint32_t last_value = component_values[0];
-  for (int i = 3; i >= existing_components - 1; i--) {
-    address[i] = static_cast<unsigned char>(last_value);
-    last_value >>= 8;
-  }
-
-  // If the last component has residual bits, report overflow.
-  if (last_value != 0)
-    return CanonHostInfo::BROKEN;
-
-  // Tell the caller how many components we saw.
-  *num_ipv4_components = existing_components;
-
-  // Success!
-  return CanonHostInfo::IPV4;
-}
-
 // Return true if we've made a final IPV4/BROKEN decision, false if the result
 // is NEUTRAL, and we could use a second opinion.
-template<typename CHAR, typename UCHAR>
-bool DoCanonicalizeIPv4Address(const CHAR* spec,
-                               const Component& host,
+template <typename CHAR, typename UCHAR>
+bool DoCanonicalizeIPv4Address(std::basic_string_view<CHAR> host_view,
                                CanonOutput* output,
                                CanonHostInfo* host_info) {
-  host_info->family = IPv4AddressToNumber(
-      spec, host, host_info->address, &host_info->num_ipv4_components);
+  host_info->family = IPv4AddressToNumber(host_view, host_info->address,
+                                          &host_info->num_ipv4_components);
 
   switch (host_info->family) {
     case CanonHostInfo::IPV4:
@@ -236,286 +43,10 @@
   }
 }
 
-// Helper class that describes the main components of an IPv6 input string.
-// See the following examples to understand how it breaks up an input string:
-//
-// [Example 1]: input = "[::aa:bb]"
-//  ==> num_hex_components = 2
-//  ==> hex_components[0] = Component(3,2) "aa"
-//  ==> hex_components[1] = Component(6,2) "bb"
-//  ==> index_of_contraction = 0
-//  ==> ipv4_component = Component(0, -1)
-//
-// [Example 2]: input = "[1:2::3:4:5]"
-//  ==> num_hex_components = 5
-//  ==> hex_components[0] = Component(1,1) "1"
-//  ==> hex_components[1] = Component(3,1) "2"
-//  ==> hex_components[2] = Component(6,1) "3"
-//  ==> hex_components[3] = Component(8,1) "4"
-//  ==> hex_components[4] = Component(10,1) "5"
-//  ==> index_of_contraction = 2
-//  ==> ipv4_component = Component(0, -1)
-//
-// [Example 3]: input = "[::ffff:192.168.0.1]"
-//  ==> num_hex_components = 1
-//  ==> hex_components[0] = Component(3,4) "ffff"
-//  ==> index_of_contraction = 0
-//  ==> ipv4_component = Component(8, 11) "192.168.0.1"
-//
-// [Example 4]: input = "[1::]"
-//  ==> num_hex_components = 1
-//  ==> hex_components[0] = Component(1,1) "1"
-//  ==> index_of_contraction = 1
-//  ==> ipv4_component = Component(0, -1)
-//
-// [Example 5]: input = "[::192.168.0.1]"
-//  ==> num_hex_components = 0
-//  ==> index_of_contraction = 0
-//  ==> ipv4_component = Component(8, 11) "192.168.0.1"
-//
-struct IPv6Parsed {
-  // Zero-out the parse information.
-  void reset() {
-    num_hex_components = 0;
-    index_of_contraction = -1;
-    ipv4_component.reset();
-  }
-
-  // There can be up to 8 hex components (colon separated) in the literal.
-  Component hex_components[8];
-
-  // The count of hex components present. Ranges from [0,8].
-  int num_hex_components;
-
-  // The index of the hex component that the "::" contraction precedes, or
-  // -1 if there is no contraction.
-  int index_of_contraction;
-
-  // The range of characters which are an IPv4 literal.
-  Component ipv4_component;
-};
-
-// Parse the IPv6 input string. If parsing succeeded returns true and fills
-// |parsed| with the information. If parsing failed (because the input is
-// invalid) returns false.
-template<typename CHAR, typename UCHAR>
-bool DoParseIPv6(const CHAR* spec, const Component& host, IPv6Parsed* parsed) {
-  // Zero-out the info.
-  parsed->reset();
-
-  if (host.is_empty())
-    return false;
-
-  // The index for start and end of address range (no brackets).
-  int begin = host.begin;
-  int end = host.end();
-
-  int cur_component_begin = begin;  // Start of the current component.
-
-  // Scan through the input, searching for hex components, "::" contractions,
-  // and IPv4 components.
-  for (int i = begin; /* i <= end */; i++) {
-    bool is_colon = spec[i] == ':';
-    bool is_contraction = is_colon && i < end - 1 && spec[i + 1] == ':';
-
-    // We reached the end of the current component if we encounter a colon
-    // (separator between hex components, or start of a contraction), or end of
-    // input.
-    if (is_colon || i == end) {
-      int component_len = i - cur_component_begin;
-
-      // A component should not have more than 4 hex digits.
-      if (component_len > 4)
-        return false;
-
-      // Don't allow empty components.
-      if (component_len == 0) {
-        // The exception is when contractions appear at beginning of the
-        // input or at the end of the input.
-        if (!((is_contraction && i == begin) || (i == end &&
-            parsed->index_of_contraction == parsed->num_hex_components)))
-          return false;
-      }
-
-      // Add the hex component we just found to running list.
-      if (component_len > 0) {
-        // Can't have more than 8 components!
-        if (parsed->num_hex_components >= 8)
-          return false;
-
-        parsed->hex_components[parsed->num_hex_components++] =
-            Component(cur_component_begin, component_len);
-      }
-    }
-
-    if (i == end)
-      break;  // Reached the end of the input, DONE.
-
-    // We found a "::" contraction.
-    if (is_contraction) {
-      // There can be at most one contraction in the literal.
-      if (parsed->index_of_contraction != -1)
-        return false;
-      parsed->index_of_contraction = parsed->num_hex_components;
-      ++i;  // Consume the colon we peeked.
-    }
-
-    if (is_colon) {
-      // Colons are separators between components, keep track of where the
-      // current component started (after this colon).
-      cur_component_begin = i + 1;
-    } else {
-      if (static_cast<UCHAR>(spec[i]) >= 0x80)
-        return false;  // Not ASCII.
-
-      if (!IsHexChar(static_cast<unsigned char>(spec[i]))) {
-        // Regular components are hex numbers. It is also possible for
-        // a component to be an IPv4 address in dotted form.
-        if (IsIPv4Char(static_cast<unsigned char>(spec[i]))) {
-          // Since IPv4 address can only appear at the end, assume the rest
-          // of the string is an IPv4 address. (We will parse this separately
-          // later).
-          parsed->ipv4_component =
-              Component(cur_component_begin, end - cur_component_begin);
-          break;
-        } else {
-          // The character was neither a hex digit, nor an IPv4 character.
-          return false;
-        }
-      }
-    }
-  }
-
-  return true;
-}
-
-// Verifies the parsed IPv6 information, checking that the various components
-// add up to the right number of bits (hex components are 16 bits, while
-// embedded IPv4 formats are 32 bits, and contractions are placeholdes for
-// 16 or more bits). Returns true if sizes match up, false otherwise. On
-// success writes the length of the contraction (if any) to
-// |out_num_bytes_of_contraction|.
-bool CheckIPv6ComponentsSize(const IPv6Parsed& parsed,
-                             int* out_num_bytes_of_contraction) {
-  // Each group of four hex digits contributes 16 bits.
-  int num_bytes_without_contraction = parsed.num_hex_components * 2;
-
-  // If an IPv4 address was embedded at the end, it contributes 32 bits.
-  if (parsed.ipv4_component.is_valid())
-    num_bytes_without_contraction += 4;
-
-  // If there was a "::" contraction, its size is going to be:
-  // MAX([16bits], [128bits] - num_bytes_without_contraction).
-  int num_bytes_of_contraction = 0;
-  if (parsed.index_of_contraction != -1) {
-    num_bytes_of_contraction = 16 - num_bytes_without_contraction;
-    if (num_bytes_of_contraction < 2)
-      num_bytes_of_contraction = 2;
-  }
-
-  // Check that the numbers add up.
-  if (num_bytes_without_contraction + num_bytes_of_contraction != 16)
-    return false;
-
-  *out_num_bytes_of_contraction = num_bytes_of_contraction;
-  return true;
-}
-
-// Converts a hex component into a number. This cannot fail since the caller has
-// already verified that each character in the string was a hex digit, and
-// that there were no more than 4 characters.
-template <typename CHAR>
-uint16_t IPv6HexComponentToNumber(const CHAR* spec,
-                                  const Component& component) {
-  GURL_DCHECK(component.len <= 4);
-
-  // Copy the hex string into a C-string.
-  char buf[5];
-  for (int i = 0; i < component.len; ++i)
-    buf[i] = static_cast<char>(spec[component.begin + i]);
-  buf[component.len] = '\0';
-
-  // Convert it to a number (overflow is not possible, since with 4 hex
-  // characters we can at most have a 16 bit number).
-  return static_cast<uint16_t>(_strtoui64(buf, NULL, 16));
-}
-
-// Converts an IPv6 address to a 128-bit number (network byte order), returning
-// true on success. False means that the input was not a valid IPv6 address.
-template<typename CHAR, typename UCHAR>
-bool DoIPv6AddressToNumber(const CHAR* spec,
-                           const Component& host,
-                           unsigned char address[16]) {
-  // Make sure the component is bounded by '[' and ']'.
-  int end = host.end();
-  if (host.is_empty() || spec[host.begin] != '[' || spec[end - 1] != ']')
-    return false;
-
-  // Exclude the square brackets.
-  Component ipv6_comp(host.begin + 1, host.len - 2);
-
-  // Parse the IPv6 address -- identify where all the colon separated hex
-  // components are, the "::" contraction, and the embedded IPv4 address.
-  IPv6Parsed ipv6_parsed;
-  if (!DoParseIPv6<CHAR, UCHAR>(spec, ipv6_comp, &ipv6_parsed))
-    return false;
-
-  // Do some basic size checks to make sure that the address doesn't
-  // specify more than 128 bits or fewer than 128 bits. This also resolves
-  // how may zero bytes the "::" contraction represents.
-  int num_bytes_of_contraction;
-  if (!CheckIPv6ComponentsSize(ipv6_parsed, &num_bytes_of_contraction))
-    return false;
-
-  int cur_index_in_address = 0;
-
-  // Loop through each hex components, and contraction in order.
-  for (int i = 0; i <= ipv6_parsed.num_hex_components; ++i) {
-    // Append the contraction if it appears before this component.
-    if (i == ipv6_parsed.index_of_contraction) {
-      for (int j = 0; j < num_bytes_of_contraction; ++j)
-        address[cur_index_in_address++] = 0;
-    }
-    // Append the hex component's value.
-    if (i != ipv6_parsed.num_hex_components) {
-      // Get the 16-bit value for this hex component.
-      uint16_t number = IPv6HexComponentToNumber<CHAR>(
-          spec, ipv6_parsed.hex_components[i]);
-      // Append to |address|, in network byte order.
-      address[cur_index_in_address++] = (number & 0xFF00) >> 8;
-      address[cur_index_in_address++] = (number & 0x00FF);
-    }
-  }
-
-  // If there was an IPv4 section, convert it into a 32-bit number and append
-  // it to |address|.
-  if (ipv6_parsed.ipv4_component.is_valid()) {
-    // Append the 32-bit number to |address|.
-    int num_ipv4_components = 0;
-    // IPv4AddressToNumber will remove the trailing dot from the component.
-    bool trailing_dot = ipv6_parsed.ipv4_component.is_nonempty() &&
-                        spec[ipv6_parsed.ipv4_component.end() - 1] == '.';
-    // The URL standard requires the embedded IPv4 address to be concisely
-    // composed of 4 parts and disallows terminal dots.
-    // See https://url.spec.whatwg.org/#concept-ipv6-parser
-    if (CanonHostInfo::IPV4 !=
-            IPv4AddressToNumber(spec, ipv6_parsed.ipv4_component,
-                                &address[cur_index_in_address],
-                                &num_ipv4_components)) {
-      return false;
-    }
-    if ((num_ipv4_components != 4 || trailing_dot)) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
 // Searches for the longest sequence of zeros in |address|, and writes the
 // range into |contraction_range|. The run of zeros must be at least 16 bits,
 // and if there is a tie the first is chosen.
-void ChooseIPv6ContractionRange(const unsigned char address[16],
+void ChooseIPv6ContractionRange(gurl_base::span<const uint8_t> address,
                                 Component* contraction_range) {
   // The longest run of zeros in |address| seen so far.
   Component max_range;
@@ -548,17 +79,16 @@
 
 // Return true if we've made a final IPV6/BROKEN decision, false if the result
 // is NEUTRAL, and we could use a second opinion.
-template<typename CHAR, typename UCHAR>
-bool DoCanonicalizeIPv6Address(const CHAR* spec,
-                               const Component& host,
+template <typename CHAR, typename UCHAR>
+bool DoCanonicalizeIPv6Address(std::basic_string_view<CHAR> host_view,
                                CanonOutput* output,
                                CanonHostInfo* host_info) {
   // Turn the IP address into a 128 bit number.
-  if (!IPv6AddressToNumber(spec, host, host_info->address)) {
+  if (!IPv6AddressToNumber(host_view, host_info->address)) {
     // If it's not an IPv6 address, scan for characters that should *only*
     // exist in an IPv6 address.
-    for (int i = host.begin; i < host.end(); i++) {
-      switch (spec[i]) {
+    for (CHAR ch : host_view) {
+      switch (ch) {
         case '[':
         case ']':
         case ':':
@@ -584,20 +114,23 @@
 
 }  // namespace
 
-void AppendIPv4Address(const unsigned char address[4], CanonOutput* output) {
+void AppendIPv4Address(gurl_base::span<const uint8_t> address, CanonOutput* output) {
+  GURL_DCHECK_GE(address.size(), 4u);
   for (int i = 0; i < 4; i++) {
     char str[16];
     _itoa_s(address[i], str, 10);
 
-    for (int ch = 0; str[ch] != 0; ch++)
-      output->push_back(str[ch]);
+    for (int ch = 0; UNSAFE_TODO(str[ch]) != 0; ch++) {
+      output->push_back(UNSAFE_TODO(str[ch]));
+    }
 
     if (i != 3)
       output->push_back('.');
   }
 }
 
-void AppendIPv6Address(const unsigned char address[16], CanonOutput* output) {
+void AppendIPv6Address(gurl_base::span<const uint8_t> address, CanonOutput* output) {
+  GURL_DCHECK_GE(address.size(), 16u);
   // We will output the address according to the rules in:
   // http://tools.ietf.org/html/draft-kawamura-ipv6-text-representation-01#section-4
 
@@ -623,8 +156,9 @@
       // Stringify the 16 bit number (at most requires 4 hex digits).
       char str[5];
       _itoa_s(x, str, 16);
-      for (int ch = 0; str[ch] != 0; ++ch)
-        output->push_back(str[ch]);
+      for (int ch = 0; UNSAFE_TODO(str[ch]) != 0; ++ch) {
+        output->push_back(UNSAFE_TODO(str[ch]));
+      }
 
       // Put a colon after each number, except the last.
       if (i < 16)
@@ -633,56 +167,43 @@
   }
 }
 
-void CanonicalizeIPAddress(const char* spec,
-                           const Component& host,
+void CanonicalizeIPAddress(std::string_view host_view,
                            CanonOutput* output,
                            CanonHostInfo* host_info) {
-  if (DoCanonicalizeIPv4Address<char, unsigned char>(
-          spec, host, output, host_info))
+  if (DoCanonicalizeIPv4Address<char, unsigned char>(host_view, output,
+                                                     host_info)) {
     return;
-  if (DoCanonicalizeIPv6Address<char, unsigned char>(
-          spec, host, output, host_info))
+  }
+  if (DoCanonicalizeIPv6Address<char, unsigned char>(host_view, output,
+                                                     host_info)) {
     return;
+  }
 }
 
-void CanonicalizeIPAddress(const char16_t* spec,
-                           const Component& host,
+void CanonicalizeIPAddress(std::u16string_view host_view,
                            CanonOutput* output,
                            CanonHostInfo* host_info) {
-  if (DoCanonicalizeIPv4Address<char16_t, char16_t>(spec, host, output,
-                                                    host_info))
+  if (DoCanonicalizeIPv4Address<char16_t, char16_t>(host_view, output,
+                                                    host_info)) {
     return;
-  if (DoCanonicalizeIPv6Address<char16_t, char16_t>(spec, host, output,
-                                                    host_info))
+  }
+  if (DoCanonicalizeIPv6Address<char16_t, char16_t>(host_view, output,
+                                                    host_info)) {
     return;
+  }
 }
 
-CanonHostInfo::Family IPv4AddressToNumber(const char* spec,
-                                          const Component& host,
-                                          unsigned char address[4],
-                                          int* num_ipv4_components) {
-  return DoIPv4AddressToNumber<char, unsigned char>(spec, host, address,
-                                                    num_ipv4_components);
+void CanonicalizeIPv6Address(std::string_view host_view,
+                             CanonOutput& output,
+                             CanonHostInfo& host_info) {
+  DoCanonicalizeIPv6Address<char, unsigned char>(host_view, &output,
+                                                 &host_info);
 }
 
-CanonHostInfo::Family IPv4AddressToNumber(const char16_t* spec,
-                                          const Component& host,
-                                          unsigned char address[4],
-                                          int* num_ipv4_components) {
-  return DoIPv4AddressToNumber<char16_t, char16_t>(spec, host, address,
-                                                   num_ipv4_components);
-}
-
-bool IPv6AddressToNumber(const char* spec,
-                         const Component& host,
-                         unsigned char address[16]) {
-  return DoIPv6AddressToNumber<char, unsigned char>(spec, host, address);
-}
-
-bool IPv6AddressToNumber(const char16_t* spec,
-                         const Component& host,
-                         unsigned char address[16]) {
-  return DoIPv6AddressToNumber<char16_t, char16_t>(spec, host, address);
+void CanonicalizeIPv6Address(std::u16string_view host_view,
+                             CanonOutput& output,
+                             CanonHostInfo& host_info) {
+  DoCanonicalizeIPv6Address<char16_t, char16_t>(host_view, &output, &host_info);
 }
 
 }  // namespace url
diff --git a/url/url_canon_ip.h b/url/url_canon_ip.h
index 953be0a..ec3d052 100644
--- a/url/url_canon_ip.h
+++ b/url/url_canon_ip.h
@@ -2,24 +2,564 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #ifndef URL_URL_CANON_IP_H_
 #define URL_URL_CANON_IP_H_
 
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <string_view>
+#include <type_traits>
+
 #include "polyfills/base/component_export.h"
+#include "base/containers/span.h"
 #include "url/third_party/mozilla/url_parse.h"
 #include "url/url_canon.h"
+#include "url/url_canon_internal.h"
 
 namespace url {
 
+namespace internal {
+
+// Converts one of the character types that represent a numerical base to the
+// corresponding base.
+constexpr uint8_t BaseForType(SharedCharTypes type) {
+  switch (type) {
+    case CHAR_HEX:
+      return 16;
+    case CHAR_DEC:
+      return 10;
+    case CHAR_OCT:
+      return 8;
+    default:
+      return 0;
+  }
+}
+
+// Converts an IPv4 component to a 32-bit number, while checking for overflow.
+//
+// Possible return values:
+// - IPV4    - The number was valid, and did not overflow.
+// - BROKEN  - The input was numeric, but too large for a 32-bit field.
+// - NEUTRAL - Input was not numeric.
+//
+// The input is assumed to be ASCII. The components are assumed to be non-empty.
+template <typename CHAR>
+constexpr CanonHostInfo::Family IPv4ComponentToNumber(
+    const CHAR* spec,
+    const Component& component,
+    uint32_t* number) {
+  // Empty components are considered non-numeric.
+  if (component.is_empty()) {
+    return CanonHostInfo::NEUTRAL;
+  }
+
+  // Figure out the base
+  SharedCharTypes base;
+  int base_prefix_len = 0;  // Size of the prefix for this base.
+  if (spec[component.begin] == '0') {
+    // Either hex or dec, or a standalone zero.
+    if (component.len == 1) {
+      base = CHAR_DEC;
+    } else if (spec[component.begin + 1] == 'X' ||
+               spec[component.begin + 1] == 'x') {
+      base = CHAR_HEX;
+      base_prefix_len = 2;
+    } else {
+      base = CHAR_OCT;
+      base_prefix_len = 1;
+    }
+  } else {
+    base = CHAR_DEC;
+  }
+
+  // Extend the prefix to consume all leading zeros.
+  while (base_prefix_len < component.len &&
+         spec[component.begin + base_prefix_len] == '0') {
+    base_prefix_len++;
+  }
+
+  // Put the component, minus any base prefix, into a NULL-terminated buffer so
+  // we can call the standard library. Because leading zeros have already been
+  // discarded, filling the entire buffer is guaranteed to trigger the 32-bit
+  // overflow check.
+  const int kMaxComponentLen = 16;
+  char buf[kMaxComponentLen + 1];  // digits + '\0'
+  int dest_i = 0;
+  bool may_be_broken_octal = false;
+  for (int i = component.begin + base_prefix_len; i < component.end(); i++) {
+    if (spec[i] >= 0x80) {
+      return CanonHostInfo::NEUTRAL;
+    }
+
+    // We know the input is 7-bit, so convert to narrow (if this is the wide
+    // version of the template) by casting.
+    auto input = static_cast<unsigned char>(spec[i]);
+
+    // Validate that this character is OK for the given base.
+    if (!IsCharOfType(input, base)) {
+      if (IsCharOfType(input, CHAR_DEC)) {
+        // Entirely numeric components with leading 0s that aren't octal are
+        // considered broken.
+        may_be_broken_octal = true;
+      } else {
+        return CanonHostInfo::NEUTRAL;
+      }
+    }
+
+    // Fill the buffer, if there's space remaining. This check allows us to
+    // verify that all characters are numeric, even those that don't fit.
+    if (dest_i < kMaxComponentLen) {
+      buf[dest_i++] = static_cast<char>(input);
+    }
+  }
+
+  if (may_be_broken_octal) {
+    return CanonHostInfo::BROKEN;
+  }
+
+  buf[dest_i] = '\0';
+
+  // Use the 64-bit StringToUint64WithBase so we get a big number (no hex,
+  // decimal, or octal number can overflow a 64-bit number in <= 16 characters).
+  uint64_t num = StringToUint64WithBase(buf, BaseForType(base));
+
+  // Check for 32-bit overflow.
+  if (num > std::numeric_limits<uint32_t>::max()) {
+    return CanonHostInfo::BROKEN;
+  }
+
+  // No overflow. Success!
+  *number = static_cast<uint32_t>(num);
+  return CanonHostInfo::IPV4;
+}
+
+// See declaration of IPv4AddressToNumber for documentation.
+template <typename CHAR>
+constexpr CanonHostInfo::Family DoIPv4AddressToNumber(
+    std::basic_string_view<CHAR> host_view,
+    gurl_base::span<uint8_t> address,
+    int* num_ipv4_components) {
+  GURL_DCHECK_GE(address.size(), 4u);
+  // Ignore terminal dot, if present.
+  if (!host_view.empty() && host_view.back() == '.') {
+    host_view = host_view.substr(0, host_view.length() - 1);
+  }
+
+  // Do nothing if empty.
+  if (host_view.empty()) {
+    return CanonHostInfo::NEUTRAL;
+  }
+
+  // Read component values.  The first `existing_components` of them are
+  // populated front to back, with the first one corresponding to the last
+  // component, which allows for early exit if the last component isn't a
+  // number.
+  std::array<uint32_t, 4> component_values;
+  uint8_t existing_components = 0;
+  // `existing_components` is used to index `component_values`.
+  // All possible values must be in range.
+  static_assert(std::numeric_limits<decltype(existing_components)>::max() >=
+                sizeof(component_values) / sizeof(component_values[0]));
+
+  size_t current_component_end = host_view.length();
+  size_t current_position = current_component_end;
+  while (true) {
+    // If this is not the first character of a component, go to the next
+    // component.
+    if (current_position != 0 && host_view[current_position - 1] != '.') {
+      --current_position;
+      continue;
+    }
+
+    CanonHostInfo::Family family = IPv4ComponentToNumber(
+        host_view.data(),
+        Component(
+            gurl_base::checked_cast<int>(current_position),
+            gurl_base::checked_cast<int>(current_component_end - current_position)),
+        &component_values[existing_components]);
+
+    // If `family` is NEUTRAL and this is the last component, return NEUTRAL. If
+    // `family` is NEUTRAL but not the last component, this is considered a
+    // BROKEN IPv4 address, as opposed to a non-IPv4 hostname.
+    if (family == CanonHostInfo::NEUTRAL && existing_components == 0) {
+      return CanonHostInfo::NEUTRAL;
+    }
+
+    if (family != CanonHostInfo::IPV4) {
+      return CanonHostInfo::BROKEN;
+    }
+
+    ++existing_components;
+
+    // If this is the final component, nothing else to do.
+    if (current_position == 0) {
+      break;
+    }
+
+    // If there are more than 4 components, fail.
+    if (existing_components == 4) {
+      return CanonHostInfo::BROKEN;
+    }
+
+    current_component_end = current_position - 1;
+    --current_position;
+  }
+  GURL_CHECK_GT(existing_components, 0);
+
+  // Use `component_values` to fill out the 4-component IP address.
+
+  // First, process all components but the last, while making sure each fits
+  // within an 8-bit field.
+  for (uint8_t i = existing_components - 1; i > 0; --i) {
+    if (component_values[i] > std::numeric_limits<uint8_t>::max()) {
+      return CanonHostInfo::BROKEN;
+    }
+    address[existing_components - i - 1] =
+        static_cast<uint8_t>(component_values[i]);
+  }
+
+  uint32_t last_value = component_values[0];
+  for (size_t i = 4; i >= existing_components; --i) {
+    address[i - 1] = static_cast<uint8_t>(last_value);
+    last_value >>= 8;
+  }
+
+  // If the last component has residual bits, report overflow.
+  if (last_value != 0) {
+    return CanonHostInfo::BROKEN;
+  }
+
+  // Tell the caller how many components we saw.
+  *num_ipv4_components = existing_components;
+
+  // Success!
+  return CanonHostInfo::IPV4;
+}
+
+// Helper class that describes the main components of an IPv6 input string.
+// See the following examples to understand how it breaks up an input string:
+//
+// [Example 1]: input = "[::aa:bb]"
+//  ==> num_hex_components = 2
+//  ==> hex_components[0] = Component(3,2) "aa"
+//  ==> hex_components[1] = Component(6,2) "bb"
+//  ==> index_of_contraction = 0
+//  ==> ipv4_component = Component(0, -1)
+//
+// [Example 2]: input = "[1:2::3:4:5]"
+//  ==> num_hex_components = 5
+//  ==> hex_components[0] = Component(1,1) "1"
+//  ==> hex_components[1] = Component(3,1) "2"
+//  ==> hex_components[2] = Component(6,1) "3"
+//  ==> hex_components[3] = Component(8,1) "4"
+//  ==> hex_components[4] = Component(10,1) "5"
+//  ==> index_of_contraction = 2
+//  ==> ipv4_component = Component(0, -1)
+//
+// [Example 3]: input = "[::ffff:192.168.0.1]"
+//  ==> num_hex_components = 1
+//  ==> hex_components[0] = Component(3,4) "ffff"
+//  ==> index_of_contraction = 0
+//  ==> ipv4_component = Component(8, 11) "192.168.0.1"
+//
+// [Example 4]: input = "[1::]"
+//  ==> num_hex_components = 1
+//  ==> hex_components[0] = Component(1,1) "1"
+//  ==> index_of_contraction = 1
+//  ==> ipv4_component = Component(0, -1)
+//
+// [Example 5]: input = "[::192.168.0.1]"
+//  ==> num_hex_components = 0
+//  ==> index_of_contraction = 0
+//  ==> ipv4_component = Component(8, 11) "192.168.0.1"
+//
+struct IPv6Parsed {
+  // Zero-out the parse information.
+  constexpr void reset() {
+    num_hex_components = 0;
+    index_of_contraction = -1;
+    ipv4_component.reset();
+  }
+
+  // There can be up to 8 hex components (colon separated) in the literal.
+  std::array<Component, 8> hex_components;
+
+  // The count of hex components present. Ranges from [0,8].
+  uint8_t num_hex_components;
+  static_assert(std::numeric_limits<decltype(num_hex_components)>::max() >=
+                sizeof(hex_components) / sizeof(hex_components[0]));
+
+  // The index of the hex component that the "::" contraction precedes, or
+  // -1 if there is no contraction.
+  int index_of_contraction;
+
+  // The range of characters which are an IPv4 literal.
+  Component ipv4_component;
+};
+
+// Parse the IPv6 input string. If parsing succeeded returns true and fills
+// |parsed| with the information. If parsing failed (because the input is
+// invalid) returns false.
+template <typename CHAR, typename UCHAR>
+constexpr bool DoParseIPv6(const CHAR* spec,
+                           const Component& host,
+                           IPv6Parsed* parsed) {
+  // Zero-out the info.
+  parsed->reset();
+
+  if (host.is_empty()) {
+    return false;
+  }
+
+  // The index for start and end of address range (no brackets).
+  int begin = host.begin;
+  int end = host.end();
+
+  int cur_component_begin = begin;  // Start of the current component.
+
+  // Scan through the input, searching for hex components, "::" contractions,
+  // and IPv4 components.
+  for (int i = begin; /* i <= end */; i++) {
+    bool is_colon = spec[i] == ':';
+    bool is_contraction = is_colon && i < end - 1 && spec[i + 1] == ':';
+
+    // We reached the end of the current component if we encounter a colon
+    // (separator between hex components, or start of a contraction), or end of
+    // input.
+    if (is_colon || i == end) {
+      int component_len = i - cur_component_begin;
+
+      // A component should not have more than 4 hex digits.
+      if (component_len > 4) {
+        return false;
+      }
+
+      // Don't allow empty components.
+      if (component_len == 0) {
+        // The exception is when contractions appear at beginning of the
+        // input or at the end of the input.
+        if (!((is_contraction && i == begin) ||
+              (i == end &&
+               parsed->index_of_contraction == parsed->num_hex_components))) {
+          return false;
+        }
+      }
+
+      // Add the hex component we just found to running list.
+      if (component_len > 0) {
+        // Can't have more than 8 components!
+        if (parsed->num_hex_components >= 8) {
+          return false;
+        }
+
+        parsed->hex_components[parsed->num_hex_components++] =
+            Component(cur_component_begin, component_len);
+      }
+    }
+
+    if (i == end) {
+      break;  // Reached the end of the input, DONE.
+    }
+
+    // We found a "::" contraction.
+    if (is_contraction) {
+      // There can be at most one contraction in the literal.
+      if (parsed->index_of_contraction != -1) {
+        return false;
+      }
+      parsed->index_of_contraction = parsed->num_hex_components;
+      ++i;  // Consume the colon we peeked.
+    }
+
+    if (is_colon) {
+      // Colons are separators between components, keep track of where the
+      // current component started (after this colon).
+      cur_component_begin = i + 1;
+    } else {
+      if (static_cast<UCHAR>(spec[i]) >= 0x80) {
+        return false;  // Not ASCII.
+      }
+
+      if (!IsHexChar(static_cast<unsigned char>(spec[i]))) {
+        // Regular components are hex numbers. It is also possible for
+        // a component to be an IPv4 address in dotted form.
+        if (IsIPv4Char(static_cast<unsigned char>(spec[i]))) {
+          // Since IPv4 address can only appear at the end, assume the rest
+          // of the string is an IPv4 address. (We will parse this separately
+          // later).
+          parsed->ipv4_component =
+              Component(cur_component_begin, end - cur_component_begin);
+          break;
+        } else {
+          // The character was neither a hex digit, nor an IPv4 character.
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+// Verifies the parsed IPv6 information, checking that the various components
+// add up to the right number of bits (hex components are 16 bits, while
+// embedded IPv4 formats are 32 bits, and contractions are placeholdes for
+// 16 or more bits). Returns true if sizes match up, false otherwise. On
+// success writes the length of the contraction (if any) to
+// |out_num_bytes_of_contraction|.
+constexpr bool CheckIPv6ComponentsSize(const IPv6Parsed& parsed,
+                                       int* out_num_bytes_of_contraction) {
+  // Each group of four hex digits contributes 16 bits.
+  int num_bytes_without_contraction = parsed.num_hex_components * 2;
+
+  // If an IPv4 address was embedded at the end, it contributes 32 bits.
+  if (parsed.ipv4_component.is_valid()) {
+    num_bytes_without_contraction += 4;
+  }
+
+  // If there was a "::" contraction, its size is going to be:
+  // MAX([16bits], [128bits] - num_bytes_without_contraction).
+  int num_bytes_of_contraction = 0;
+  if (parsed.index_of_contraction != -1) {
+    num_bytes_of_contraction = 16 - num_bytes_without_contraction;
+    if (num_bytes_of_contraction < 2) {
+      num_bytes_of_contraction = 2;
+    }
+  }
+
+  // Check that the numbers add up.
+  if (num_bytes_without_contraction + num_bytes_of_contraction != 16) {
+    return false;
+  }
+
+  *out_num_bytes_of_contraction = num_bytes_of_contraction;
+  return true;
+}
+
+// Converts a hex component into a number. This cannot fail since the caller has
+// already verified that each character in the string was a hex digit, and
+// that there were no more than 4 characters.
+template <typename CHAR>
+constexpr uint16_t IPv6HexComponentToNumber(const CHAR* spec,
+                                            const Component& component) {
+  GURL_DCHECK(component.len <= 4);
+
+  // Copy the hex string into a C-string.
+  char buf[5];
+  for (int i = 0; i < component.len; ++i) {
+    buf[i] = static_cast<char>(spec[component.begin + i]);
+  }
+  buf[component.len] = '\0';
+
+  // Convert it to a number (overflow is not possible, since with 4 hex
+  // characters we can at most have a 16 bit number).
+  return static_cast<uint16_t>(StringToUint64WithBase(buf, 16));
+}
+
+// Converts an IPv6 address to a 128-bit number (network byte order), returning
+// true on success. False means that the input was not a valid IPv6 address.
+// `address` must have 16 or more elements.
+template <typename CHAR, typename UCHAR>
+constexpr bool DoIPv6AddressToNumber(std::basic_string_view<CHAR> host_view,
+                                     gurl_base::span<uint8_t> address) {
+  GURL_DCHECK_GE(address.size(), 16u);
+  // Make sure the component is bounded by '[' and ']'.
+  size_t length = host_view.length();
+  if (host_view.empty() || host_view.front() != '[' ||
+      host_view.back() != ']') {
+    return false;
+  }
+
+  // Exclude the square brackets.
+  auto trimmed = host_view.substr(1, length - 2);
+
+  // Parse the IPv6 address -- identify where all the colon separated hex
+  // components are, the "::" contraction, and the embedded IPv4 address.
+  IPv6Parsed ipv6_parsed;
+  if (!DoParseIPv6<CHAR, UCHAR>(
+          trimmed.data(),
+          Component(0, gurl_base::checked_cast<int>(trimmed.length())),
+          &ipv6_parsed)) {
+    return false;
+  }
+
+  // Do some basic size checks to make sure that the address doesn't
+  // specify more than 128 bits or fewer than 128 bits. This also resolves
+  // how may zero bytes the "::" contraction represents.
+  int num_bytes_of_contraction;
+  if (!CheckIPv6ComponentsSize(ipv6_parsed, &num_bytes_of_contraction)) {
+    return false;
+  }
+
+  size_t cur_index_in_address = 0;
+
+  // Loop through each hex components, and contraction in order.
+  for (decltype(ipv6_parsed.num_hex_components) i = 0;
+       i <= ipv6_parsed.num_hex_components; ++i) {
+    // Append the contraction if it appears before this component.
+    if (i == ipv6_parsed.index_of_contraction) {
+      for (int j = 0; j < num_bytes_of_contraction; ++j) {
+        address[cur_index_in_address++] = 0;
+      }
+    }
+    // Append the hex component's value.
+    if (i != ipv6_parsed.num_hex_components) {
+      // Get the 16-bit value for this hex component.
+      uint16_t number = IPv6HexComponentToNumber<CHAR>(
+          trimmed.data(), ipv6_parsed.hex_components[i]);
+      // Append to |address|, in network byte order.
+      address[cur_index_in_address++] = (number & 0xFF00) >> 8;
+      address[cur_index_in_address++] = (number & 0x00FF);
+    }
+  }
+
+  // If there was an IPv4 section, convert it into a 32-bit number and append
+  // it to |address|.
+  if (ipv6_parsed.ipv4_component.is_valid()) {
+    // Append the 32-bit number to |address|.
+    int num_ipv4_components = 0;
+    // IPv4AddressToNumber will remove the trailing dot from the component.
+    bool trailing_dot =
+        ipv6_parsed.ipv4_component.is_nonempty() &&
+        trimmed[static_cast<size_t>(ipv6_parsed.ipv4_component.end() - 1)] ==
+            '.';
+    // The URL standard requires the embedded IPv4 address to be concisely
+    // composed of 4 parts and disallows terminal dots.
+    // See https://url.spec.whatwg.org/#concept-ipv6-parser
+    if (CanonHostInfo::IPV4 !=
+        DoIPv4AddressToNumber(ipv6_parsed.ipv4_component.AsViewOn(trimmed),
+                              address.subspan(cur_index_in_address),
+                              &num_ipv4_components)) {
+      return false;
+    }
+    if ((num_ipv4_components != 4 || trailing_dot)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace internal
+
 // Writes the given IPv4 address to |output|.
+// `address` must have 4 or more elements.
 COMPONENT_EXPORT(URL)
-void AppendIPv4Address(const unsigned char address[4], CanonOutput* output);
+void AppendIPv4Address(gurl_base::span<const uint8_t> address, CanonOutput* output);
 
 // Writes the given IPv6 address to |output|.
+// `address` must have 16 or more elements.
 COMPONENT_EXPORT(URL)
-void AppendIPv6Address(const unsigned char address[16], CanonOutput* output);
+void AppendIPv6Address(gurl_base::span<const uint8_t> address, CanonOutput* output);
 
 // Converts an IPv4 address to a 32-bit number (network byte order).
+// `address` must have 4 or more elements.
 //
 // Possible return values:
 //   IPV4    - IPv4 address was successfully parsed.
@@ -31,29 +571,42 @@
 // On success, |num_ipv4_components| will be populated with the number of
 // components in the IPv4 address.
 COMPONENT_EXPORT(URL)
-CanonHostInfo::Family IPv4AddressToNumber(const char* spec,
-                                          const Component& host,
-                                          unsigned char address[4],
-                                          int* num_ipv4_components);
+constexpr CanonHostInfo::Family IPv4AddressToNumber(std::string_view host_view,
+                                                    gurl_base::span<uint8_t> address,
+                                                    int* num_ipv4_components) {
+  return internal::DoIPv4AddressToNumber<char>(host_view, address,
+                                               num_ipv4_components);
+}
+
 COMPONENT_EXPORT(URL)
-CanonHostInfo::Family IPv4AddressToNumber(const char16_t* spec,
-                                          const Component& host,
-                                          unsigned char address[4],
-                                          int* num_ipv4_components);
+constexpr CanonHostInfo::Family IPv4AddressToNumber(
+    std::u16string_view host_view,
+    gurl_base::span<uint8_t> address,
+    int* num_ipv4_components) {
+  return internal::DoIPv4AddressToNumber<char16_t>(host_view, address,
+                                                   num_ipv4_components);
+}
 
 // Converts an IPv6 address to a 128-bit number (network byte order), returning
 // true on success. False means that the input was not a valid IPv6 address.
 //
 // NOTE that |host| is expected to be surrounded by square brackets.
 // i.e. "[::1]" rather than "::1".
+//
+// `address` must have 16 or more elements.
 COMPONENT_EXPORT(URL)
-bool IPv6AddressToNumber(const char* spec,
-                         const Component& host,
-                         unsigned char address[16]);
+constexpr bool IPv6AddressToNumber(std::string_view host_view,
+                                   gurl_base::span<uint8_t> address) {
+  return internal::DoIPv6AddressToNumber<char, unsigned char>(host_view,
+                                                              address);
+}
+
 COMPONENT_EXPORT(URL)
-bool IPv6AddressToNumber(const char16_t* spec,
-                         const Component& host,
-                         unsigned char address[16]);
+constexpr bool IPv6AddressToNumber(std::u16string_view host_view,
+                                   gurl_base::span<uint8_t> address) {
+  return internal::DoIPv6AddressToNumber<char16_t, char16_t>(host_view,
+                                                             address);
+}
 
 }  // namespace url
 
diff --git a/url/url_canon_mailtourl.cc b/url/url_canon_mailtourl.cc
index cbd4bb4..8f2f49d 100644
--- a/url/url_canon_mailtourl.cc
+++ b/url/url_canon_mailtourl.cc
@@ -4,6 +4,7 @@
 
 // Functions for canonicalizing "mailto:" URLs.
 
+#include "base/compiler_specific.h"
 #include "url/url_canon.h"
 #include "url/url_canon_internal.h"
 #include "url/url_file.h"
@@ -31,7 +32,7 @@
 }
 
 template <typename CHAR, typename UCHAR>
-bool DoCanonicalizeMailtoURL(const URLComponentSource<CHAR>& source,
+bool DoCanonicalizeMailtoUrl(const URLComponentSource<CHAR>& source,
                              const Parsed& parsed,
                              CanonOutput* output,
                              Parsed* new_parsed) {
@@ -59,7 +60,7 @@
     // ASCII characters alone.
     size_t end = static_cast<size_t>(parsed.path.end());
     for (size_t i = static_cast<size_t>(parsed.path.begin); i < end; ++i) {
-      UCHAR uch = static_cast<UCHAR>(source.path[i]);
+      UCHAR uch = static_cast<UCHAR>(UNSAFE_TODO(source.path[i]));
       if (ShouldEncodeMailboxCharacter<UCHAR>(uch))
         success &= AppendUTF8EscapedChar(source.path, &i, end, output);
       else
@@ -73,7 +74,7 @@
   }
 
   // Query -- always use the default UTF8 charset converter.
-  CanonicalizeQuery(source.query, parsed.query, NULL,
+  CanonicalizeQuery(parsed.query.maybe_as_string_view_on(source.query), nullptr,
                     output, &new_parsed->query);
 
   return success;
@@ -81,47 +82,46 @@
 
 } // namespace
 
-bool CanonicalizeMailtoURL(const char* spec,
-                           int spec_len,
+bool CanonicalizeMailtoUrl(std::string_view spec,
                            const Parsed& parsed,
                            CanonOutput* output,
                            Parsed* new_parsed) {
-  return DoCanonicalizeMailtoURL<char, unsigned char>(
-      URLComponentSource<char>(spec), parsed, output, new_parsed);
+  return DoCanonicalizeMailtoUrl<char, unsigned char>(
+      URLComponentSource<char>(spec.data()), parsed, output, new_parsed);
 }
 
-bool CanonicalizeMailtoURL(const char16_t* spec,
-                           int spec_len,
+bool CanonicalizeMailtoUrl(std::u16string_view spec,
                            const Parsed& parsed,
                            CanonOutput* output,
                            Parsed* new_parsed) {
-  return DoCanonicalizeMailtoURL<char16_t, char16_t>(
-      URLComponentSource<char16_t>(spec), parsed, output, new_parsed);
+  return DoCanonicalizeMailtoUrl<char16_t, char16_t>(
+      URLComponentSource<char16_t>(spec.data()), parsed, output, new_parsed);
 }
 
-bool ReplaceMailtoURL(const char* base,
+bool ReplaceMailtoUrl(std::string_view base,
                       const Parsed& base_parsed,
                       const Replacements<char>& replacements,
                       CanonOutput* output,
                       Parsed* new_parsed) {
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupOverrideComponents(base, replacements, &source, &parsed);
-  return DoCanonicalizeMailtoURL<char, unsigned char>(
-      source, parsed, output, new_parsed);
+  SetupOverrideComponents(base.data(), replacements, &source, &parsed);
+  return DoCanonicalizeMailtoUrl<char, unsigned char>(source, parsed, output,
+                                                      new_parsed);
 }
 
-bool ReplaceMailtoURL(const char* base,
+bool ReplaceMailtoUrl(std::string_view base,
                       const Parsed& base_parsed,
                       const Replacements<char16_t>& replacements,
                       CanonOutput* output,
                       Parsed* new_parsed) {
   RawCanonOutput<1024> utf8;
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupUTF16OverrideComponents(base, replacements, &utf8, &source, &parsed);
-  return DoCanonicalizeMailtoURL<char, unsigned char>(
-      source, parsed, output, new_parsed);
+  SetupUTF16OverrideComponents(base.data(), replacements, &utf8, &source,
+                               &parsed);
+  return DoCanonicalizeMailtoUrl<char, unsigned char>(source, parsed, output,
+                                                      new_parsed);
 }
 
 }  // namespace url
diff --git a/url/url_canon_non_special_url.cc b/url/url_canon_non_special_url.cc
new file mode 100644
index 0000000..d0b0f9a
--- /dev/null
+++ b/url/url_canon_non_special_url.cc
@@ -0,0 +1,264 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Functions to canonicalize non-special URLs.
+
+#include "url/url_canon.h"
+#include "url/url_canon_internal.h"
+
+namespace url {
+
+namespace {
+
+template <typename CHAR>
+bool DoCanonicalizeNonSpecialUrl(const URLComponentSource<CHAR>& source,
+                                 const Parsed& parsed,
+                                 CharsetConverter* query_converter,
+                                 CanonOutput& output,
+                                 Parsed& new_parsed) {
+  // The implementation is similar to `DoCanonicalizeStandardURL()`, but there
+  // are many subtle differences. So we have a different function for
+  // canonicalizing non-special URLs.
+  //
+  // Since canonicalization is also used from url::ReplaceComponents(),
+  // we have to handle an invalid URL replacement here, such as:
+  //
+  // > const url = "git:///";
+  // > url.username = "x";
+  // > url.href
+  // "git:///" (this should not be "git://x@").
+
+  GURL_DCHECK(!parsed.has_opaque_path);
+
+  // Scheme: this will append the colon.
+  bool success =
+      CanonicalizeScheme(parsed.scheme.maybe_as_string_view_on(source.scheme),
+                         &output, &new_parsed.scheme);
+  bool have_authority =
+      (parsed.username.is_valid() || parsed.password.is_valid() ||
+       parsed.host.is_valid() || parsed.port.is_valid());
+
+  // Non-special URL examples which should be carefully handled:
+  //
+  // | URL      | parsed.user   | parsed.host   | have_authority | Valid URL? |
+  // |----------+---------------+---------------+----------------+------------|
+  // | git:/a   | invalid       | invalid       | false          | valid      |
+  // | git://@/ | valid (empty) | invalid       | true           | invalid    |
+  // | git:///  | invalid       | valid (empty) | true           | valid      |
+
+  if (have_authority) {
+    // Only write the authority separators when we have a scheme.
+    if (parsed.scheme.is_valid()) {
+      output.push_back('/');
+      output.push_back('/');
+    }
+
+    // Username and Password
+    //
+    // URL Standard:
+    // - https://url.spec.whatwg.org/#cannot-have-a-username-password-port
+    // - https://url.spec.whatwg.org/#dom-url-username
+    // - https://url.spec.whatwg.org/#dom-url-password
+    if (parsed.host.is_nonempty()) {
+      // User info: the canonicalizer will handle the : and @.
+      success &= CanonicalizeUserInfo(
+          parsed.username.maybe_as_string_view_on(source.username),
+          parsed.password.maybe_as_string_view_on(source.password), &output,
+          &new_parsed.username, &new_parsed.password);
+    } else {
+      new_parsed.username.reset();
+      new_parsed.password.reset();
+    }
+
+    // Host
+    if (parsed.host.is_valid()) {
+      success &= CanonicalizeNonSpecialHost(
+          std::basic_string_view<CHAR>(
+              source.host, parsed.host.is_valid() ? parsed.host.end() : 0),
+          parsed.host, output, new_parsed.host);
+    } else {
+      new_parsed.host.reset();
+      // URL is invalid if `have_authority` is true, but `parsed.host` is
+      // invalid. Example: "git://@/".
+      success = false;
+    }
+
+    // Port
+    //
+    // URL Standard:
+    // - https://url.spec.whatwg.org/#cannot-have-a-username-password-port
+    // - https://url.spec.whatwg.org/#dom-url-port
+    if (parsed.host.is_nonempty()) {
+      success &=
+          CanonicalizePort(parsed.port.maybe_as_string_view_on(source.port),
+                           PORT_UNSPECIFIED, &output, &new_parsed.port);
+    } else {
+      new_parsed.port.reset();
+    }
+  } else {
+    // No authority, clear the components.
+    new_parsed.host.reset();
+    new_parsed.username.reset();
+    new_parsed.password.reset();
+    new_parsed.port.reset();
+  }
+
+  // Path
+  if (parsed.path.is_valid()) {
+    if (!parsed.host.is_valid() && parsed.path.is_empty()) {
+      // Handle an edge case: Replacing non-special path-only URL's pathname
+      // with an empty path.
+      //
+      // Path-only non-special URLs cannot have their paths erased.
+      //
+      // Example:
+      //
+      // > const url = new URL("git:/a");
+      // > url.pathname = '';
+      // > url.href
+      // => The result should be "git:/", instead of "git:".
+      // > url.pathname
+      // => The result should be "/", instead of "".
+      //
+      // URL Standard is https://url.spec.whatwg.org/#dom-url-pathname, however,
+      // it would take some time to understand why url.pathname ends up as "/"
+      // in this case. Please read the URL Standard carefully to understand
+      // that.
+      new_parsed.path.begin = output.length();
+      output.push_back('/');
+      new_parsed.path.len = output.length() - new_parsed.path.begin;
+    } else {
+      success &= CanonicalizePath(parsed.path.as_string_view_on(source.path),
+                                  CanonMode::kNonSpecialURL, &output,
+                                  &new_parsed.path);
+      if (!parsed.host.is_valid() && new_parsed.path.is_valid() &&
+          new_parsed.path.as_string_view_on(output.view().data())
+              .starts_with("//")) {
+        // To avoid path being treated as the host, prepend "/." to the path".
+        //
+        // Examples:
+        //
+        // > const url = new URL("git:/.//a");
+        // > url.href
+        // => The result should be "git:/.//a", instead of "git://a".
+        //
+        // > const url = new URL("git:/");
+        // > url.pathname = "/.//a"
+        // > url.href
+        // => The result should be "git:/.//a", instead of "git://a".
+        //
+        // URL Standard: https://url.spec.whatwg.org/#concept-url-serializer
+        //
+        // > 3. If url’s host is null, url does not have an opaque path, url’s
+        // > path’s size is greater than 1, and url’s path[0] is the empty
+        // > string, then append U+002F (/) followed by U+002E (.) to output.
+        //
+        // Since the path length is unknown in advance, we post-process the new
+        // path here. This case is likely to be infrequent, so the performance
+        // impact should be minimal.
+        size_t prior_output_length = output.length();
+        output.Insert(new_parsed.path.begin, "/.");
+        // Adjust path.
+        new_parsed.path.begin += output.length() - prior_output_length;
+      }
+    }
+  } else {
+    new_parsed.path.reset();
+  }
+
+  // Query
+  CanonicalizeQuery(parsed.query.maybe_as_string_view_on(source.query),
+                    query_converter, &output, &new_parsed.query);
+
+  // Ref: ignore failure for this, since the page can probably still be loaded.
+  CanonicalizeRef(parsed.ref.maybe_as_string_view_on(source.ref), &output,
+                  &new_parsed.ref);
+
+  // Carry over the flag for potentially dangling markup:
+  if (parsed.potentially_dangling_markup) {
+    new_parsed.potentially_dangling_markup = true;
+  }
+
+  return success;
+}
+
+}  // namespace
+
+bool CanonicalizeNonSpecialUrl(std::string_view spec,
+                               const Parsed& parsed,
+                               CharsetConverter* query_converter,
+                               CanonOutput& output,
+                               Parsed& new_parsed) {
+  // Carry over the flag.
+  new_parsed.has_opaque_path = parsed.has_opaque_path;
+
+  if (parsed.has_opaque_path) {
+    return CanonicalizePathUrl(spec, parsed, &output, &new_parsed);
+  }
+  return DoCanonicalizeNonSpecialUrl(URLComponentSource(spec.data()), parsed,
+                                     query_converter, output, new_parsed);
+}
+
+bool CanonicalizeNonSpecialUrl(std::u16string_view spec,
+                               const Parsed& parsed,
+                               CharsetConverter* query_converter,
+                               CanonOutput& output,
+                               Parsed& new_parsed) {
+  // Carry over the flag.
+  new_parsed.has_opaque_path = parsed.has_opaque_path;
+
+  if (parsed.has_opaque_path) {
+    return CanonicalizePathUrl(spec, parsed, &output, &new_parsed);
+  }
+  return DoCanonicalizeNonSpecialUrl(URLComponentSource(spec.data()), parsed,
+                                     query_converter, output, new_parsed);
+}
+
+bool ReplaceNonSpecialUrl(std::string_view base,
+                          const Parsed& base_parsed,
+                          const Replacements<char>& replacements,
+                          CharsetConverter* query_converter,
+                          CanonOutput& output,
+                          Parsed& new_parsed) {
+  // Carry over the flag.
+  new_parsed.has_opaque_path = base_parsed.has_opaque_path;
+
+  if (base_parsed.has_opaque_path) {
+    return ReplacePathUrl(base, base_parsed, replacements, &output,
+                          &new_parsed);
+  }
+
+  URLComponentSource<char> source(base.data());
+  Parsed parsed(base_parsed);
+  SetupOverrideComponents(base.data(), replacements, &source, &parsed);
+  return DoCanonicalizeNonSpecialUrl(source, parsed, query_converter, output,
+                                     new_parsed);
+}
+
+// For 16-bit replacements, we turn all the replacements into UTF-8 so the
+// regular code path can be used.
+bool ReplaceNonSpecialUrl(std::string_view base,
+                          const Parsed& base_parsed,
+                          const Replacements<char16_t>& replacements,
+                          CharsetConverter* query_converter,
+                          CanonOutput& output,
+                          Parsed& new_parsed) {
+  // Carry over the flag.
+  new_parsed.has_opaque_path = base_parsed.has_opaque_path;
+
+  if (base_parsed.has_opaque_path) {
+    return ReplacePathUrl(base, base_parsed, replacements, &output,
+                          &new_parsed);
+  }
+
+  RawCanonOutput<1024> utf8;
+  URLComponentSource<char> source(base.data());
+  Parsed parsed(base_parsed);
+  SetupUTF16OverrideComponents(base.data(), replacements, &utf8, &source,
+                               &parsed);
+  return DoCanonicalizeNonSpecialUrl(source, parsed, query_converter, output,
+                                     new_parsed);
+}
+
+}  // namespace url
diff --git a/url/url_canon_path.cc b/url/url_canon_path.cc
index 966ce27..49c3314 100644
--- a/url/url_canon_path.cc
+++ b/url/url_canon_path.cc
@@ -5,8 +5,11 @@
 #include <limits.h>
 
 #include <optional>
+#include <string_view>
+
 #include "polyfills/base/check.h"
 #include "polyfills/base/check_op.h"
+#include "base/compiler_specific.h"
 #include "url/url_canon.h"
 #include "url/url_canon_internal.h"
 #include "url/url_features.h"
@@ -40,9 +43,8 @@
 // only flag that may be combined with others.
 //
 // This table was used to be designed to match exactly what IE did with the
-// characters, however, which doesn't comply with the URL Standard as of Jun
-// 2023. See http://crbug.com/1400251 and http://crbug.com/1252531 for efforts
-// to comply with the URL Standard.
+// characters, however, which doesn't comply with the URL Standard as of Dec
+// 2023. See https://crbug.com/1509295.
 //
 // Dot is even more special, and the escaped version is handled specially by
 // IsDot. Therefore, we don't need the "escape" flag. We just need the "special"
@@ -109,7 +111,7 @@
     *consumed_len = 0;
     return DIRECTORY_CUR;
   }
-  if (IsURLSlash(spec[after_dot])) {
+  if (IsSlashOrBackslash(UNSAFE_TODO(spec[after_dot]))) {
     // Single dot followed by a slash.
     *consumed_len = 1;  // Consume the slash
     return DIRECTORY_CUR;
@@ -123,7 +125,7 @@
       *consumed_len = second_dot_len;
       return DIRECTORY_UP;
     }
-    if (IsURLSlash(spec[after_second_dot])) {
+    if (IsSlashOrBackslash(UNSAFE_TODO(spec[after_second_dot]))) {
       // Double dot followed by a slash.
       *consumed_len = second_dot_len + 1;
       return DIRECTORY_UP;
@@ -178,33 +180,35 @@
 // no web browsers do this, and we don't want incompatibilities, even though
 // it would be correct for most systems.
 template <typename CHAR, typename UCHAR>
-bool DoPartialPathInternal(const CHAR* spec,
-                           const Component& path,
+bool DoPartialPathInternal(std::optional<std::basic_string_view<CHAR>> path,
                            size_t path_begin_in_output,
+                           CanonMode canon_mode,
                            CanonOutput* output) {
-  if (path.is_empty())
+  if (!path.has_value() || path->empty()) {
     return true;
+  }
 
-  size_t end = static_cast<size_t>(path.end());
+  auto& path_value = *path;
 
   bool success = true;
-  for (size_t i = static_cast<size_t>(path.begin); i < end; i++) {
-    UCHAR uch = static_cast<UCHAR>(spec[i]);
+  for (size_t i = 0; i < path_value.size(); i++) {
+    UCHAR uch = static_cast<UCHAR>(path_value[i]);
     if (sizeof(CHAR) > 1 && uch >= 0x80) {
       // We only need to test wide input for having non-ASCII characters. For
       // narrow input, we'll always just use the lookup table. We don't try to
       // do anything tricky with decoding/validating UTF-8. This function will
       // read one or two UTF-16 characters and append the output as UTF-8. This
       // call will be removed in 8-bit mode.
-      success &= AppendUTF8EscapedChar(spec, &i, end, output);
+      success &= AppendUTF8EscapedChar(path_value.data(), &i, path_value.size(),
+                                       output);
     } else {
       // Normal ASCII character or 8-bit input, use the lookup table.
       unsigned char out_ch = static_cast<unsigned char>(uch);
-      unsigned char flags = kPathCharLookup[out_ch];
+      unsigned char flags = UNSAFE_TODO(kPathCharLookup[out_ch]);
       if (flags & SPECIAL) {
         // Needs special handling of some sort.
         size_t dotlen;
-        if ((dotlen = IsDot(spec, i, end)) > 0) {
+        if ((dotlen = IsDot(path_value.data(), i, path_value.size())) > 0) {
           // See if this dot was preceded by a slash in the output.
           //
           // Note that we check this in the case of dots so we don't have to
@@ -215,8 +219,8 @@
               output->at(output->length() - 1) == '/') {
             // Slash followed by a dot, check to see if this is means relative
             size_t consumed_len;
-            switch (ClassifyAfterDot<CHAR>(spec, i + dotlen, end,
-                                           &consumed_len)) {
+            switch (ClassifyAfterDot<CHAR>(path_value.data(), i + dotlen,
+                                           path_value.size(), &consumed_len)) {
               case NOT_A_DIRECTORY:
                 // Copy the dot to the output, it means nothing special.
                 output->push_back('.');
@@ -238,17 +242,27 @@
           }
 
         } else if (out_ch == '\\') {
-          // Convert backslashes to forward slashes
-          output->push_back('/');
-
+          if (canon_mode == CanonMode::kSpecialURL ||
+              canon_mode == CanonMode::kFileURL) {
+            // Backslashes are path separators in special URLs.
+            //
+            // URL Standard: https://url.spec.whatwg.org/#path-state
+            // > 1. url is special and c is U+005C (\)
+            //
+            // Convert backslashes to forward slashes.
+            output->push_back('/');
+          } else {
+            output->push_back(out_ch);
+          }
         } else if (out_ch == '%') {
           // Handle escape sequences.
           unsigned char unused_unescaped_value;
-          if (DecodeEscaped(spec, &i, end, &unused_unescaped_value)) {
+          if (DecodeEscaped(path_value.data(), &i, path_value.size(),
+                            &unused_unescaped_value)) {
             // Valid escape sequence. We should just copy it exactly.
             output->push_back('%');
-            output->push_back(static_cast<char>(spec[i - 1]));
-            output->push_back(static_cast<char>(spec[i]));
+            output->push_back(static_cast<char>(path_value[i - 1]));
+            output->push_back(static_cast<char>(path_value[i]));
           } else {
             // Invalid escape sequence. IE7+ rejects any URLs with such
             // sequences, while other browsers pass them through unchanged. We
@@ -274,36 +288,52 @@
 // publicly exposed CanonOutput structure similar to DoPath().  Returns
 // true if successful.
 template <typename CHAR, typename UCHAR>
-bool DoPartialPath(const CHAR* spec,
-                   const Component& path,
+bool DoPartialPath(std::optional<std::basic_string_view<CHAR>> path,
                    CanonOutput* output,
                    Component* out_path) {
   out_path->begin = output->length();
-  bool success =
-      DoPartialPathInternal<CHAR, UCHAR>(spec, path, out_path->begin, output);
+  bool success = DoPartialPathInternal<CHAR, UCHAR>(
+      path, out_path->begin,
+      // TODO(crbug.com/40063064): Support Non-special URLs.
+      CanonMode::kSpecialURL, output);
   out_path->len = output->length() - out_path->begin;
   return success;
 }
 
-template<typename CHAR, typename UCHAR>
-bool DoPath(const CHAR* spec,
-            const Component& path,
+template <typename CHAR, typename UCHAR>
+bool DoPath(std::optional<std::basic_string_view<CHAR>> path,
+            CanonMode canon_mode,
             CanonOutput* output,
             Component* out_path) {
+  // URL Standard:
+  // - https://url.spec.whatwg.org/#path-start-state
+  // - https://url.spec.whatwg.org/#path-state
+
   bool success = true;
   out_path->begin = output->length();
-  if (path.is_nonempty()) {
+  if (path.has_value() && !path->empty()) {
     // Write out an initial slash if the input has none. If we just parse a URL
     // and then canonicalize it, it will of course have a slash already. This
     // check is for the replacement and relative URL resolving cases of file
     // URLs.
-    if (!IsURLSlash(spec[path.begin]))
+    if (!IsSlashOrBackslash((*path)[0])) {
       output->push_back('/');
+    }
 
-    success =
-        DoPartialPathInternal<CHAR, UCHAR>(spec, path, out_path->begin, output);
-  } else {
-    // No input, canonical path is a slash.
+    success = DoPartialPathInternal<CHAR, UCHAR>(*path, out_path->begin,
+                                                 canon_mode, output);
+  } else if (canon_mode == CanonMode::kSpecialURL ||
+             canon_mode == CanonMode::kFileURL) {
+    // No input, canonical path is a slash for special URLs, but it is empty for
+    // non-special URLs.
+    //
+    // Implementation note:
+    //
+    // According to the URL Standard, for non-special URLs whose parsed path is
+    // empty, such as "git://host", the state-machine finishes in the
+    // `path-start-state` without entering the `path-state`. As a result, the
+    // url's path remains an empty array. Therefore, no slash should be
+    // appended.
     output->push_back('/');
   }
   out_path->len = output->length() - out_path->begin;
@@ -312,48 +342,70 @@
 
 }  // namespace
 
+bool CanonicalizePath(std::optional<std::string_view> path,
+                      CanonMode canon_mode,
+                      CanonOutput* output,
+                      Component* out_path) {
+  return DoPath<char, unsigned char>(path, canon_mode, output, out_path);
+}
+
+bool CanonicalizePath(std::optional<std::u16string_view> path,
+                      CanonMode canon_mode,
+                      CanonOutput* output,
+                      Component* out_path) {
+  return DoPath<char16_t, char16_t>(path, canon_mode, output, out_path);
+}
+
+// TODO(crbug.com/422740114): Remove this after `//net/third_party/quiche` is
+// not depending on it.
 bool CanonicalizePath(const char* spec,
                       const Component& path,
                       CanonOutput* output,
                       Component* out_path) {
-  return DoPath<char, unsigned char>(spec, path, output, out_path);
+  return DoPath<char, unsigned char>(path.maybe_as_string_view_on(spec),
+                                     CanonMode::kSpecialURL, output, out_path);
 }
 
-bool CanonicalizePath(const char16_t* spec,
-                      const Component& path,
+bool CanonicalizePath(std::optional<std::string_view> path,
                       CanonOutput* output,
                       Component* out_path) {
-  return DoPath<char16_t, char16_t>(spec, path, output, out_path);
+  return DoPath<char, unsigned char>(path, CanonMode::kSpecialURL, output,
+                                     out_path);
 }
 
-bool CanonicalizePartialPath(const char* spec,
-                             const Component& path,
+bool CanonicalizePath(std::optional<std::u16string_view> path,
+                      CanonOutput* output,
+                      Component* out_path) {
+  return DoPath<char16_t, char16_t>(path, CanonMode::kSpecialURL, output,
+                                    out_path);
+}
+
+bool CanonicalizePartialPath(std::optional<std::string_view> path,
                              CanonOutput* output,
                              Component* out_path) {
-  return DoPartialPath<char, unsigned char>(spec, path, output, out_path);
+  return DoPartialPath<char, unsigned char>(path, output, out_path);
 }
 
-bool CanonicalizePartialPath(const char16_t* spec,
-                             const Component& path,
+bool CanonicalizePartialPath(std::optional<std::u16string_view> path,
                              CanonOutput* output,
                              Component* out_path) {
-  return DoPartialPath<char16_t, char16_t>(spec, path, output, out_path);
+  return DoPartialPath<char16_t, char16_t>(path, output, out_path);
 }
 
-bool CanonicalizePartialPathInternal(const char* spec,
-                                     const Component& path,
+bool CanonicalizePartialPathInternal(std::string_view path,
                                      size_t path_begin_in_output,
+                                     CanonMode canon_mode,
                                      CanonOutput* output) {
-  return DoPartialPathInternal<char, unsigned char>(
-      spec, path, path_begin_in_output, output);
+  return DoPartialPathInternal<char, unsigned char>(path, path_begin_in_output,
+                                                    canon_mode, output);
 }
 
-bool CanonicalizePartialPathInternal(const char16_t* spec,
-                                     const Component& path,
+bool CanonicalizePartialPathInternal(std::u16string_view path,
                                      size_t path_begin_in_output,
+                                     CanonMode canon_mode,
                                      CanonOutput* output) {
-  return DoPartialPathInternal<char16_t, char16_t>(
-      spec, path, path_begin_in_output, output);
+  return DoPartialPathInternal<char16_t, char16_t>(path, path_begin_in_output,
+                                                   canon_mode, output);
 }
 
 }  // namespace url
diff --git a/url/url_canon_pathurl.cc b/url/url_canon_pathurl.cc
index 85983a8..6ebf1c1 100644
--- a/url/url_canon_pathurl.cc
+++ b/url/url_canon_pathurl.cc
@@ -17,12 +17,14 @@
 // |new_component|. If |separator| is non-zero, it is pre-pended to |output|
 // prior to the canonicalized component; i.e. for the '?' or '#' characters.
 template <typename CHAR, typename UCHAR>
-void DoCanonicalizePathComponent(const CHAR* source,
-                                 const Component& component,
-                                 char separator,
-                                 CanonOutput* output,
-                                 Component* new_component) {
-  if (component.is_valid()) {
+void DoCanonicalizePathComponent(
+    std::optional<std::basic_string_view<CHAR>> source,
+    char separator,
+    CanonOutput* output,
+    Component* new_component) {
+  if (source.has_value()) {
+    auto& source_value = *source;
+
     if (separator)
       output->push_back(separator);
     // Copy the path using path URL's more lax escaping rules (think for
@@ -32,13 +34,14 @@
     // https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state
     // https://url.spec.whatwg.org/#c0-control-percent-encode-set
     new_component->begin = output->length();
-    size_t end = static_cast<size_t>(component.end());
-    for (size_t i = static_cast<size_t>(component.begin); i < end; i++) {
-      UCHAR uch = static_cast<UCHAR>(source[i]);
-      if (uch < 0x20 || uch > 0x7E)
-        AppendUTF8EscapedChar(source, &i, end, output);
-      else
+    for (size_t i = 0; i < source_value.size(); i++) {
+      UCHAR uch = static_cast<UCHAR>(source_value[i]);
+      if (IsInC0ControlPercentEncodeSet(uch)) {
+        AppendUTF8EscapedChar(source_value.data(), &i, source_value.size(),
+                              output);
+      } else {
         output->push_back(static_cast<char>(uch));
+      }
     }
     new_component->len = output->length() - new_component->begin;
   } else {
@@ -48,13 +51,14 @@
 }
 
 template <typename CHAR, typename UCHAR>
-bool DoCanonicalizePathURL(const URLComponentSource<CHAR>& source,
+bool DoCanonicalizePathUrl(const URLComponentSource<CHAR>& source,
                            const Parsed& parsed,
                            CanonOutput* output,
                            Parsed* new_parsed) {
   // Scheme: this will append the colon.
-  bool success = CanonicalizeScheme(source.scheme, parsed.scheme,
-                                    output, &new_parsed->scheme);
+  bool success =
+      CanonicalizeScheme(parsed.scheme.maybe_as_string_view_on(source.scheme),
+                         output, &new_parsed->scheme);
 
   // We assume there's no authority for path URLs. Note that hosts should never
   // have -1 length.
@@ -67,78 +71,77 @@
   //
   // Note: parsing the path part should never cause a failure, see
   // https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state
-  DoCanonicalizePathComponent<CHAR, UCHAR>(source.path, parsed.path, '\0',
-                                           output, &new_parsed->path);
+  DoCanonicalizePathComponent<CHAR, UCHAR>(
+      parsed.path.maybe_as_string_view_on(source.path), '\0', output,
+      &new_parsed->path);
 
   // Similar to mailto:, always use the default UTF-8 charset converter for
   // query.
-  CanonicalizeQuery(source.query, parsed.query, nullptr, output,
-                    &new_parsed->query);
+  CanonicalizeQuery(parsed.query.maybe_as_string_view_on(source.query), nullptr,
+                    output, &new_parsed->query);
 
-  CanonicalizeRef(source.ref, parsed.ref, output, &new_parsed->ref);
+  CanonicalizeRef(parsed.ref.maybe_as_string_view_on(source.ref), output,
+                  &new_parsed->ref);
 
   return success;
 }
 
 }  // namespace
 
-bool CanonicalizePathURL(const char* spec,
-                         int spec_len,
+bool CanonicalizePathUrl(std::string_view spec,
                          const Parsed& parsed,
                          CanonOutput* output,
                          Parsed* new_parsed) {
-  return DoCanonicalizePathURL<char, unsigned char>(
-      URLComponentSource<char>(spec), parsed, output, new_parsed);
+  return DoCanonicalizePathUrl<char, unsigned char>(
+      URLComponentSource<char>(spec.data()), parsed, output, new_parsed);
 }
 
-bool CanonicalizePathURL(const char16_t* spec,
-                         int spec_len,
+bool CanonicalizePathUrl(std::u16string_view spec,
                          const Parsed& parsed,
                          CanonOutput* output,
                          Parsed* new_parsed) {
-  return DoCanonicalizePathURL<char16_t, char16_t>(
-      URLComponentSource<char16_t>(spec), parsed, output, new_parsed);
+  return DoCanonicalizePathUrl<char16_t, char16_t>(
+      URLComponentSource<char16_t>(spec.data()), parsed, output, new_parsed);
 }
 
-void CanonicalizePathURLPath(const char* source,
-                             const Component& component,
+void CanonicalizePathUrlPath(std::optional<std::string_view> source,
                              CanonOutput* output,
                              Component* new_component) {
-  DoCanonicalizePathComponent<char, unsigned char>(source, component, '\0',
-                                                   output, new_component);
+  DoCanonicalizePathComponent<char, unsigned char>(source, '\0', output,
+                                                   new_component);
 }
 
-void CanonicalizePathURLPath(const char16_t* source,
-                             const Component& component,
+void CanonicalizePathUrlPath(std::optional<std::u16string_view> source,
                              CanonOutput* output,
                              Component* new_component) {
-  DoCanonicalizePathComponent<char16_t, char16_t>(source, component, '\0',
-                                                  output, new_component);
+  DoCanonicalizePathComponent<char16_t, char16_t>(source, '\0', output,
+                                                  new_component);
 }
 
-bool ReplacePathURL(const char* base,
+bool ReplacePathUrl(std::string_view base,
                     const Parsed& base_parsed,
                     const Replacements<char>& replacements,
                     CanonOutput* output,
                     Parsed* new_parsed) {
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupOverrideComponents(base, replacements, &source, &parsed);
-  return DoCanonicalizePathURL<char, unsigned char>(
-      source, parsed, output, new_parsed);
+  SetupOverrideComponents(base.data(), replacements, &source, &parsed);
+  return DoCanonicalizePathUrl<char, unsigned char>(source, parsed, output,
+                                                    new_parsed);
 }
 
-bool ReplacePathURL(const char* base,
+bool ReplacePathUrl(std::string_view base,
                     const Parsed& base_parsed,
                     const Replacements<char16_t>& replacements,
                     CanonOutput* output,
                     Parsed* new_parsed) {
   RawCanonOutput<1024> utf8;
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupUTF16OverrideComponents(base, replacements, &utf8, &source, &parsed);
-  return DoCanonicalizePathURL<char, unsigned char>(
-      source, parsed, output, new_parsed);
+  SetupUTF16OverrideComponents(base.data(), replacements, &utf8, &source,
+                               &parsed);
+  return DoCanonicalizePathUrl<char, unsigned char>(source, parsed, output,
+                                                    new_parsed);
 }
 
 }  // namespace url
diff --git a/url/url_canon_query.cc b/url/url_canon_query.cc
index b48800c..9b6021a 100644
--- a/url/url_canon_query.cc
+++ b/url/url_canon_query.cc
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string_view>
+
+#include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "url/url_canon.h"
 #include "url/url_canon_internal.h"
 
@@ -43,107 +47,97 @@
 // match the given |type| in SharedCharTypes. This version will accept 8 or 16
 // bit characters, but assumes that they have only 7-bit values. It also assumes
 // that all UTF-8 values are correct, so doesn't bother checking
-template<typename CHAR>
-void AppendRaw8BitQueryString(const CHAR* source, int length,
+template <typename CHAR>
+void AppendRaw8BitQueryString(std::basic_string_view<CHAR> source,
                               CanonOutput* output) {
-  for (int i = 0; i < length; i++) {
-    if (!IsQueryChar(static_cast<unsigned char>(source[i])))
-      AppendEscapedChar(static_cast<unsigned char>(source[i]), output);
-    else  // Doesn't need escaping.
-      output->push_back(static_cast<char>(source[i]));
+  for (const CHAR& c : source) {
+    unsigned char uc = static_cast<unsigned char>(c);
+    if (!IsQueryChar(uc)) {
+      AppendEscapedChar(uc, output);
+    } else {  // Doesn't need escaping.
+      output->push_back(static_cast<char>(uc));
+    }
   }
 }
 
 // Runs the converter on the given UTF-8 input. Since the converter expects
 // UTF-16, we have to convert first. The converter must be non-NULL.
-void RunConverter(const char* spec,
-                  const Component& query,
+void RunConverter(std::string_view input,
                   CharsetConverter* converter,
                   CanonOutput* output) {
-  GURL_DCHECK(query.is_valid());
   // This function will replace any misencoded values with the invalid
   // character. This is what we want so we don't have to check for error.
   RawCanonOutputW<1024> utf16;
-  ConvertUTF8ToUTF16(&spec[query.begin], static_cast<size_t>(query.len),
-                     &utf16);
-  converter->ConvertFromUTF16(utf16.data(), utf16.length(), output);
+  ConvertUTF8ToUTF16(input, &utf16);
+  converter->ConvertFromUTF16(utf16.view(), output);
 }
 
 // Runs the converter with the given UTF-16 input. We don't have to do
 // anything, but this overridden function allows us to use the same code
 // for both UTF-8 and UTF-16 input.
-void RunConverter(const char16_t* spec,
-                  const Component& query,
+void RunConverter(std::u16string_view input,
                   CharsetConverter* converter,
                   CanonOutput* output) {
-  GURL_DCHECK(query.is_valid());
-  converter->ConvertFromUTF16(&spec[query.begin],
-                              static_cast<size_t>(query.len), output);
+  converter->ConvertFromUTF16(input, output);
 }
 
 template <typename CHAR, typename UCHAR>
-void DoConvertToQueryEncoding(const CHAR* spec,
-                              const Component& query,
+void DoConvertToQueryEncoding(std::basic_string_view<CHAR> input,
                               CharsetConverter* converter,
                               CanonOutput* output) {
   if (converter) {
     // Run the converter to get an 8-bit string, then append it, escaping
     // necessary values.
     RawCanonOutput<1024> eight_bit;
-    RunConverter(spec, query, converter, &eight_bit);
-    AppendRaw8BitQueryString(eight_bit.data(), eight_bit.length(), output);
+    RunConverter(input, converter, &eight_bit);
+    AppendRaw8BitQueryString(eight_bit.view(), output);
 
   } else {
     // No converter, do our own UTF-8 conversion.
-    AppendStringOfType(&spec[query.begin], static_cast<size_t>(query.len),
-                       CHAR_QUERY, output);
+    AppendStringOfType(input, CHAR_QUERY, output);
   }
 }
 
-template<typename CHAR, typename UCHAR>
-void DoCanonicalizeQuery(const CHAR* spec,
-                         const Component& query,
+template <typename CHAR, typename UCHAR>
+void DoCanonicalizeQuery(std::optional<std::basic_string_view<CHAR>> input,
                          CharsetConverter* converter,
                          CanonOutput* output,
                          Component* out_query) {
-  if (!query.is_valid()) {
+  if (!input.has_value()) {
     *out_query = Component();
     return;
   }
 
+  auto input_value = input.value();
+
   output->push_back('?');
   out_query->begin = output->length();
 
-  DoConvertToQueryEncoding<CHAR, UCHAR>(spec, query, converter, output);
+  DoConvertToQueryEncoding<CHAR, UCHAR>(input_value, converter, output);
 
   out_query->len = output->length() - out_query->begin;
 }
 
 }  // namespace
 
-void CanonicalizeQuery(const char* spec,
-                       const Component& query,
+void CanonicalizeQuery(std::optional<std::string_view> input,
                        CharsetConverter* converter,
                        CanonOutput* output,
                        Component* out_query) {
-  DoCanonicalizeQuery<char, unsigned char>(spec, query, converter,
-                                           output, out_query);
+  DoCanonicalizeQuery<char, unsigned char>(input, converter, output, out_query);
 }
 
-void CanonicalizeQuery(const char16_t* spec,
-                       const Component& query,
+void CanonicalizeQuery(std::optional<std::u16string_view> input,
                        CharsetConverter* converter,
                        CanonOutput* output,
                        Component* out_query) {
-  DoCanonicalizeQuery<char16_t, char16_t>(spec, query, converter, output,
-                                          out_query);
+  DoCanonicalizeQuery<char16_t, char16_t>(input, converter, output, out_query);
 }
 
-void ConvertUTF16ToQueryEncoding(const char16_t* input,
-                                 const Component& query,
+void ConvertUTF16ToQueryEncoding(std::u16string_view input,
                                  CharsetConverter* converter,
                                  CanonOutput* output) {
-  DoConvertToQueryEncoding<char16_t, char16_t>(input, query, converter, output);
+  DoConvertToQueryEncoding<char16_t, char16_t>(input, converter, output);
 }
 
 }  // namespace url
diff --git a/url/url_canon_relative.cc b/url/url_canon_relative.cc
index b9f7b98..fc2a784 100644
--- a/url/url_canon_relative.cc
+++ b/url/url_canon_relative.cc
@@ -2,10 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 // Canonicalizer functions for working with and resolving relative URLs.
 
 #include <algorithm>
 #include <ostream>
+#include <string_view>
 
 #include "polyfills/base/check_op.h"
 #include "base/strings/string_util.h"
@@ -59,7 +65,7 @@
                                     int spec_len) {
   if (start_offset >= spec_len)
     return false;
-  return IsURLSlash(spec[start_offset]) &&
+  return IsSlashOrBackslash(spec[start_offset]) &&
          DoesBeginWindowsDriveSpec(spec, start_offset + 1, spec_len);
 }
 
@@ -98,21 +104,20 @@
   return true;
 }
 
-// See IsRelativeURL in the header file for usage.
-template<typename CHAR>
-bool DoIsRelativeURL(const char* base,
+// See IsRelativeUrl in the header file for usage.
+template <typename CHAR>
+bool DoIsRelativeUrl(std::string_view base,
                      const Parsed& base_parsed,
-                     const CHAR* url,
-                     int url_len,
+                     std::basic_string_view<CHAR> input_url,
                      bool is_base_hierarchical,
                      bool* is_relative,
                      Component* relative_component) {
   *is_relative = false;  // So we can default later to not relative.
 
   // Trim whitespace and construct a new range for the substring.
-  int begin = 0;
-  TrimURL(url, &begin, &url_len);
-  if (begin >= url_len) {
+  auto [begin, end] = TrimUrl(input_url);
+  std::basic_string_view<CHAR> url = input_url.substr(begin, end - begin);
+  if (url.empty()) {
     // Empty URLs are relative, but do nothing.
     if (!is_base_hierarchical) {
       // Don't allow relative URLs if the base scheme doesn't support it.
@@ -135,9 +140,10 @@
   //
   // We require strict backslashes when detecting UNC since two forward
   // slashes should be treated a a relative URL with a hostname.
-  if (DoesBeginWindowsDriveSpec(url, begin, url_len) ||
-      DoesBeginUNCPath(url, begin, url_len, true))
+  if (DoesBeginWindowsDriveSpec(url.data(), 0, url.length()) ||
+      DoesBeginUNCPath(url.data(), 0, url.length(), true)) {
     return true;
+  }
 #endif  // WIN32
 
   // See if we've got a scheme, if not, we know this is a relative URL.
@@ -145,10 +151,9 @@
   // "http:foo.html" is a relative URL with path "foo.html". If the scheme is
   // empty, we treat it as relative (":foo"), like IE does.
   Component scheme;
-  const bool scheme_is_empty =
-      !ExtractScheme(url, url_len, &scheme) || scheme.len == 0;
+  const bool scheme_is_empty = !ExtractScheme(url, &scheme) || scheme.len == 0;
   if (scheme_is_empty) {
-    if (url[begin] == '#') {
+    if (url[0] == '#') {
       // |url| is a bare fragment (e.g. "#foo"). This can be resolved against
       // any base. Fall-through.
     } else if (!is_base_hierarchical) {
@@ -156,30 +161,37 @@
       return false;
     }
 
-    *relative_component = MakeRange(begin, url_len);
+    *relative_component = MakeRange(begin, begin + url.length());
     *is_relative = true;
     return true;
   }
 
   // If the scheme isn't valid, then it's relative.
-  if (!IsValidScheme(url, scheme)) {
-    if (url[begin] == '#' &&
-        gurl_base::FeatureList::IsEnabled(
-            kResolveBareFragmentWithColonOnNonHierarchical)) {
+  if (!IsValidScheme(url.data(), scheme)) {
+    if (url[0] == '#') {
       // |url| is a bare fragment (e.g. "#foo:bar"). This can be resolved
       // against any base. Fall-through.
     } else if (!is_base_hierarchical) {
       // Don't allow relative URLs if the base scheme doesn't support it.
       return false;
     }
-    *relative_component = MakeRange(begin, url_len);
+    *relative_component = MakeRange(begin, begin + url.length());
     *is_relative = true;
     return true;
   }
 
-  // If the scheme is not the same, then we can't count it as relative.
-  if (!AreSchemesEqual(base, base_parsed.scheme, url, scheme))
+  // If base scheme is not standard, or the schemes are different, we can't
+  // count it as relative.
+  //
+  // URL Standard: https://url.spec.whatwg.org/#scheme-state
+  //
+  // scheme state:
+  // > 2.6. Otherwise, if url is special, base is non-null, and base’s scheme is
+  // >      url’s scheme:
+  if (!IsStandard(base_parsed.scheme.MaybeAsViewOn(base)) ||
+      !AreSchemesEqual(base.data(), base_parsed.scheme, url.data(), scheme)) {
     return true;
+  }
 
   // When the scheme that they both share is not hierarchical, treat the
   // incoming scheme as absolute (this way with the base of "data:foo",
@@ -191,19 +203,21 @@
 
   // If it's a filesystem URL, the only valid way to make it relative is not to
   // supply a scheme. There's no equivalent to e.g. http:index.html.
-  if (CompareSchemeComponent(url, scheme, kFileSystemScheme))
+  if (CompareSchemeComponent(url, scheme, kFileSystemScheme)) {
     return true;
+  }
 
   // ExtractScheme guarantees that the colon immediately follows what it
   // considers to be the scheme. CountConsecutiveSlashes will handle the
   // case where the begin offset is the end of the input.
-  int num_slashes = CountConsecutiveSlashes(url, colon_offset + 1, url_len);
+  int num_slashes = CountConsecutiveSlashesOrBackslashes(url, colon_offset + 1);
 
   if (num_slashes == 0 || num_slashes == 1) {
     // No slashes means it's a relative path like "http:foo.html". One slash
     // is an absolute path. "http:/home/foo.html"
     *is_relative = true;
-    *relative_component = MakeRange(colon_offset + 1, url_len);
+    *relative_component =
+        MakeRange(begin + colon_offset + 1, begin + url.length());
     return true;
   }
 
@@ -217,7 +231,7 @@
 //
 // For stardard URLs the input should be canonical, but when resolving relative
 // URLs on a non-standard base (like "data:") the input can be anything.
-void CopyToLastSlash(const char* spec,
+void CopyToLastSlash(std::string_view spec,
                      int begin,
                      int end,
                      CanonOutput* output) {
@@ -241,7 +255,7 @@
 // when resolving relative URLs and a given component is unchanged. Since the
 // source should already be canonical, we don't have to do anything special,
 // and the input is ASCII.
-void CopyOneComponent(const char* source,
+void CopyOneComponent(std::string_view source,
                       const Component& source_component,
                       CanonOutput* output,
                       Component* output_component) {
@@ -269,27 +283,25 @@
 // there is no drive letter, the slash at the beginning of the path, or
 // the end of the base. This can be used as the starting offset for further
 // path processing.
-template<typename CHAR>
-int CopyBaseDriveSpecIfNecessary(const char* base_url,
+template <typename CHAR>
+int CopyBaseDriveSpecIfNecessary(std::string_view base_url,
                                  int base_path_begin,
                                  int base_path_end,
-                                 const CHAR* relative_url,
-                                 int path_start,
-                                 int relative_url_len,
+                                 std::basic_string_view<CHAR> relative_url,
                                  CanonOutput* output) {
   if (base_path_begin >= base_path_end)
     return base_path_begin;  // No path.
 
   // If the relative begins with a drive spec, don't do anything. The existing
   // drive spec in the base will be replaced.
-  if (DoesBeginWindowsDriveSpec(relative_url, path_start, relative_url_len)) {
+  if (DoesBeginWindowsDriveSpec(relative_url.data(), 0,
+                                relative_url.length())) {
     return base_path_begin;  // Relative URL path is "C:/foo"
   }
 
   // The path should begin with a slash (as all canonical paths do). We check
   // if it is followed by a drive letter and copy it.
-  if (DoesBeginSlashWindowsDriveSpec(base_url,
-                                     base_path_begin,
+  if (DoesBeginSlashWindowsDriveSpec(base_url.data(), base_path_begin,
                                      base_path_end)) {
     // Copy the two-character drive spec to the output. It will now look like
     // "file:///C:" so the rest of it can be treated like a standard path.
@@ -304,15 +316,15 @@
 
 #endif  // WIN32
 
-// A subroutine of DoResolveRelativeURL, this resolves the URL knowning that
+// A subroutine of DoResolveRelativeUrl, this resolves the URL knowing that
 // the input is a relative path or less (query or ref).
-template<typename CHAR>
-bool DoResolveRelativePath(const char* base_url,
+template <typename CHAR>
+bool DoResolveRelativePath(std::string_view base_url,
                            const Parsed& base_parsed,
                            bool base_is_file,
-                           const CHAR* relative_url,
-                           const Component& relative_component,
+                           std::basic_string_view<CHAR> relative_url,
                            CharsetConverter* query_converter,
+                           CanonMode canon_mode,
                            CanonOutput* output,
                            Parsed* out_parsed) {
   bool success = true;
@@ -320,14 +332,43 @@
   // We know the authority section didn't change, copy it to the output. We
   // also know we have a path so can copy up to there.
   Component path, query, ref;
-  ParsePathInternal(relative_url, relative_component, &path, &query, &ref);
+  ParsePathInternal(relative_url.data(), Component(0, relative_url.size()),
+                    &path, &query, &ref);
 
   // Canonical URLs always have a path, so we can use that offset. Reserve
   // enough room for the base URL, the new path, and some extra bytes for
   // possible escaped characters.
   output->ReserveSizeIfNeeded(base_parsed.path.begin +
                               std::max({path.end(), query.end(), ref.end()}));
-  output->Append(base_url, base_parsed.path.begin);
+
+  // Append a base URL up to the beginning of base URL's path.
+  if (base_parsed.path.is_empty()) {
+    // A non-special URL may have an empty path (e.g. "git://host"). In these
+    // cases, attempting to use `base_parsed.path` is invalid.
+    output->Append(base_url.substr(0, base_parsed.Length()));
+  } else if (!base_parsed.host.is_valid() &&
+             // Exclude a file URL and an URL with an inner-path because we are
+             // interested in only non-special URLs here.
+             //
+             // If we don't exclude a file URL here, for example, `new
+             // URL("test", "file:///tmp").href` will result in
+             // "file:/tmp/mock/test" instead of "file:///tmp/mock/test".
+             !base_is_file && !base_parsed.inner_parsed()) {
+    // The URL is a path-only non-special URL. e.g. "git:/path".
+    //
+    // In this case, we can't use `base_parsed.path.begin` because it may append
+    // "/." wrongly if the URL is, for example, "git:/.//a", where
+    // `base_parsed.path` represents "//a", instead of "/.//a". We want to
+    // append "git:", instead of "git:/.".
+    //
+    // Fortunately, we can use `base_parsed.scheme.end()` here because we don't
+    // need to append a user, a password, a host, nor a port when a host is
+    // invalid.
+    output->Append(base_url.substr(0, base_parsed.scheme.end()));
+    output->Append(":");
+  } else {
+    output->Append(base_url.substr(0, base_parsed.path.begin));
+  }
 
   if (path.is_nonempty()) {
     // The path is replaced or modified.
@@ -342,39 +383,63 @@
     if (base_is_file) {
       base_path_begin = CopyBaseDriveSpecIfNecessary(
           base_url, base_parsed.path.begin, base_parsed.path.end(),
-          relative_url, relative_component.begin, relative_component.end(),
-          output);
+          relative_url, output);
       // Now the output looks like either "file://" or "file:///C:"
       // and we can start appending the rest of the path. |base_path_begin|
       // points to the character in the base that comes next.
     }
 #endif  // WIN32
 
-    if (IsURLSlash(relative_url[path.begin])) {
+    if (IsSlashOrBackslash(relative_url[path.begin])) {
       // Easy case: the path is an absolute path on the server, so we can
       // just replace everything from the path on with the new versions.
       // Since the input should be canonical hierarchical URL, we should
       // always have a path.
-      success &= CanonicalizePath(relative_url, path,
-                                  output, &out_parsed->path);
+      success &= CanonicalizePath(path.AsViewOn(relative_url), output,
+                                  &out_parsed->path);
     } else {
       // Relative path, replace the query, and reference. We take the
       // original path with the file part stripped, and append the new path.
       // The canonicalizer will take care of resolving ".." and "."
       size_t path_begin = output->length();
+
+      if (base_parsed.path.is_empty() && !path.is_empty()) {
+        // Ensure a leading "/" is present before appending a non-empty relative
+        // path when the base URL's path is empty, as can occur with non-special
+        // URLs. This prevents incorrect path concatenation, such as resolving
+        // "path" based on "git://host" resulting in "git://hostpath" instead of
+        // the intended "git://host/path".
+        output->push_back('/');
+      }
+
       CopyToLastSlash(base_url, base_path_begin, base_parsed.path.end(),
                       output);
-      success &= CanonicalizePartialPathInternal(relative_url, path, path_begin,
-                                                 output);
+      success &= CanonicalizePartialPathInternal(
+          path.AsViewOn(relative_url), path_begin, canon_mode, output);
       out_parsed->path = MakeRange(path_begin, output->length());
 
       // Copy the rest of the stuff after the path from the relative path.
     }
 
+    // To avoid path being treated as the host, prepend "/." to the path".
+    //
+    // Example:
+    //
+    // > const url = new URL("/.//path", "git:/");
+    // > url.href
+    // => The result should be "git:/.//path", instead of "git://path".
+    if (!base_parsed.host.is_valid() && out_parsed->path.is_valid() &&
+        out_parsed->path.AsViewOn(output->view()).starts_with("//")) {
+      size_t prior_output_length = output->length();
+      output->Insert(out_parsed->path.begin, "/.");
+      // Adjust path.
+      out_parsed->path.begin += output->length() - prior_output_length;
+      true_path_begin = out_parsed->path.begin;
+    }
     // Finish with the query and reference part (these can't fail).
-    CanonicalizeQuery(relative_url, query, query_converter,
+    CanonicalizeQuery(query.MaybeAsViewOn(relative_url), query_converter,
                       output, &out_parsed->query);
-    CanonicalizeRef(relative_url, ref, output, &out_parsed->ref);
+    CanonicalizeRef(ref.MaybeAsViewOn(relative_url), output, &out_parsed->ref);
 
     // Fix the path beginning to add back the "C:" we may have written above.
     out_parsed->path = MakeRange(true_path_begin, out_parsed->path.end());
@@ -387,9 +452,9 @@
   if (query.is_valid()) {
     // Just the query specified, replace the query and reference (ignore
     // failures for refs)
-    CanonicalizeQuery(relative_url, query, query_converter,
-                      output, &out_parsed->query);
-    CanonicalizeRef(relative_url, ref, output, &out_parsed->ref);
+    CanonicalizeQuery(query.AsViewOn(relative_url), query_converter, output,
+                      &out_parsed->query);
+    CanonicalizeRef(ref.MaybeAsViewOn(relative_url), output, &out_parsed->ref);
     return success;
   }
 
@@ -402,7 +467,7 @@
 
   if (ref.is_valid()) {
     // Just the reference specified: replace it (ignoring failures).
-    CanonicalizeRef(relative_url, ref, output, &out_parsed->ref);
+    CanonicalizeRef(ref.AsViewOn(relative_url), output, &out_parsed->ref);
     return success;
   }
 
@@ -415,72 +480,73 @@
 // Resolves a relative URL that contains a host. Typically, these will
 // be of the form "//www.google.com/foo/bar?baz#ref" and the only thing which
 // should be kept from the original URL is the scheme.
-template<typename CHAR>
-bool DoResolveRelativeHost(const char* base_url,
+template <typename CHAR>
+bool DoResolveRelativeHost(std::string_view base_url,
                            const Parsed& base_parsed,
-                           const CHAR* relative_url,
-                           const Component& relative_component,
+                           std::basic_string_view<CHAR> relative_url,
                            CharsetConverter* query_converter,
                            CanonOutput* output,
                            Parsed* out_parsed) {
+  SchemeType scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
+  const bool is_standard_scheme = GetStandardSchemeType(
+      base_parsed.scheme.MaybeAsViewOn(base_url), &scheme_type);
+
   // Parse the relative URL, just like we would for anything following a
   // scheme.
   Parsed relative_parsed;  // Everything but the scheme is valid.
-  ParseAfterScheme(relative_url, relative_component.end(),
-                   relative_component.begin, &relative_parsed);
+
+  if (!is_standard_scheme) {
+    ParseAfterNonSpecialScheme(relative_url, 0, &relative_parsed);
+  } else {
+    ParseAfterSpecialScheme(relative_url, 0, &relative_parsed);
+  }
 
   // Now we can just use the replacement function to replace all the necessary
   // parts of the old URL with the new one.
   Replacements<CHAR> replacements;
-  replacements.SetUsername(relative_url, relative_parsed.username);
-  replacements.SetPassword(relative_url, relative_parsed.password);
-  replacements.SetHost(relative_url, relative_parsed.host);
-  replacements.SetPort(relative_url, relative_parsed.port);
-  replacements.SetPath(relative_url, relative_parsed.path);
-  replacements.SetQuery(relative_url, relative_parsed.query);
-  replacements.SetRef(relative_url, relative_parsed.ref);
+  const CHAR* relative_url_ptr = relative_url.data();
+  replacements.SetUsername(relative_url_ptr, relative_parsed.username);
+  replacements.SetPassword(relative_url_ptr, relative_parsed.password);
+  replacements.SetHost(relative_url_ptr, relative_parsed.host);
+  replacements.SetPort(relative_url_ptr, relative_parsed.port);
+  replacements.SetPath(relative_url_ptr, relative_parsed.path);
+  replacements.SetQuery(relative_url_ptr, relative_parsed.query);
+  replacements.SetRef(relative_url_ptr, relative_parsed.ref);
 
   // Length() does not include the old scheme, so make sure to add it from the
   // base URL.
   output->ReserveSizeIfNeeded(
       replacements.components().Length() +
       base_parsed.CountCharactersBefore(Parsed::USERNAME, false));
-  SchemeType scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
-  if (!GetStandardSchemeType(base_url, base_parsed.scheme, &scheme_type)) {
-    // A path with an authority section gets canonicalized under standard URL
-    // rules, even though the base was not known to be standard.
-    scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
+  if (!is_standard_scheme) {
+    return ReplaceNonSpecialUrl(base_url, base_parsed, replacements,
+                                query_converter, *output, *out_parsed);
   }
-  return ReplaceStandardURL(base_url, base_parsed, replacements, scheme_type,
+
+  return ReplaceStandardUrl(base_url, base_parsed, replacements, scheme_type,
                             query_converter, output, out_parsed);
 }
 
 // Resolves a relative URL that happens to be an absolute file path. Examples
 // include: "//hostname/path", "/c:/foo", and "//hostname/c:/foo".
-template<typename CHAR>
-bool DoResolveAbsoluteFile(const CHAR* relative_url,
-                           const Component& relative_component,
+template <typename CharT>
+bool DoResolveAbsoluteFile(std::basic_string_view<CharT> relative_url,
                            CharsetConverter* query_converter,
                            CanonOutput* output,
                            Parsed* out_parsed) {
-  // Parse the file URL. The file URl parsing function uses the same logic
+  // Parse the file URL. The file URL parsing function uses the same logic
   // as we do for determining if the file is absolute, in which case it will
   // not bother to look for a scheme.
-  Parsed relative_parsed;
-  ParseFileURL(&relative_url[relative_component.begin], relative_component.len,
-               &relative_parsed);
-
-  return CanonicalizeFileURL(&relative_url[relative_component.begin],
-                             relative_component.len, relative_parsed,
+  return CanonicalizeFileUrl(relative_url, ParseFileUrl(relative_url),
                              query_converter, output, out_parsed);
 }
 
 // TODO(brettw) treat two slashes as root like Mozilla for FTP?
-template<typename CHAR>
-bool DoResolveRelativeURL(const char* base_url,
+template <typename CHAR>
+bool DoResolveRelativeUrl(std::string_view base_url,
                           const Parsed& base_parsed,
                           bool base_is_file,
-                          const CHAR* relative_url,
+                          std::basic_string_view<CHAR> relative_url,
                           const Component& relative_component,
                           CharsetConverter* query_converter,
                           CanonOutput* output,
@@ -493,17 +559,13 @@
   if (potentially_dangling_markup)
     out_parsed->potentially_dangling_markup = true;
 
-  // Sanity check: the input should have a host or we'll break badly below.
-  // We can only resolve relative URLs with base URLs that have hosts and
-  // paths (even the default path of "/" is OK).
-  //
-  // We allow hosts with no length so we can handle file URLs, for example.
-  if (base_parsed.path.is_empty()) {
-    // On error, return the input (resolving a relative URL on a non-relative
-    // base = the base).
+  if (base_parsed.scheme.is_empty()) {
+    // On error, return the input (resolving a relative URL on a
+    // non-relative base = the base).
     int base_len = base_parsed.Length();
-    for (int i = 0; i < base_len; i++)
+    for (int i = 0; i < base_len; i++) {
       output->push_back(base_url[i]);
+    }
     return false;
   }
 
@@ -512,12 +574,13 @@
     int base_len = base_parsed.Length();
     base_len -= base_parsed.ref.len + 1;
     out_parsed->ref.reset();
-    output->Append(base_url, base_len);
+    output->Append(base_url.data(), base_len);
     return true;
   }
 
-  int num_slashes = CountConsecutiveSlashes(
-      relative_url, relative_component.begin, relative_component.end());
+  auto relative_url_view = relative_component.AsViewOn(relative_url);
+  size_t num_slashes =
+      CountConsecutiveSlashesOrBackslashes(relative_url_view, 0);
 
 #ifdef WIN32
   // On Windows, two slashes for a file path (regardless of which direction
@@ -534,13 +597,13 @@
   // This assumes the absolute path resolver handles absolute URLs like this
   // properly. DoCanonicalize does this.
   int after_slashes = relative_component.begin + num_slashes;
-  if (DoesBeginUNCPath(relative_url, relative_component.begin,
+  if (DoesBeginUNCPath(relative_url.data(), relative_component.begin,
                        relative_component.end(), !base_is_file) ||
       ((num_slashes == 0 || base_is_file) &&
-       DoesBeginWindowsDriveSpec(
-           relative_url, after_slashes, relative_component.end()))) {
-    return DoResolveAbsoluteFile(relative_url, relative_component,
-                                 query_converter, output, out_parsed);
+       DoesBeginWindowsDriveSpec(relative_url.data(), after_slashes,
+                                 relative_component.end()))) {
+    return DoResolveAbsoluteFile(relative_url_view, query_converter, output,
+                                 out_parsed);
   }
 #else
   // Other platforms need explicit handling for file: URLs with multiple
@@ -550,72 +613,70 @@
   // URLs provided by DoResolveAbsoluteFile(), as opposed to the generic host
   // detection logic, for consistency with parsing file URLs from scratch.
   if (base_is_file && num_slashes >= 2) {
-    return DoResolveAbsoluteFile(relative_url, relative_component,
-                                 query_converter, output, out_parsed);
+    return DoResolveAbsoluteFile(relative_url_view, query_converter, output,
+                                 out_parsed);
   }
 #endif
 
   // Any other double-slashes mean that this is relative to the scheme.
   if (num_slashes >= 2) {
-    return DoResolveRelativeHost(base_url, base_parsed,
-                                 relative_url, relative_component,
+    return DoResolveRelativeHost(base_url, base_parsed, relative_url_view,
                                  query_converter, output, out_parsed);
   }
 
   // When we get here, we know that the relative URL is on the same host.
-  return DoResolveRelativePath(base_url, base_parsed, base_is_file,
-                               relative_url, relative_component,
-                               query_converter, output, out_parsed);
+  return DoResolveRelativePath(
+      base_url, base_parsed, base_is_file, relative_url_view, query_converter,
+      // TODO(crbug.com/40063064): Support Non-special URLs
+      CanonMode::kSpecialURL, output, out_parsed);
 }
 
 }  // namespace
 
-bool IsRelativeURL(const char* base,
+bool IsRelativeUrl(std::string_view base,
                    const Parsed& base_parsed,
-                   const char* fragment,
-                   int fragment_len,
+                   std::string_view fragment,
                    bool is_base_hierarchical,
                    bool* is_relative,
                    Component* relative_component) {
-  return DoIsRelativeURL<char>(
-      base, base_parsed, fragment, fragment_len, is_base_hierarchical,
-      is_relative, relative_component);
+  return DoIsRelativeUrl<char>(base, base_parsed, fragment,
+                               is_base_hierarchical, is_relative,
+                               relative_component);
 }
 
-bool IsRelativeURL(const char* base,
+bool IsRelativeUrl(std::string_view base,
                    const Parsed& base_parsed,
-                   const char16_t* fragment,
-                   int fragment_len,
+                   std::u16string_view fragment,
                    bool is_base_hierarchical,
                    bool* is_relative,
                    Component* relative_component) {
-  return DoIsRelativeURL<char16_t>(base, base_parsed, fragment, fragment_len,
+  return DoIsRelativeUrl<char16_t>(base, base_parsed, fragment,
                                    is_base_hierarchical, is_relative,
                                    relative_component);
 }
 
-bool ResolveRelativeURL(const char* base_url,
+bool ResolveRelativeUrl(std::string_view base_url,
                         const Parsed& base_parsed,
                         bool base_is_file,
-                        const char* relative_url,
+                        std::string_view relative_url,
                         const Component& relative_component,
                         CharsetConverter* query_converter,
                         CanonOutput* output,
                         Parsed* out_parsed) {
-  return DoResolveRelativeURL<char>(
-      base_url, base_parsed, base_is_file, relative_url,
-      relative_component, query_converter, output, out_parsed);
+  return DoResolveRelativeUrl<char>(base_url, base_parsed, base_is_file,
+                                    relative_url, relative_component,
+                                    query_converter, output, out_parsed);
 }
 
-bool ResolveRelativeURL(const char* base_url,
+bool ResolveRelativeUrl(std::string_view base_url,
                         const Parsed& base_parsed,
                         bool base_is_file,
-                        const char16_t* relative_url,
+                        std::u16string_view relative_url,
                         const Component& relative_component,
                         CharsetConverter* query_converter,
                         CanonOutput* output,
                         Parsed* out_parsed) {
-  return DoResolveRelativeURL<char16_t>(base_url, base_parsed, base_is_file,
+  return DoResolveRelativeUrl<char16_t>(base_url, base_parsed, base_is_file,
                                         relative_url, relative_component,
                                         query_converter, output, out_parsed);
 }
diff --git a/url/url_canon_stdurl.cc b/url/url_canon_stdurl.cc
index 8096b56..f949df0 100644
--- a/url/url_canon_stdurl.cc
+++ b/url/url_canon_stdurl.cc
@@ -5,6 +5,7 @@
 // Functions to canonicalize "standard" URLs, which are ones that have an
 // authority section including a host name.
 
+#include "base/compiler_specific.h"
 #include "url/url_canon.h"
 #include "url/url_canon_internal.h"
 #include "url/url_constants.h"
@@ -13,16 +14,19 @@
 
 namespace {
 
-template <typename CHAR, typename UCHAR>
-bool DoCanonicalizeStandardURL(const URLComponentSource<CHAR>& source,
+template <typename CHAR>
+bool DoCanonicalizeStandardUrl(const URLComponentSource<CHAR>& source,
                                const Parsed& parsed,
                                SchemeType scheme_type,
                                CharsetConverter* query_converter,
                                CanonOutput* output,
                                Parsed* new_parsed) {
+  GURL_DCHECK(!parsed.has_opaque_path);
+
   // Scheme: this will append the colon.
-  bool success = CanonicalizeScheme(source.scheme, parsed.scheme,
-                                    output, &new_parsed->scheme);
+  bool success =
+      CanonicalizeScheme(parsed.scheme.maybe_as_string_view_on(source.scheme),
+                         output, &new_parsed->scheme);
 
   bool scheme_supports_user_info =
       (scheme_type == SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION);
@@ -47,15 +51,18 @@
     // User info: the canonicalizer will handle the : and @.
     if (scheme_supports_user_info) {
       success &= CanonicalizeUserInfo(
-          source.username, parsed.username, source.password, parsed.password,
-          output, &new_parsed->username, &new_parsed->password);
+          parsed.username.maybe_as_string_view_on(source.username),
+          parsed.password.maybe_as_string_view_on(source.password), output,
+          &new_parsed->username, &new_parsed->password);
     } else {
       new_parsed->username.reset();
       new_parsed->password.reset();
     }
 
-    success &= CanonicalizeHost(source.host, parsed.host,
-                                output, &new_parsed->host);
+    success &= CanonicalizeHost(
+        std::basic_string_view<CHAR>(
+            source.host, parsed.host.is_valid() ? parsed.host.end() : 0),
+        parsed.host, output, &new_parsed->host);
 
     // Host must not be empty for standard URLs.
     if (parsed.host.is_empty())
@@ -63,10 +70,12 @@
 
     // Port: the port canonicalizer will handle the colon.
     if (scheme_supports_ports) {
-      int default_port = DefaultPortForScheme(
-          &output->data()[new_parsed->scheme.begin], new_parsed->scheme.len);
-      success &= CanonicalizePort(source.port, parsed.port, default_port,
-                                  output, &new_parsed->port);
+      int default_port = DefaultPortForScheme(std::string_view(
+          &UNSAFE_TODO(output->data()[new_parsed->scheme.begin]),
+          new_parsed->scheme.len));
+      success &=
+          CanonicalizePort(parsed.port.maybe_as_string_view_on(source.port),
+                           default_port, output, &new_parsed->port);
     } else {
       new_parsed->port.reset();
     }
@@ -82,7 +91,7 @@
 
   // Path
   if (parsed.path.is_valid()) {
-    success &= CanonicalizePath(source.path, parsed.path,
+    success &= CanonicalizePath(parsed.path.as_string_view_on(source.path),
                                 output, &new_parsed->path);
   } else if (have_authority ||
              parsed.query.is_valid() || parsed.ref.is_valid()) {
@@ -97,11 +106,12 @@
   }
 
   // Query
-  CanonicalizeQuery(source.query, parsed.query, query_converter,
-                    output, &new_parsed->query);
+  CanonicalizeQuery(parsed.query.maybe_as_string_view_on(source.query),
+                    query_converter, output, &new_parsed->query);
 
   // Ref: ignore failure for this, since the page can probably still be loaded.
-  CanonicalizeRef(source.ref, parsed.ref, output, &new_parsed->ref);
+  CanonicalizeRef(parsed.ref.maybe_as_string_view_on(source.ref), output,
+                  &new_parsed->ref);
 
   // Carry over the flag for potentially dangling markup:
   if (parsed.potentially_dangling_markup)
@@ -117,53 +127,54 @@
 //
 // Please keep blink::DefaultPortForProtocol and url::DefaultPortForProtocol in
 // sync.
-int DefaultPortForScheme(const char* scheme, int scheme_len) {
-  int default_port = PORT_UNSPECIFIED;
-  switch (scheme_len) {
+int DefaultPortForScheme(std::string_view scheme) {
+  switch (scheme.length()) {
     case 4:
-      if (!strncmp(scheme, kHttpScheme, scheme_len))
-        default_port = 80;
+      if (scheme == kHttpScheme) {
+        return 80;
+      }
       break;
     case 5:
-      if (!strncmp(scheme, kHttpsScheme, scheme_len))
-        default_port = 443;
+      if (scheme == kHttpsScheme) {
+        return 443;
+      }
       break;
     case 3:
-      if (!strncmp(scheme, kFtpScheme, scheme_len))
-        default_port = 21;
-      else if (!strncmp(scheme, kWssScheme, scheme_len))
-        default_port = 443;
+      if (scheme == kFtpScheme) {
+        return 21;
+      } else if (scheme == kWssScheme) {
+        return 443;
+      }
       break;
     case 2:
-      if (!strncmp(scheme, kWsScheme, scheme_len))
-        default_port = 80;
+      if (scheme == kWsScheme) {
+        return 80;
+      }
       break;
   }
-  return default_port;
+  return PORT_UNSPECIFIED;
 }
 
-bool CanonicalizeStandardURL(const char* spec,
-                             int spec_len,
+bool CanonicalizeStandardUrl(std::string_view spec,
                              const Parsed& parsed,
                              SchemeType scheme_type,
                              CharsetConverter* query_converter,
                              CanonOutput* output,
                              Parsed* new_parsed) {
-  return DoCanonicalizeStandardURL<char, unsigned char>(
-      URLComponentSource<char>(spec), parsed, scheme_type, query_converter,
-      output, new_parsed);
+  return DoCanonicalizeStandardUrl(URLComponentSource(spec.data()), parsed,
+                                   scheme_type, query_converter, output,
+                                   new_parsed);
 }
 
-bool CanonicalizeStandardURL(const char16_t* spec,
-                             int spec_len,
+bool CanonicalizeStandardUrl(std::u16string_view spec,
                              const Parsed& parsed,
                              SchemeType scheme_type,
                              CharsetConverter* query_converter,
                              CanonOutput* output,
                              Parsed* new_parsed) {
-  return DoCanonicalizeStandardURL<char16_t, char16_t>(
-      URLComponentSource<char16_t>(spec), parsed, scheme_type, query_converter,
-      output, new_parsed);
+  return DoCanonicalizeStandardUrl(URLComponentSource(spec.data()), parsed,
+                                   scheme_type, query_converter, output,
+                                   new_parsed);
 }
 
 // It might be nice in the future to optimize this so unchanged components don't
@@ -175,23 +186,23 @@
 //
 // You would also need to update DoReplaceComponents in url_util.cc which
 // relies on this re-checking everything (see the comment there for why).
-bool ReplaceStandardURL(const char* base,
+bool ReplaceStandardUrl(std::string_view base,
                         const Parsed& base_parsed,
                         const Replacements<char>& replacements,
                         SchemeType scheme_type,
                         CharsetConverter* query_converter,
                         CanonOutput* output,
                         Parsed* new_parsed) {
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupOverrideComponents(base, replacements, &source, &parsed);
-  return DoCanonicalizeStandardURL<char, unsigned char>(
-      source, parsed, scheme_type, query_converter, output, new_parsed);
+  SetupOverrideComponents(base.data(), replacements, &source, &parsed);
+  return DoCanonicalizeStandardUrl(source, parsed, scheme_type, query_converter,
+                                   output, new_parsed);
 }
 
 // For 16-bit replacements, we turn all the replacements into UTF-8 so the
 // regular code path can be used.
-bool ReplaceStandardURL(const char* base,
+bool ReplaceStandardUrl(std::string_view base,
                         const Parsed& base_parsed,
                         const Replacements<char16_t>& replacements,
                         SchemeType scheme_type,
@@ -199,11 +210,12 @@
                         CanonOutput* output,
                         Parsed* new_parsed) {
   RawCanonOutput<1024> utf8;
-  URLComponentSource<char> source(base);
+  URLComponentSource<char> source(base.data());
   Parsed parsed(base_parsed);
-  SetupUTF16OverrideComponents(base, replacements, &utf8, &source, &parsed);
-  return DoCanonicalizeStandardURL<char, unsigned char>(
-      source, parsed, scheme_type, query_converter, output, new_parsed);
+  SetupUTF16OverrideComponents(base.data(), replacements, &utf8, &source,
+                               &parsed);
+  return DoCanonicalizeStandardUrl(source, parsed, scheme_type, query_converter,
+                                   output, new_parsed);
 }
 
 }  // namespace url
diff --git a/url/url_canon_unittest.cc b/url/url_canon_unittest.cc
index e0ac0ee..af4de85 100644
--- a/url/url_canon_unittest.cc
+++ b/url/url_canon_unittest.cc
@@ -2,13 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "url/url_canon.h"
 
 #include <errno.h>
 #include <stddef.h>
+
+#include <array>
+#include <optional>
 #include <string_view>
 
+#include "base/containers/span.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_view_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/gtest_util.h"
 #include "base/test/scoped_feature_list.h"
@@ -23,6 +33,13 @@
 
 namespace {
 
+// Struct to hold test cases for StringToUint64WithBase function.
+struct StringToUint64TestCase {
+  std::string_view input;
+  uint8_t base;
+  uint64_t expected;
+};
+
 struct ComponentCase {
   const char* input;
   const char* expected;
@@ -93,9 +110,85 @@
   }
 }
 
+bool CanonicalizeSpecialPath(std::optional<std::string_view> path,
+                             CanonOutput* output,
+                             Component* out_path) {
+  return CanonicalizePath(path, CanonMode::kSpecialURL, output, out_path);
+}
+
+bool CanonicalizeSpecialPath(std::optional<std::u16string_view> path,
+                             CanonOutput* output,
+                             Component* out_path) {
+  return CanonicalizePath(path, CanonMode::kSpecialURL, output, out_path);
+}
+
+bool CanonicalizeNonSpecialPath(std::optional<std::string_view> path,
+                                CanonOutput* output,
+                                Component* out_path) {
+  return CanonicalizePath(path, CanonMode::kNonSpecialURL, output, out_path);
+}
+
+bool CanonicalizeNonSpecialPath(std::optional<std::u16string_view> path,
+                                CanonOutput* output,
+                                Component* out_path) {
+  return CanonicalizePath(path, CanonMode::kNonSpecialURL, output, out_path);
+}
+
 }  // namespace
 
-TEST(URLCanonTest, DoAppendUTF8) {
+class URLCanonTest : public ::testing::Test {
+ public:
+  URLCanonTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        url::kDisallowSpaceCharacterInURLHostParsing);
+  }
+
+ protected:
+  struct ResolveRelativeURLCase {
+    const std::string_view base;
+    const std::string_view rel;
+    const bool is_base_hier;
+    const bool expected_base_is_valid;
+    const bool expected_is_relative;
+    const bool expected_succeed_resolve;
+    const std::string_view expected_resolved_url;
+  };
+
+  void TestNonSpecialResolveRelativeURL(
+      const ResolveRelativeURLCase& relative_case) {
+    // The following test is similar to URLCanonTest::ResolveRelativeURL, but
+    // simplified.
+    Parsed parsed = ParseNonSpecialUrl(relative_case.base);
+
+    // First see if it is relative.
+    bool is_relative;
+    Component relative_component;
+    bool succeed_is_rel = IsRelativeUrl(
+        relative_case.base, parsed, relative_case.rel,
+        relative_case.is_base_hier, &is_relative, &relative_component);
+
+    EXPECT_EQ(is_relative, relative_case.expected_is_relative);
+    if (succeed_is_rel && is_relative) {
+      std::string resolved_url;
+      StdStringCanonOutput output(&resolved_url);
+      Parsed resolved_parsed;
+
+      bool succeed_resolve = ResolveRelativeUrl(
+          relative_case.base, parsed, relative_case.is_base_hier,
+          relative_case.rel, relative_component, nullptr, &output,
+          &resolved_parsed);
+      output.Complete();
+
+      EXPECT_EQ(succeed_resolve, relative_case.expected_succeed_resolve);
+      EXPECT_EQ(resolved_url, relative_case.expected_resolved_url);
+    }
+  }
+
+ private:
+  gurl_base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(URLCanonTest, DoAppendUTF8) {
   struct UTF8Case {
     unsigned input;
     const char* output;
@@ -117,7 +210,7 @@
   }
 }
 
-TEST(URLCanonTest, DoAppendUTF8Invalid) {
+TEST_F(URLCanonTest, DoAppendUTF8Invalid) {
   std::string out_str;
   StdStringCanonOutput output(&out_str);
   // Invalid code point (too large).
@@ -127,7 +220,7 @@
   });
 }
 
-TEST(URLCanonTest, UTF) {
+TEST_F(URLCanonTest, UTF) {
   // Low-level test that we handle reading, canonicalization, and writing
   // UTF-8/UTF-16 strings properly.
   struct UTFCase {
@@ -207,7 +300,7 @@
   }
 }
 
-TEST(URLCanonTest, Scheme) {
+TEST_F(URLCanonTest, Scheme) {
   // Here, we're mostly testing that unusual characters are handled properly.
   // The canonicalizer doesn't do any parsing or whitespace detection. It will
   // also do its best on error, and will escape funny sequences (these won't be
@@ -237,8 +330,8 @@
 
     out_str.clear();
     StdStringCanonOutput output1(&out_str);
-    bool success =
-        CanonicalizeScheme(scheme_case.input, in_comp, &output1, &out_comp);
+    bool success = CanonicalizeScheme(
+        in_comp.as_string_view_on(scheme_case.input), &output1, &out_comp);
     output1.Complete();
 
     EXPECT_EQ(scheme_case.expected_success, success);
@@ -252,8 +345,8 @@
 
     std::u16string wide_input(gurl_base::UTF8ToUTF16(scheme_case.input));
     in_comp.len = static_cast<int>(wide_input.length());
-    success = CanonicalizeScheme(wide_input.c_str(), in_comp, &output2,
-                                 &out_comp);
+    success =
+        CanonicalizeScheme(in_comp.AsViewOn(wide_input), &output2, &out_comp);
     output2.Complete();
 
     EXPECT_EQ(scheme_case.expected_success, success);
@@ -268,7 +361,8 @@
   out_str.clear();
   StdStringCanonOutput output(&out_str);
 
-  EXPECT_FALSE(CanonicalizeScheme("", Component(0, -1), &output, &out_comp));
+  EXPECT_FALSE(CanonicalizeScheme(std::optional<std::string_view>(std::nullopt),
+                                  &output, &out_comp));
   output.Complete();
 
   EXPECT_EQ(":", out_str);
@@ -276,49 +370,32 @@
   EXPECT_EQ(0, out_comp.len);
 }
 
-// IDNA mode to use in CanonHost tests.
-enum class IDNAMode { kTransitional, kNonTransitional };
-
-class URLCanonHostTest
-    : public ::testing::Test,
-      public ::testing::WithParamInterface<IDNAMode> {
+class URLCanonHostTest : public ::testing::Test {
  public:
   URLCanonHostTest() {
-    if (GetParam() == IDNAMode::kNonTransitional) {
-      scoped_feature_list_.InitAndEnableFeature(kUseIDNA2008NonTransitional);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(kUseIDNA2008NonTransitional);
-    }
+    scoped_feature_list_.InitAndEnableFeature(
+        kDisallowSpaceCharacterInURLHostParsing);
   }
 
  private:
   gurl_base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         URLCanonHostTest,
-                         ::testing::Values(IDNAMode::kTransitional,
-                                           IDNAMode::kNonTransitional));
-
-TEST_P(URLCanonHostTest, Host) {
-  bool use_idna_non_transitional = IsUsingIDNA2008NonTransitional();
-
+TEST_F(URLCanonHostTest, Host) {
   // clang-format off
   IPAddressCase host_cases[] = {
       // Basic canonicalization, uppercase should be converted to lowercase.
       {"GoOgLe.CoM", L"GoOgLe.CoM", "google.com", Component(0, 10),
        CanonHostInfo::NEUTRAL, -1, ""},
-      // TODO(https://crbug.com/1416013): Update the test after SPACE is
-      // correctly handled.
       {"Goo%20 goo.com", L"Goo%20 goo.com", "goo%20%20goo.com",
-       Component(0, 16), CanonHostInfo::NEUTRAL, -1, ""},
-      // TODO(https://crbug.com/1416013): Update the test after ASTERISK is
+       Component(0, 16), CanonHostInfo::BROKEN, -1, ""},
+      // TODO(crbug.com/40256677): Update the test after ASTERISK is
       // correctly handled.
       {"Goo%2a*goo.com", L"Goo%2a*goo.com", "goo%2A%2Agoo.com",
        Component(0, 16), CanonHostInfo::NEUTRAL, -1, ""},
       // Exciting different types of spaces!
       {nullptr, L"GOO\x00a0\x3000goo.com", "goo%20%20goo.com", Component(0, 16),
-       CanonHostInfo::NEUTRAL, -1, ""},
+       CanonHostInfo::BROKEN, -1, ""},
       // Other types of space (no-break, zero-width, zero-width-no-break) are
       // name-prepped away to nothing.
       {nullptr, L"GOO\x200b\x2060\xfeffgoo.com", "googoo.com", Component(0, 10),
@@ -397,14 +474,14 @@
        "ball.de",
        L"fu\x00df"
        L"ball.de",
-       use_idna_non_transitional ? "xn--fuball-cta.de" : "fussball.de",
-       use_idna_non_transitional ? Component(0, 17) : Component(0, 11),
+       "xn--fuball-cta.de",
+       Component(0, 17),
        CanonHostInfo::NEUTRAL, -1, ""},
 
       // Final-sigma (U+03C3) was mapped to regular sigma (U+03C2).
       // Previously, it'd be "xn--wxaikc9b".
       {"\xcf\x83\xcf\x8c\xce\xbb\xce\xbf\xcf\x82", L"\x3c3\x3cc\x3bb\x3bf\x3c2",
-       use_idna_non_transitional ? "xn--wxaijb9b" : "xn--wxaikc6b",
+       "xn--wxaijb9b",
        Component(0, 12), CanonHostInfo::NEUTRAL, -1, ""},
 
       // ZWNJ (U+200C) and ZWJ (U+200D) are mapped away in UTS 46 transitional
@@ -415,8 +492,8 @@
        L"a\x200c"
        L"b\x200d"
        L"c",
-       use_idna_non_transitional ? "xn--abc-9m0ag" : "abc",
-       use_idna_non_transitional ? Component(0, 13) : Component(0, 3),
+       "xn--abc-9m0ag",
+       Component(0, 13),
        CanonHostInfo::NEUTRAL, -1, ""},
 
       // ZWJ between Devanagari characters was still mapped away in UTS 46
@@ -424,8 +501,8 @@
       // Previously "xn--11bo0m".
       {"\xe0\xa4\x95\xe0\xa5\x8d\xe2\x80\x8d\xe0\xa4\x9c",
        L"\x915\x94d\x200d\x91c",
-       use_idna_non_transitional ? "xn--11bo0mv54g" : "xn--11bo0m",
-       use_idna_non_transitional ? Component(0, 14) : Component(0, 10),
+       "xn--11bo0mv54g",
+       Component(0, 14),
        CanonHostInfo::NEUTRAL, -1, ""},
 
       // Fullwidth exclamation mark is disallowed. UTS 46, table 4, row (b)
@@ -435,13 +512,12 @@
        CanonHostInfo::NEUTRAL, -1, ""},
       // U+2132 (turned capital F) is disallowed. UTS 46, table 4, row (c)
       // Allowed in IDNA 2003, but the mapping changed after Unicode 3.2
-      {"\xe2\x84\xb2oo", L"\x2132oo", "%E2%84%B2oo", Component(0, 11),
-       CanonHostInfo::BROKEN, -1, ""},
+      {"\xe2\x84\xb2oo", L"\x2132oo", "xn--oo-3tu", Component(0, 10),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // U+2F868 (CJK Comp) is disallowed. UTS 46, table 4, row (d)
       // Allowed in IDNA 2003, but the mapping changed after Unicode 3.2
       {"\xf0\xaf\xa1\xa8\xe5\xa7\xbb.cn", L"\xd87e\xdc68\x59fb.cn",
-       "%F0%AF%A1%A8%E5%A7%BB.cn", Component(0, 24), CanonHostInfo::BROKEN, -1,
-       ""},
+       "xn--snl080h.cn", Component(0, 14), CanonHostInfo::NEUTRAL, -1, ""},
       // Maps uppercase letters to lower case letters. UTS 46 table 4 row (e)
       {"M\xc3\x9cNCHEN", L"M\xdcNCHEN", "xn--mnchen-3ya", Component(0, 14),
        CanonHostInfo::NEUTRAL, -1, ""},
@@ -549,9 +625,9 @@
        L"%3g%78%63%30%2e%30%32%35%30%2E.01", "%253gxc0.0250..01",
        Component(0, 17), CanonHostInfo::BROKEN, -1, ""},
       // Something that isn't exactly an IP should get treated as a host and
-      // spaces escaped.
+      // spaces treated as invalid.
       {"192.168.0.1 hello", L"192.168.0.1 hello", "192.168.0.1%20hello",
-       Component(0, 19), CanonHostInfo::NEUTRAL, -1, ""},
+       Component(0, 19), CanonHostInfo::BROKEN, -1, ""},
       // Fullwidth and escaped UTF-8 fullwidth should still be treated as IP.
       // These are "0Xc0.0250.01" in fullwidth.
       {"\xef\xbc\x90%Ef%bc\xb8%ef%Bd%83\xef\xbc\x90%EF%BC%"
@@ -598,15 +674,15 @@
   for (const auto& host_case : host_cases) {
     // Narrow version.
     if (host_case.input8) {
-      int host_len = static_cast<int>(strlen(host_case.input8));
+      std::string_view input8(host_case.input8);
+      int host_len = static_cast<int>(input8.length());
       Component in_comp(0, host_len);
       Component out_comp;
 
       out_str.clear();
       StdStringCanonOutput output(&out_str);
 
-      bool success =
-          CanonicalizeHost(host_case.input8, in_comp, &output, &out_comp);
+      bool success = CanonicalizeHost(input8, in_comp, &output, &out_comp);
       output.Complete();
 
       EXPECT_EQ(host_case.expected_family != CanonHostInfo::BROKEN, success)
@@ -630,8 +706,7 @@
       out_str.clear();
       StdStringCanonOutput output(&out_str);
 
-      bool success = CanonicalizeHost(input16.c_str(), in_comp, &output,
-                                      &out_comp);
+      bool success = CanonicalizeHost(input16, in_comp, &output, &out_comp);
       output.Complete();
 
       EXPECT_EQ(host_case.expected_family != CanonHostInfo::BROKEN, success);
@@ -645,24 +720,23 @@
   for (const auto& host_case : host_cases) {
     // Narrow version.
     if (host_case.input8) {
-      int host_len = static_cast<int>(strlen(host_case.input8));
+      std::string_view input8(host_case.input8);
+      int host_len = static_cast<int>(input8.length());
       Component in_comp(0, host_len);
 
       out_str.clear();
       StdStringCanonOutput output(&out_str);
       CanonHostInfo host_info;
 
-      CanonicalizeHostVerbose(host_case.input8, in_comp, &output, &host_info);
+      CanonicalizeHostVerbose(input8, in_comp, &output, &host_info);
       output.Complete();
 
       EXPECT_EQ(host_case.expected_family, host_info.family);
       EXPECT_EQ(host_case.expected, out_str);
       EXPECT_EQ(host_case.expected_component.begin, host_info.out_host.begin);
       EXPECT_EQ(host_case.expected_component.len, host_info.out_host.len);
-      EXPECT_EQ(
-          host_case.expected_address_hex,
-          gurl_base::HexEncode(host_info.address,
-                          static_cast<size_t>(host_info.AddressLength())));
+      EXPECT_EQ(host_case.expected_address_hex,
+                gurl_base::HexEncode(host_info.AddressSpan()));
       if (host_case.expected_family == CanonHostInfo::IPV4) {
         EXPECT_EQ(host_case.expected_num_ipv4_components,
                   host_info.num_ipv4_components);
@@ -680,17 +754,15 @@
       StdStringCanonOutput output(&out_str);
       CanonHostInfo host_info;
 
-      CanonicalizeHostVerbose(input16.c_str(), in_comp, &output, &host_info);
+      CanonicalizeHostVerbose(input16, in_comp, &output, &host_info);
       output.Complete();
 
       EXPECT_EQ(host_case.expected_family, host_info.family);
       EXPECT_EQ(host_case.expected, out_str);
       EXPECT_EQ(host_case.expected_component.begin, host_info.out_host.begin);
       EXPECT_EQ(host_case.expected_component.len, host_info.out_host.len);
-      EXPECT_EQ(
-          host_case.expected_address_hex,
-          gurl_base::HexEncode(host_info.address,
-                          static_cast<size_t>(host_info.AddressLength())));
+      EXPECT_EQ(host_case.expected_address_hex,
+                gurl_base::HexEncode(host_info.AddressSpan()));
       if (host_case.expected_family == CanonHostInfo::IPV4) {
         EXPECT_EQ(host_case.expected_num_ipv4_components,
                   host_info.num_ipv4_components);
@@ -699,15 +771,14 @@
   }
 }
 
-TEST(URLCanonTest, HostPuncutationChar) {
+TEST_F(URLCanonTest, SpecialHostPuncutationChar) {
   // '%' is not tested here. '%' is used for percent-escaping.
-  const std::string_view allowed_host_chars[] = {
-      "!", "\"", "$", "&", "'", "(", ")", "+", ",",
-      "-", ".",  ";", "=", "_", "`", "{", "}", "~",
-  };
+  const std::string_view allowed_host_chars[] = {"!", "\"", "$", "&", "'", "(",
+                                                 ")", "+",  ",", "-", ".", ";",
+                                                 "=", "_",  "`", "{", "}", "~"};
 
   const std::string_view forbidden_host_chars[] = {
-      "#", "/", ":", "<", ">", "?", "@", "[", "\\", "]", "^", "|",
+      " ", "#", "/", ":", "<", ">", "?", "@", "[", "\\", "]", "^", "|",
   };
 
   // Standard non-compliant characters which are escaped. See
@@ -715,14 +786,14 @@
   struct EscapedCharTestCase {
     std::string_view input;
     std::string_view expected;
-  } escaped_host_chars[] = {{" ", "%20"}, {"*", "%2A"}};
+  } escaped_host_chars[] = {{"*", "%2A"}};
 
   for (const std::string_view input : allowed_host_chars) {
     std::string out_str;
     Component in_comp(0, input.size());
     Component out_comp;
     StdStringCanonOutput output(&out_str);
-    bool success = CanonicalizeHost(input.data(), in_comp, &output, &out_comp);
+    bool success = CanonicalizeSpecialHost(input, in_comp, output, out_comp);
     EXPECT_TRUE(success) << "Input: " << input;
     output.Complete();
     EXPECT_EQ(out_str, input) << "Input: " << input;
@@ -733,7 +804,7 @@
     Component in_comp(0, input.size());
     Component out_comp;
     StdStringCanonOutput output(&out_str);
-    EXPECT_FALSE(CanonicalizeHost(input.data(), in_comp, &output, &out_comp))
+    EXPECT_FALSE(CanonicalizeSpecialHost(input, in_comp, output, out_comp))
         << "Input: " << input;
   }
 
@@ -742,15 +813,44 @@
     Component in_comp(0, c.input.size());
     Component out_comp;
     StdStringCanonOutput output(&out_str);
-    bool success =
-        CanonicalizeHost(c.input.data(), in_comp, &output, &out_comp);
+    bool success = CanonicalizeSpecialHost(c.input, in_comp, output, out_comp);
     EXPECT_TRUE(success) << "Input: " << c.input;
     output.Complete();
     EXPECT_EQ(out_str, c.expected) << "Input: " << c.input;
   }
 }
 
-TEST(URLCanonTest, IPv4) {
+TEST_F(URLCanonTest, ForbiddenHostCodePoint) {
+  // Test only CanonicalizeNonSpecialHost.
+  // CanonicalizeSpecialHost is not standard compliant yet.
+  // See URLCanonTest::SpecialHostPuncutationChar.
+
+  // https://url.spec.whatwg.org/#forbidden-host-code-point
+  const std::string_view forbidden_host_chars[] = {
+      "\x09", "\x0A", "\x0D", " ", "#",  "/", ":", "<",
+      ">",    "?",    "@",    "[", "\\", "]", "^", "|",
+  };
+
+  for (const std::string_view input : forbidden_host_chars) {
+    std::string out_str;
+    Component in_comp(0, input.size());
+    Component out_comp;
+    StdStringCanonOutput output(&out_str);
+    EXPECT_FALSE(CanonicalizeNonSpecialHost(input, in_comp, output, out_comp))
+        << "Input: " << input;
+  }
+
+  // Test NULL manually.
+  const char host_with_null[] = "a\0b";
+  std::string out_str;
+  Component in_comp(0, 3);
+  Component out_comp;
+  StdStringCanonOutput output(&out_str);
+  EXPECT_FALSE(CanonicalizeNonSpecialHost(std::string_view(host_with_null, 3u),
+                                          in_comp, output, out_comp));
+}
+
+TEST_F(URLCanonTest, IPv4) {
   // clang-format off
   IPAddressCase cases[] = {
     // Empty is not an IP address.
@@ -859,18 +959,15 @@
     SCOPED_TRACE(test_case.input8);
 
     // 8-bit version.
-    Component component(0, static_cast<int>(strlen(test_case.input8)));
-
     std::string out_str1;
     StdStringCanonOutput output1(&out_str1);
     CanonHostInfo host_info;
-    CanonicalizeIPAddress(test_case.input8, component, &output1, &host_info);
+    CanonicalizeIPAddress(test_case.input8, &output1, &host_info);
     output1.Complete();
 
     EXPECT_EQ(test_case.expected_family, host_info.family);
     EXPECT_EQ(test_case.expected_address_hex,
-              gurl_base::HexEncode(host_info.address,
-                              static_cast<size_t>(host_info.AddressLength())));
+              gurl_base::HexEncode(host_info.AddressSpan()));
     if (host_info.family == CanonHostInfo::IPV4) {
       EXPECT_STREQ(test_case.expected, out_str1.c_str());
       EXPECT_EQ(test_case.expected_component.begin, host_info.out_host.begin);
@@ -882,17 +979,15 @@
     // 16-bit version.
     std::u16string input16(
         test_utils::TruncateWStringToUTF16(test_case.input16));
-    component = Component(0, static_cast<int>(input16.length()));
 
     std::string out_str2;
     StdStringCanonOutput output2(&out_str2);
-    CanonicalizeIPAddress(input16.c_str(), component, &output2, &host_info);
+    CanonicalizeIPAddress(input16, &output2, &host_info);
     output2.Complete();
 
     EXPECT_EQ(test_case.expected_family, host_info.family);
     EXPECT_EQ(test_case.expected_address_hex,
-              gurl_base::HexEncode(host_info.address,
-                              static_cast<size_t>(host_info.AddressLength())));
+              gurl_base::HexEncode(host_info.AddressSpan()));
     if (host_info.family == CanonHostInfo::IPV4) {
       EXPECT_STREQ(test_case.expected, out_str2.c_str());
       EXPECT_EQ(test_case.expected_component.begin, host_info.out_host.begin);
@@ -903,8 +998,8 @@
   }
 }
 
-TEST(URLCanonTest, IPv6) {
-  IPAddressCase cases[] = {
+TEST_F(URLCanonTest, IPv6) {
+  auto cases = std::to_array<IPAddressCase>({
       // Empty is not an IP address.
       {"", L"", "", Component(), CanonHostInfo::NEUTRAL, -1, ""},
       // Non-IPs with [:] characters are marked BROKEN.
@@ -1059,22 +1154,19 @@
       // Spaces should be rejected.
       {"[::1 hello]", L"[::1 hello]", "", Component(), CanonHostInfo::BROKEN,
        -1, ""},
-  };
+  });
 
   for (size_t i = 0; i < std::size(cases); i++) {
     // 8-bit version.
-    Component component(0, static_cast<int>(strlen(cases[i].input8)));
-
     std::string out_str1;
     StdStringCanonOutput output1(&out_str1);
     CanonHostInfo host_info;
-    CanonicalizeIPAddress(cases[i].input8, component, &output1, &host_info);
+    CanonicalizeIPAddress(cases[i].input8, &output1, &host_info);
     output1.Complete();
 
     EXPECT_EQ(cases[i].expected_family, host_info.family);
     EXPECT_EQ(cases[i].expected_address_hex,
-              gurl_base::HexEncode(host_info.address,
-                              static_cast<size_t>(host_info.AddressLength())))
+              gurl_base::HexEncode(host_info.AddressSpan()))
         << "iter " << i << " host " << cases[i].input8;
     if (host_info.family == CanonHostInfo::IPV6) {
       EXPECT_STREQ(cases[i].expected, out_str1.c_str());
@@ -1086,17 +1178,15 @@
     // 16-bit version.
     std::u16string input16(
         test_utils::TruncateWStringToUTF16(cases[i].input16));
-    component = Component(0, static_cast<int>(input16.length()));
 
     std::string out_str2;
     StdStringCanonOutput output2(&out_str2);
-    CanonicalizeIPAddress(input16.c_str(), component, &output2, &host_info);
+    CanonicalizeIPAddress(input16, &output2, &host_info);
     output2.Complete();
 
     EXPECT_EQ(cases[i].expected_family, host_info.family);
     EXPECT_EQ(cases[i].expected_address_hex,
-              gurl_base::HexEncode(host_info.address,
-                              static_cast<size_t>(host_info.AddressLength())));
+              gurl_base::HexEncode(host_info.AddressSpan()));
     if (host_info.family == CanonHostInfo::IPV6) {
       EXPECT_STREQ(cases[i].expected, out_str2.c_str());
       EXPECT_EQ(cases[i].expected_component.begin, host_info.out_host.begin);
@@ -1105,30 +1195,28 @@
   }
 }
 
-TEST(URLCanonTest, IPEmpty) {
+TEST_F(URLCanonTest, IPEmpty) {
   std::string out_str1;
   StdStringCanonOutput output1(&out_str1);
   CanonHostInfo host_info;
 
   // This tests tests.
-  const char spec[] = "192.168.0.1";
-  CanonicalizeIPAddress(spec, Component(), &output1, &host_info);
+  CanonicalizeIPAddress(std::string_view(), &output1, &host_info);
   EXPECT_FALSE(host_info.IsIPAddress());
 
-  CanonicalizeIPAddress(spec, Component(0, 0), &output1, &host_info);
+  CanonicalizeIPAddress("", &output1, &host_info);
   EXPECT_FALSE(host_info.IsIPAddress());
 }
 
 // Verifies that CanonicalizeHostSubstring produces the expected output and
 // does not "fix" IP addresses. Because this code is a subset of
 // CanonicalizeHost, the shared functionality is not tested.
-TEST(URLCanonTest, CanonicalizeHostSubstring) {
+TEST_F(URLCanonTest, CanonicalizeHostSubstring) {
   // Basic sanity check.
   {
     std::string out_str;
     StdStringCanonOutput output(&out_str);
-    EXPECT_TRUE(CanonicalizeHostSubstring("M\xc3\x9cNCHEN.com",
-                                          Component(0, 12), &output));
+    EXPECT_TRUE(CanonicalizeHostSubstring("M\xc3\x9cNCHEN.com", &output));
     output.Complete();
     EXPECT_EQ("xn--mnchen-3ya.com", out_str);
   }
@@ -1138,8 +1226,7 @@
     std::string out_str;
     StdStringCanonOutput output(&out_str);
     EXPECT_FALSE(CanonicalizeHostSubstring(
-        test_utils::TruncateWStringToUTF16(L"\xfdd0zyx.com").c_str(),
-        Component(0, 8), &output));
+        test_utils::TruncateWStringToUTF16(L"\xfdd0zyx.com"), &output));
     output.Complete();
     EXPECT_EQ("%EF%B7%90zyx.com", out_str);
   }
@@ -1148,7 +1235,7 @@
   {
     std::string out_str;
     StdStringCanonOutput output(&out_str);
-    EXPECT_TRUE(CanonicalizeHostSubstring("", Component(0, 0), &output));
+    EXPECT_TRUE(CanonicalizeHostSubstring("", &output));
     output.Complete();
     EXPECT_EQ(std::string(), out_str);
   }
@@ -1157,14 +1244,13 @@
   {
     std::string out_str;
     StdStringCanonOutput output(&out_str);
-    EXPECT_TRUE(
-        CanonicalizeHostSubstring("01.02.03.04", Component(0, 11), &output));
+    EXPECT_TRUE(CanonicalizeHostSubstring("01.02.03.04", &output));
     output.Complete();
     EXPECT_EQ("01.02.03.04", out_str);
   }
 }
 
-TEST(URLCanonTest, UserInfo) {
+TEST_F(URLCanonTest, UserInfo) {
   // Note that the canonicalizer should escape and treat empty components as
   // not being there.
 
@@ -1191,16 +1277,15 @@
   };
 
   for (const auto& user_info_case : user_info_cases) {
-    int url_len = static_cast<int>(strlen(user_info_case.input));
-    Parsed parsed;
-    ParseStandardURL(user_info_case.input, url_len, &parsed);
+    Parsed parsed = ParseStandardUrl(user_info_case.input);
     Component out_user, out_pass;
     std::string out_str;
     StdStringCanonOutput output1(&out_str);
 
-    bool success = CanonicalizeUserInfo(user_info_case.input, parsed.username,
-                                        user_info_case.input, parsed.password,
-                                        &output1, &out_user, &out_pass);
+    bool success = CanonicalizeUserInfo(
+        parsed.username.maybe_as_string_view_on(user_info_case.input),
+        parsed.password.maybe_as_string_view_on(user_info_case.input), &output1,
+        &out_user, &out_pass);
     output1.Complete();
 
     EXPECT_EQ(user_info_case.expected_success, success);
@@ -1214,13 +1299,9 @@
     out_str.clear();
     StdStringCanonOutput output2(&out_str);
     std::u16string wide_input(gurl_base::UTF8ToUTF16(user_info_case.input));
-    success = CanonicalizeUserInfo(wide_input.c_str(),
-                                   parsed.username,
-                                   wide_input.c_str(),
-                                   parsed.password,
-                                   &output2,
-                                   &out_user,
-                                   &out_pass);
+    success = CanonicalizeUserInfo(parsed.username.MaybeAsViewOn(wide_input),
+                                   parsed.password.MaybeAsViewOn(wide_input),
+                                   &output2, &out_user, &out_pass);
     output2.Complete();
 
     EXPECT_EQ(user_info_case.expected_success, success);
@@ -1232,7 +1313,7 @@
   }
 }
 
-TEST(URLCanonTest, Port) {
+TEST_F(URLCanonTest, Port) {
   // We only need to test that the number gets properly put into the output
   // buffer. The parser unit tests will test scanning the number correctly.
   //
@@ -1256,13 +1337,11 @@
   };
 
   for (const auto& port_case : port_cases) {
-    int url_len = static_cast<int>(strlen(port_case.input));
-    Component in_comp(0, url_len);
     Component out_comp;
     std::string out_str;
     StdStringCanonOutput output1(&out_str);
-    bool success = CanonicalizePort(
-        port_case.input, in_comp, port_case.default_port, &output1, &out_comp);
+    bool success = CanonicalizePort(port_case.input, port_case.default_port,
+                                    &output1, &out_comp);
     output1.Complete();
 
     EXPECT_EQ(port_case.expected_success, success);
@@ -1274,8 +1353,8 @@
     out_str.clear();
     StdStringCanonOutput output2(&out_str);
     std::u16string wide_input(gurl_base::UTF8ToUTF16(port_case.input));
-    success = CanonicalizePort(wide_input.c_str(), in_comp,
-                               port_case.default_port, &output2, &out_comp);
+    success = CanonicalizePort(wide_input, port_case.default_port, &output2,
+                               &out_comp);
     output2.Complete();
 
     EXPECT_EQ(port_case.expected_success, success);
@@ -1341,8 +1420,6 @@
      true},
     // Funny characters that are unescaped should be escaped
     {"/foo\tbar", L"/foo\tbar", "/foo%09bar", Component(0, 10), true},
-    // Backslashes should get converted to forward slashes
-    {"\\foo\\bar", L"\\foo\\bar", "/foo/bar", Component(0, 8), true},
     // Hashes found in paths (possibly only when the caller explicitly sets
     // the path on an already-parsed URL) should be escaped.
     {"/foo#bar", L"/foo#bar", "/foo%23bar", Component(0, 10), true},
@@ -1377,31 +1454,29 @@
     {nullptr, L"/\xfdd0zyx", "/%EF%B7%90zyx", Component(0, 13), true},
 };
 
-typedef bool (*CanonFunc8Bit)(const char*,
-                              const Component&,
-                              CanonOutput*,
-                              Component*);
-typedef bool (*CanonFunc16Bit)(const char16_t*,
-                               const Component&,
+using CanonFunc8Bit = bool (*)(std::optional<std::string_view>,
                                CanonOutput*,
                                Component*);
-
-void DoPathTest(const DualComponentCase* path_cases,
-                size_t num_cases,
+using CanonFunc16Bit = bool (*)(std::optional<std::u16string_view>,
+                                CanonOutput*,
+                                Component*);
+void DoPathTest(gurl_base::span<const DualComponentCase> path_cases,
+                size_t spanification_suspected_redundant_num_cases,
                 CanonFunc8Bit canon_func_8,
                 CanonFunc16Bit canon_func_16) {
-  for (size_t i = 0; i < num_cases; i++) {
+  // TODO(crbug.com/431824301): Remove unneeded parameter once validated to be
+  // redundant in M143.
+  GURL_CHECK(spanification_suspected_redundant_num_cases == path_cases.size(),
+        gurl_base::NotFatalUntil::M143);
+  for (size_t i = 0; i < spanification_suspected_redundant_num_cases; i++) {
     testing::Message scope_message;
     scope_message << path_cases[i].input8 << "," << path_cases[i].input16;
     SCOPED_TRACE(scope_message);
     if (path_cases[i].input8) {
-      int len = static_cast<int>(strlen(path_cases[i].input8));
-      Component in_comp(0, len);
       Component out_comp;
       std::string out_str;
       StdStringCanonOutput output(&out_str);
-      bool success =
-          canon_func_8(path_cases[i].input8, in_comp, &output, &out_comp);
+      bool success = canon_func_8(path_cases[i].input8, &output, &out_comp);
       output.Complete();
 
       EXPECT_EQ(path_cases[i].expected_success, success);
@@ -1413,14 +1488,11 @@
     if (path_cases[i].input16) {
       std::u16string input16(
           test_utils::TruncateWStringToUTF16(path_cases[i].input16));
-      int len = static_cast<int>(input16.length());
-      Component in_comp(0, len);
       Component out_comp;
       std::string out_str;
       StdStringCanonOutput output(&out_str);
 
-      bool success =
-          canon_func_16(input16.c_str(), in_comp, &output, &out_comp);
+      bool success = canon_func_16(input16, &output, &out_comp);
       output.Complete();
 
       EXPECT_EQ(path_cases[i].expected_success, success);
@@ -1431,37 +1503,68 @@
   }
 }
 
-TEST(URLCanonTest, Path) {
-  DoPathTest(kCommonPathCases, std::size(kCommonPathCases), CanonicalizePath,
-             CanonicalizePath);
+TEST_F(URLCanonTest, SpecialPath) {
+  // Common test cases
+  DoPathTest(kCommonPathCases, std::size(kCommonPathCases),
+             CanonicalizeSpecialPath, CanonicalizeSpecialPath);
 
   // Manual test: embedded NULLs should be escaped and the URL should be marked
   // as valid.
   const char path_with_null[] = "/ab\0c";
-  Component in_comp(0, 5);
   Component out_comp;
 
   std::string out_str;
   StdStringCanonOutput output(&out_str);
-  bool success = CanonicalizePath(path_with_null, in_comp, &output, &out_comp);
+  bool success = CanonicalizeSpecialPath(
+      gurl_base::MakeStringViewWithNulChars(path_with_null), &output, &out_comp);
   output.Complete();
   EXPECT_TRUE(success);
   EXPECT_EQ("/ab%00c", out_str);
+
+  // Test cases specific on special URLs.
+  DualComponentCase special_path_cases[] = {
+      // Canonical path for empty path is a slash.
+      {"", L"", "/", Component(0, 1), true},
+      // Backslashes should be used as path separators.
+      {"\\a\\b", L"\\a\\b", "/a/b", Component(0, 4), true},
+      {"/a\\..\\b", L"/a\\..\\b", "/b", Component(0, 2), true},
+      {"/a\\.\\b", L"/a\\.\\b", "/a/b", Component(0, 4), true},
+  };
+
+  DoPathTest(special_path_cases, std::size(special_path_cases),
+             CanonicalizeSpecialPath, CanonicalizeSpecialPath);
 }
 
-TEST(URLCanonTest, PartialPath) {
+TEST_F(URLCanonTest, NonSpecialPath) {
+  // Common test cases
+  DoPathTest(kCommonPathCases, std::size(kCommonPathCases),
+             CanonicalizeNonSpecialPath, CanonicalizeNonSpecialPath);
+
+  // Test cases specific on non-special URLs.
+  DualComponentCase non_special_path_cases[] = {
+      // Empty.
+      {"", L"", "", Component(0, 0), true},
+      // Backslashes.
+      {"/a\\..\\b", L"/a\\..\\b", "/a\\..\\b", Component(0, 7), true},
+      {"/a\\./b", L"/a\\./b", "/a\\./b", Component(0, 6), true},
+  };
+
+  DoPathTest(non_special_path_cases, std::size(non_special_path_cases),
+             CanonicalizeNonSpecialPath, CanonicalizeNonSpecialPath);
+}
+
+TEST_F(URLCanonTest, PartialPath) {
   DualComponentCase partial_path_cases[] = {
       {".html", L".html", ".html", Component(0, 5), true},
       {"", L"", "", Component(0, 0), true},
   };
-
   DoPathTest(kCommonPathCases, std::size(kCommonPathCases),
              CanonicalizePartialPath, CanonicalizePartialPath);
   DoPathTest(partial_path_cases, std::size(partial_path_cases),
              CanonicalizePartialPath, CanonicalizePartialPath);
 }
 
-TEST(URLCanonTest, Query) {
+TEST_F(URLCanonTest, Query) {
   struct QueryCase {
     const char* input8;
     const wchar_t* input16;
@@ -1496,8 +1599,8 @@
       std::string out_str;
 
       StdStringCanonOutput output(&out_str);
-      CanonicalizeQuery(query_case.input8, in_comp, nullptr, &output,
-                        &out_comp);
+      CanonicalizeQuery(in_comp.as_string_view_on(query_case.input8), nullptr,
+                        &output, &out_comp);
       output.Complete();
 
       EXPECT_EQ(query_case.expected, out_str);
@@ -1511,7 +1614,7 @@
       std::string out_str;
 
       StdStringCanonOutput output(&out_str);
-      CanonicalizeQuery(input16.c_str(), in_comp, nullptr, &output, &out_comp);
+      CanonicalizeQuery(in_comp.AsViewOn(input16), nullptr, &output, &out_comp);
       output.Complete();
 
       EXPECT_EQ(query_case.expected, out_str);
@@ -1522,13 +1625,13 @@
   std::string out_str;
   StdStringCanonOutput output(&out_str);
   Component out_comp;
-  CanonicalizeQuery("a \x00z\x01", Component(0, 5), nullptr, &output,
-                    &out_comp);
+  CanonicalizeQuery(gurl_base::MakeStringViewWithNulChars("a \x00z\x01"), nullptr,
+                    &output, &out_comp);
   output.Complete();
   EXPECT_EQ("?a%20%00z%01", out_str);
 }
 
-TEST(URLCanonTest, Ref) {
+TEST_F(URLCanonTest, Ref) {
   // Refs are trivial, it just checks the encoding.
   DualComponentCase ref_cases[] = {
       {"hello!", L"hello!", "#hello!", Component(1, 6), true},
@@ -1569,7 +1672,8 @@
 
       std::string out_str;
       StdStringCanonOutput output(&out_str);
-      CanonicalizeRef(ref_case.input8, in_comp, &output, &out_comp);
+      CanonicalizeRef(in_comp.maybe_as_string_view_on(ref_case.input8), &output,
+                      &out_comp);
       output.Complete();
 
       EXPECT_EQ(ref_case.expected_component.begin, out_comp.begin);
@@ -1587,7 +1691,7 @@
 
       std::string out_str;
       StdStringCanonOutput output(&out_str);
-      CanonicalizeRef(input16.c_str(), in_comp, &output, &out_comp);
+      CanonicalizeRef(in_comp.MaybeAsViewOn(input16), &output, &out_comp);
       output.Complete();
 
       EXPECT_EQ(ref_case.expected_component.begin, out_comp.begin);
@@ -1603,7 +1707,8 @@
 
   std::string out_str;
   StdStringCanonOutput output(&out_str);
-  CanonicalizeRef(null_input, null_input_component, &output, &out_comp);
+  CanonicalizeRef(null_input_component.as_string_view_on(null_input), &output,
+                  &out_comp);
   output.Complete();
 
   EXPECT_EQ(1, out_comp.begin);
@@ -1611,7 +1716,7 @@
   EXPECT_EQ("#ab%00z", out_str);
 }
 
-TEST(URLCanonTest, CanonicalizeStandardURL) {
+TEST_F(URLCanonTest, CanonicalizeStandardUrl) {
   // The individual component canonicalize tests should have caught the cases
   // for each of those components. Here, we just need to test that the various
   // parts are included or excluded properly, and have the correct separators.
@@ -1667,16 +1772,14 @@
   // clang-format on
 
   for (const auto& i : cases) {
-    int url_len = static_cast<int>(strlen(i.input));
-    Parsed parsed;
-    ParseStandardURL(i.input, url_len, &parsed);
+    Parsed parsed = ParseStandardUrl(i.input);
 
     Parsed out_parsed;
     std::string out_str;
     StdStringCanonOutput output(&out_str);
-    bool success = CanonicalizeStandardURL(
-        i.input, url_len, parsed, SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION,
-        nullptr, &output, &out_parsed);
+    bool success = CanonicalizeStandardUrl(
+        i.input, parsed, SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr,
+        &output, &out_parsed);
     output.Complete();
 
     EXPECT_EQ(i.expected_success, success);
@@ -1684,9 +1787,138 @@
   }
 }
 
+TEST_F(URLCanonTest, CanonicalizeNonSpecialUrl) {
+  // The individual component canonicalize tests should have caught the cases
+  // for each of those components. Here, we just need to test that the various
+  // parts are included or excluded properly, and have the correct separators.
+  struct URLCase {
+    const std::string_view input;
+    const std::string_view expected;
+    bool expected_success;
+  } cases[] = {
+      // Basic cases.
+      {"git://host:80/path?a=b#ref", "git://host:80/path?a=b#ref", true},
+      {"git://host", "git://host", true},
+      {"git://host/", "git://host/", true},
+      {"git://HosT/", "git://HosT/", true},
+      {"git://..", "git://..", true},
+      {"git://../", "git://../", true},
+      {"git://../..", "git://../", true},
+
+      // Empty hosts.
+      {"git://", "git://", true},
+      {"git:///", "git:///", true},
+      {"git:////", "git:////", true},
+      {"git:///a", "git:///a", true},
+      {"git:///a/../b", "git:///b", true},
+      {"git:///..", "git:///", true},
+
+      // No hosts.
+      {"git:/", "git:/", true},
+      {"git:/a", "git:/a", true},
+      {"git:/a/../b", "git:/b", true},
+      {"git:/..", "git:/", true},
+      {"git:/../", "git:/", true},
+      {"git:/../..", "git:/", true},
+      {"git:/.//a", "git:/.//a", true},
+
+      // Users.
+      {"git://@host", "git://host", true},
+      {"git:// @host", "git://%20@host", true},
+      {"git://\\@host", "git://%5C@host", true},
+
+      // Paths.
+      {"git://host/path", "git://host/path", true},
+      {"git://host/p ath", "git://host/p%20ath", true},
+      {"git://host/a/../b", "git://host/b", true},
+      {"git://host/..", "git://host/", true},
+      {"git://host/../", "git://host/", true},
+      {"git://host/../..", "git://host/", true},
+      {"git://host/.", "git://host/", true},
+      {"git://host/./", "git://host/", true},
+      {"git://host/./.", "git://host/", true},
+      // Backslashes.
+      {"git://host/a\\..\\b", "git://host/a\\..\\b", true},
+
+      // IPv6.
+      {"git://[1:2:0:0:5:0:0:0]", "git://[1:2:0:0:5::]", true},
+      {"git://[1:2:0:0:5:0:0:0]/", "git://[1:2:0:0:5::]/", true},
+      {"git://[1:2:0:0:5:0:0:0]/path", "git://[1:2:0:0:5::]/path", true},
+
+      // IPv4 is unsupported.
+      {"git://127.00.0.1", "git://127.00.0.1", true},
+      {"git://127.1000.0.1", "git://127.1000.0.1", true},
+
+      // Invalid URLs.
+      {"git://@", "git://", false},
+      // Forbidden host code points.
+      {"git://<", "git://", false},
+      {"git:// /", "git:///", false},
+      // Backslashes cannot be used as host terminators.
+      {"git://host\\a/../b", "git://host/b", false},
+
+      // Opaque paths.
+      {"git:", "git:", true},
+      {"git:opaque", "git:opaque", true},
+      {"git:o p a q u e", "git:o p a q u e", true},
+      {"git: <", "git: <", true},
+      {"git:opaque/a/../b", "git:opaque/a/../b", true},
+      {"git:opaque\\a\\..\\b", "git:opaque\\a\\..\\b", true},
+      {"git:\\a", "git:\\a", true},
+      // Like URNs.
+      {"git:a:b:c:123", "git:a:b:c:123", true},
+  };
+
+  for (const auto& i : cases) {
+    SCOPED_TRACE(i.input);
+    Parsed parsed = ParseNonSpecialUrl(i.input);
+    Parsed out_parsed;
+    std::string out_str;
+    StdStringCanonOutput output(&out_str);
+    bool success = CanonicalizeNonSpecialUrl(i.input, parsed,
+                                             /*query_converter=*/nullptr,
+                                             output, out_parsed);
+    output.Complete();
+    EXPECT_EQ(success, i.expected_success);
+    EXPECT_EQ(out_str, i.expected);
+  }
+}
+
+TEST_F(URLCanonTest, CanonicalizeNonSpecialUrlOutputParsed) {
+  // Test that out_parsed is correctly set.
+  struct URLCase {
+    const std::string_view input;
+    // Currently, test only host and length.
+    Component expected_output_parsed_host;
+    int expected_output_parsed_length;
+  } cases[] = {
+      {"git:", Component(), 4},
+      {"git:opaque", Component(), 10},
+      {"git:/", Component(), 5},
+      {"git://", Component(6, 0), 6},
+      {"git:///", Component(6, 0), 7},
+      // The length of "[1:2:0:0:5::]" is 13.
+      {"git://[1:2:0:0:5:0:0:0]/", Component(6, 13), 20},
+  };
+
+  for (const auto& i : cases) {
+    SCOPED_TRACE(i.input);
+    Parsed parsed = ParseNonSpecialUrl(i.input);
+    Parsed out_parsed;
+    std::string unused_out_str;
+    StdStringCanonOutput unused_output(&unused_out_str);
+    bool success = CanonicalizeNonSpecialUrl(i.input, parsed,
+                                             /*query_converter=*/nullptr,
+                                             unused_output, out_parsed);
+    ASSERT_TRUE(success);
+    EXPECT_EQ(out_parsed.host, i.expected_output_parsed_host);
+    EXPECT_EQ(out_parsed.Length(), i.expected_output_parsed_length);
+  }
+}
+
 // The codepath here is the same as for regular canonicalization, so we just
 // need to test that things are replaced or not correctly.
-TEST(URLCanonTest, ReplaceStandardURL) {
+TEST_F(URLCanonTest, ReplaceStandardUrl) {
   ReplaceCase replace_cases[] = {
       // Common case of truncating the path.
       {"http://www.google.com/foo?bar=baz#ref", nullptr, nullptr, nullptr,
@@ -1709,9 +1941,7 @@
 
   for (const auto& replace_case : replace_cases) {
     const ReplaceCase& cur = replace_case;
-    int base_len = static_cast<int>(strlen(cur.base));
-    Parsed parsed;
-    ParseStandardURL(cur.base, base_len, &parsed);
+    Parsed parsed = ParseStandardUrl(cur.base);
 
     Replacements<char> r;
     typedef Replacements<char> R;  // Clean up syntax.
@@ -1730,7 +1960,7 @@
     std::string out_str;
     StdStringCanonOutput output(&out_str);
     Parsed out_parsed;
-    ReplaceStandardURL(replace_case.base, parsed, r,
+    ReplaceStandardUrl(replace_case.base, parsed, r,
                        SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr,
                        &output, &out_parsed);
     output.Complete();
@@ -1741,10 +1971,7 @@
   // The path pointer should be ignored if the address is invalid.
   {
     const char src[] = "http://www.google.com/here_is_the_path";
-    int src_len = static_cast<int>(strlen(src));
-
-    Parsed parsed;
-    ParseStandardURL(src, src_len, &parsed);
+    Parsed parsed = ParseStandardUrl(src);
 
     // Replace the path to 0 length string. By using 1 as the string address,
     // the test should get an access violation if it tries to dereference it.
@@ -1753,7 +1980,7 @@
     std::string out_str1;
     StdStringCanonOutput output1(&out_str1);
     Parsed new_parsed;
-    ReplaceStandardURL(src, parsed, r,
+    ReplaceStandardUrl(src, parsed, r,
                        SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr,
                        &output1, &new_parsed);
     output1.Complete();
@@ -1763,7 +1990,7 @@
     r.SetPath(reinterpret_cast<char*>(0x00000001), Component());
     std::string out_str2;
     StdStringCanonOutput output2(&out_str2);
-    ReplaceStandardURL(src, parsed, r,
+    ReplaceStandardUrl(src, parsed, r,
                        SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr,
                        &output2, &new_parsed);
     output2.Complete();
@@ -1771,7 +1998,7 @@
   }
 }
 
-TEST(URLCanonTest, ReplaceFileURL) {
+TEST_F(URLCanonTest, ReplaceFileUrl) {
   ReplaceCase replace_cases[] = {
       // Replace everything
       {"file:///C:/gaba?query#ref", nullptr, nullptr, nullptr, "filer", nullptr,
@@ -1810,9 +2037,7 @@
   for (const auto& replace_case : replace_cases) {
     const ReplaceCase& cur = replace_case;
     SCOPED_TRACE(cur.base);
-    int base_len = static_cast<int>(strlen(cur.base));
-    Parsed parsed;
-    ParseFileURL(cur.base, base_len, &parsed);
+    Parsed parsed = ParseFileUrl(cur.base);
 
     Replacements<char> r;
     typedef Replacements<char> R;  // Clean up syntax.
@@ -1828,14 +2053,14 @@
     std::string out_str;
     StdStringCanonOutput output(&out_str);
     Parsed out_parsed;
-    ReplaceFileURL(cur.base, parsed, r, nullptr, &output, &out_parsed);
+    ReplaceFileUrl(cur.base, parsed, r, nullptr, &output, &out_parsed);
     output.Complete();
 
     EXPECT_EQ(replace_case.expected, out_str);
   }
 }
 
-TEST(URLCanonTest, ReplaceFileSystemURL) {
+TEST_F(URLCanonTest, ReplaceFileSystemUrl) {
   ReplaceCase replace_cases[] = {
       // Replace everything in the outer URL.
       {"filesystem:file:///temporary/gaba?query#ref", nullptr, nullptr, nullptr,
@@ -1878,9 +2103,7 @@
 
   for (const auto& replace_case : replace_cases) {
     const ReplaceCase& cur = replace_case;
-    int base_len = static_cast<int>(strlen(cur.base));
-    Parsed parsed;
-    ParseFileSystemURL(cur.base, base_len, &parsed);
+    Parsed parsed = ParseFileSystemUrl(cur.base);
 
     Replacements<char> r;
     typedef Replacements<char> R;  // Clean up syntax.
@@ -1896,14 +2119,14 @@
     std::string out_str;
     StdStringCanonOutput output(&out_str);
     Parsed out_parsed;
-    ReplaceFileSystemURL(cur.base, parsed, r, nullptr, &output, &out_parsed);
+    ReplaceFileSystemUrl(cur.base, parsed, r, nullptr, &output, &out_parsed);
     output.Complete();
 
     EXPECT_EQ(replace_case.expected, out_str);
   }
 }
 
-TEST(URLCanonTest, ReplacePathURL) {
+TEST_F(URLCanonTest, ReplacePathUrl) {
   ReplaceCase replace_cases[] = {
       // Replace everything
       {"data:foo", "javascript", nullptr, nullptr, nullptr, nullptr,
@@ -1922,9 +2145,6 @@
 
   for (const auto& replace_case : replace_cases) {
     const ReplaceCase& cur = replace_case;
-    int base_len = static_cast<int>(strlen(cur.base));
-    Parsed parsed;
-    ParsePathURL(cur.base, base_len, false, &parsed);
 
     Replacements<char> r;
     typedef Replacements<char> R;  // Clean up syntax.
@@ -1940,14 +2160,15 @@
     std::string out_str;
     StdStringCanonOutput output(&out_str);
     Parsed out_parsed;
-    ReplacePathURL(cur.base, parsed, r, &output, &out_parsed);
+    ReplacePathUrl(cur.base, ParsePathUrl(cur.base, false), r, &output,
+                   &out_parsed);
     output.Complete();
 
     EXPECT_EQ(replace_case.expected, out_str);
   }
 }
 
-TEST(URLCanonTest, ReplaceMailtoURL) {
+TEST_F(URLCanonTest, ReplaceMailtoUrl) {
   ReplaceCase replace_cases[] = {
       // Replace everything
       {"mailto:jon@foo.com?body=sup", "mailto", nullptr, nullptr, nullptr,
@@ -1983,9 +2204,7 @@
 
   for (const auto& replace_case : replace_cases) {
     const ReplaceCase& cur = replace_case;
-    int base_len = static_cast<int>(strlen(cur.base));
-    Parsed parsed;
-    ParseMailtoURL(cur.base, base_len, &parsed);
+    Parsed parsed = ParseMailtoUrl(cur.base);
 
     Replacements<char> r;
     typedef Replacements<char> R;
@@ -2001,14 +2220,14 @@
     std::string out_str;
     StdStringCanonOutput output(&out_str);
     Parsed out_parsed;
-    ReplaceMailtoURL(cur.base, parsed, r, &output, &out_parsed);
+    ReplaceMailtoUrl(cur.base, parsed, r, &output, &out_parsed);
     output.Complete();
 
     EXPECT_EQ(replace_case.expected, out_str);
   }
 }
 
-TEST(URLCanonTest, CanonicalizeFileURL) {
+TEST_F(URLCanonTest, CanonicalizeFileUrl) {
   struct URLCase {
     const char* input;
     const char* expected;
@@ -2095,15 +2314,13 @@
   };
 
   for (const auto& i : cases) {
-    int url_len = static_cast<int>(strlen(i.input));
-    Parsed parsed;
-    ParseFileURL(i.input, url_len, &parsed);
+    Parsed parsed = ParseFileUrl(i.input);
 
     Parsed out_parsed;
     std::string out_str;
     StdStringCanonOutput output(&out_str);
-    bool success = CanonicalizeFileURL(i.input, url_len, parsed, nullptr,
-                                       &output, &out_parsed);
+    bool success =
+        CanonicalizeFileUrl(i.input, parsed, nullptr, &output, &out_parsed);
     output.Complete();
 
     EXPECT_EQ(i.expected_success, success);
@@ -2122,7 +2339,7 @@
   }
 }
 
-TEST(URLCanonTest, CanonicalizeFileSystemURL) {
+TEST_F(URLCanonTest, CanonicalizeFileSystemUrl) {
   struct URLCase {
     const char* input;
     const char* expected;
@@ -2144,15 +2361,13 @@
   };
 
   for (const auto& i : cases) {
-    int url_len = static_cast<int>(strlen(i.input));
-    Parsed parsed;
-    ParseFileSystemURL(i.input, url_len, &parsed);
+    Parsed parsed = ParseFileSystemUrl(i.input);
 
     Parsed out_parsed;
     std::string out_str;
     StdStringCanonOutput output(&out_str);
-    bool success = CanonicalizeFileSystemURL(i.input, url_len, parsed, nullptr,
-                                             &output, &out_parsed);
+    bool success = CanonicalizeFileSystemUrl(i.input, parsed, nullptr, &output,
+                                             &out_parsed);
     output.Complete();
 
     EXPECT_EQ(i.expected_success, success);
@@ -2167,7 +2382,7 @@
   }
 }
 
-TEST(URLCanonTest, CanonicalizePathURL) {
+TEST_F(URLCanonTest, CanonicalizePathUrl) {
   // Path URLs should get canonicalized schemes but nothing else.
   struct PathCase {
     const char* input;
@@ -2184,13 +2399,12 @@
 
   for (const auto& path_case : path_cases) {
     int url_len = static_cast<int>(strlen(path_case.input));
-    Parsed parsed;
-    ParsePathURL(path_case.input, url_len, true, &parsed);
 
     Parsed out_parsed;
     std::string out_str;
     StdStringCanonOutput output(&out_str);
-    bool success = CanonicalizePathURL(path_case.input, url_len, parsed,
+    bool success = CanonicalizePathUrl(path_case.input,
+                                       ParsePathUrl(path_case.input, true),
                                        &output, &out_parsed);
     output.Complete();
 
@@ -2208,7 +2422,7 @@
   }
 }
 
-TEST(URLCanonTest, CanonicalizePathURLPath) {
+TEST_F(URLCanonTest, CanonicalizePathUrlPath) {
   struct PathCase {
     std::string input;
     std::wstring input16;
@@ -2225,9 +2439,7 @@
     std::string out_str;
     StdStringCanonOutput output(&out_str);
     url::Component out_component;
-    CanonicalizePathURLPath(path_case.input.data(),
-                            Component(0, path_case.input.size()), &output,
-                            &out_component);
+    CanonicalizePathUrlPath(path_case.input, &output, &out_component);
     output.Complete();
 
     EXPECT_EQ(path_case.expected, out_str);
@@ -2242,9 +2454,7 @@
     url::Component out_component16;
     std::u16string input16(
         test_utils::TruncateWStringToUTF16(path_case.input16.data()));
-    CanonicalizePathURLPath(input16.c_str(),
-                            Component(0, path_case.input16.size()), &output16,
-                            &out_component16);
+    CanonicalizePathUrlPath(input16, &output16, &out_component16);
     output16.Complete();
 
     EXPECT_EQ(path_case.expected, out_str16);
@@ -2255,78 +2465,68 @@
   }
 }
 
-TEST(URLCanonTest, CanonicalizeMailtoURL) {
+TEST_F(URLCanonTest, CanonicalizeMailtoUrl) {
   struct URLCase {
     const char* input;
     const char* expected;
     bool expected_success;
     Component expected_path;
     Component expected_query;
-  } cases[] = {
-    // Null character should be escaped to %00.
-    // Keep this test first in the list as it is handled specially below.
-    {"mailto:addr1\0addr2?foo",
-     "mailto:addr1%00addr2?foo",
-     true, Component(7, 13), Component(21, 3)},
-    {"mailto:addr1",
-     "mailto:addr1",
-     true, Component(7, 5), Component()},
-    {"mailto:addr1@foo.com",
-     "mailto:addr1@foo.com",
-     true, Component(7, 13), Component()},
-    // Trailing whitespace is stripped.
-    {"MaIlTo:addr1 \t ",
-     "mailto:addr1",
-     true, Component(7, 5), Component()},
-    {"MaIlTo:addr1?to=jon",
-     "mailto:addr1?to=jon",
-     true, Component(7, 5), Component(13,6)},
-    {"mailto:addr1,addr2",
-     "mailto:addr1,addr2",
-     true, Component(7, 11), Component()},
-    // Embedded spaces must be encoded.
-    {"mailto:addr1, addr2",
-     "mailto:addr1,%20addr2",
-     true, Component(7, 14), Component()},
-    {"mailto:addr1, addr2?subject=one two ",
-     "mailto:addr1,%20addr2?subject=one%20two",
-     true, Component(7, 14), Component(22, 17)},
-    {"mailto:addr1%2caddr2",
-     "mailto:addr1%2caddr2",
-     true, Component(7, 13), Component()},
-    {"mailto:\xF0\x90\x8C\x80",
-     "mailto:%F0%90%8C%80",
-     true, Component(7, 12), Component()},
-    // Invalid -- UTF-8 encoded surrogate value.
-    {"mailto:\xed\xa0\x80",
-     "mailto:%EF%BF%BD%EF%BF%BD%EF%BF%BD",
-     false, Component(7, 27), Component()},
-    {"mailto:addr1?",
-     "mailto:addr1?",
-     true, Component(7, 5), Component(13, 0)},
-    // Certain characters have special meanings and must be encoded.
-    {"mailto:! \x22$&()+,-./09:;<=>@AZ[\\]&_`az{|}~\x7f?Query! \x22$&()+,-./09:;<=>@AZ[\\]&_`az{|}~",
-     "mailto:!%20%22$&()+,-./09:;%3C=%3E@AZ[\\]&_%60az%7B%7C%7D~%7F?Query!%20%22$&()+,-./09:;%3C=%3E@AZ[\\]&_`az{|}~",
-     true, Component(7, 53), Component(61, 47)},
   };
+  auto cases = std::to_array<URLCase>({
+      // Null character should be escaped to %00.
+      // Keep this test first in the list as it is handled specially below.
+      {"mailto:addr1\0addr2?foo", "mailto:addr1%00addr2?foo", true,
+       Component(7, 13), Component(21, 3)},
+      {"mailto:addr1", "mailto:addr1", true, Component(7, 5), Component()},
+      {"mailto:addr1@foo.com", "mailto:addr1@foo.com", true, Component(7, 13),
+       Component()},
+      // Trailing whitespace is stripped.
+      {"MaIlTo:addr1 \t ", "mailto:addr1", true, Component(7, 5), Component()},
+      {"MaIlTo:addr1?to=jon", "mailto:addr1?to=jon", true, Component(7, 5),
+       Component(13, 6)},
+      {"mailto:addr1,addr2", "mailto:addr1,addr2", true, Component(7, 11),
+       Component()},
+      // Embedded spaces must be encoded.
+      {"mailto:addr1, addr2", "mailto:addr1,%20addr2", true, Component(7, 14),
+       Component()},
+      {"mailto:addr1, addr2?subject=one two ",
+       "mailto:addr1,%20addr2?subject=one%20two", true, Component(7, 14),
+       Component(22, 17)},
+      {"mailto:addr1%2caddr2", "mailto:addr1%2caddr2", true, Component(7, 13),
+       Component()},
+      {"mailto:\xF0\x90\x8C\x80", "mailto:%F0%90%8C%80", true, Component(7, 12),
+       Component()},
+      // Invalid -- UTF-8 encoded surrogate value.
+      {"mailto:\xed\xa0\x80", "mailto:%EF%BF%BD%EF%BF%BD%EF%BF%BD", false,
+       Component(7, 27), Component()},
+      {"mailto:addr1?", "mailto:addr1?", true, Component(7, 5),
+       Component(13, 0)},
+      // Certain characters have special meanings and must be encoded.
+      {"mailto:! \x22$&()+,-./09:;<=>@AZ[\\]&_`az{|}~\x7f?Query! "
+       "\x22$&()+,-./09:;<=>@AZ[\\]&_`az{|}~",
+       "mailto:!%20%22$&()+,-./"
+       "09:;%3C=%3E@AZ[\\]&_%60az%7B%7C%7D~%7F?Query!%20%22$&()+,-./"
+       "09:;%3C=%3E@AZ[\\]&_`az{|}~",
+       true, Component(7, 53), Component(61, 47)},
+  });
 
   // Define outside of loop to catch bugs where components aren't reset
-  Parsed parsed;
   Parsed out_parsed;
 
   for (size_t i = 0; i < std::size(cases); i++) {
-    int url_len = static_cast<int>(strlen(cases[i].input));
+    size_t url_len = strlen(cases[i].input);
     if (i == 0) {
       // The first test case purposely has a '\0' in it -- don't count it
       // as the string terminator.
       url_len = 22;
     }
-    ParseMailtoURL(cases[i].input, url_len, &parsed);
 
+    std::string_view input(cases[i].input, url_len);
     std::string out_str;
     StdStringCanonOutput output(&out_str);
-    bool success = CanonicalizeMailtoURL(cases[i].input, url_len, parsed,
-                                         &output, &out_parsed);
+    bool success = CanonicalizeMailtoUrl(input, ParseMailtoUrl(input), &output,
+                                         &out_parsed);
     output.Complete();
 
     EXPECT_EQ(cases[i].expected_success, success);
@@ -2346,7 +2546,7 @@
 
 #ifndef WIN32
 
-TEST(URLCanonTest, _itoa_s) {
+TEST_F(URLCanonTest, _itoa_s) {
   // We fill the buffer with 0xff to ensure that it's getting properly
   // null-terminated. We also allocate one byte more than what we tell
   // _itoa_s about, and ensure that the extra byte is untouched.
@@ -2385,40 +2585,6 @@
   EXPECT_EQ('\xFF', buf[5]);
 }
 
-TEST(URLCanonTest, _itow_s) {
-  // We fill the buffer with 0xff to ensure that it's getting properly
-  // null-terminated. We also allocate one byte more than what we tell
-  // _itoa_s about, and ensure that the extra byte is untouched.
-  char16_t buf[6];
-  const char fill_mem = 0xff;
-  const char16_t fill_char = 0xffff;
-  memset(buf, fill_mem, sizeof(buf));
-  EXPECT_EQ(0, _itow_s(12, buf, sizeof(buf) / 2 - 1, 10));
-  EXPECT_EQ(u"12", std::u16string(buf));
-  EXPECT_EQ(fill_char, buf[3]);
-
-  // Test the edge cases - exactly the buffer size and one over
-  EXPECT_EQ(0, _itow_s(1234, buf, sizeof(buf) / 2 - 1, 10));
-  EXPECT_EQ(u"1234", std::u16string(buf));
-  EXPECT_EQ(fill_char, buf[5]);
-
-  memset(buf, fill_mem, sizeof(buf));
-  EXPECT_EQ(EINVAL, _itow_s(12345, buf, sizeof(buf) / 2 - 1, 10));
-  EXPECT_EQ(fill_char, buf[5]);  // should never write to this location
-
-  // Test the template overload (note that this will see the full buffer)
-  memset(buf, fill_mem, sizeof(buf));
-  EXPECT_EQ(0, _itow_s(12, buf, 10));
-  EXPECT_EQ(u"12", std::u16string(buf));
-  EXPECT_EQ(fill_char, buf[3]);
-
-  memset(buf, fill_mem, sizeof(buf));
-  EXPECT_EQ(0, _itow_s(12345, buf, 10));
-  EXPECT_EQ(u"12345", std::u16string(buf));
-
-  EXPECT_EQ(EINVAL, _itow_s(123456, buf, 10));
-}
-
 #endif  // !WIN32
 
 // Returns true if the given two structures are the same.
@@ -2433,7 +2599,7 @@
          a.ref.begin == b.ref.begin && a.ref.len == b.ref.len;
 }
 
-TEST(URLCanonTest, ResolveRelativeURL) {
+TEST_F(URLCanonTest, ResolveRelativeUrl) {
   struct RelativeCase {
     const char* base;      // Input base URL: MUST BE CANONICAL
     bool is_base_hier;     // Is the base URL hierarchical
@@ -2649,21 +2815,19 @@
 
   for (const auto& cur_case : rel_cases) {
     Parsed parsed;
-    int base_len = static_cast<int>(strlen(cur_case.base));
     if (cur_case.is_base_file)
-      ParseFileURL(cur_case.base, base_len, &parsed);
+      parsed = ParseFileUrl(cur_case.base);
     else if (cur_case.is_base_hier)
-      ParseStandardURL(cur_case.base, base_len, &parsed);
+      parsed = ParseStandardUrl(cur_case.base);
     else
-      ParsePathURL(cur_case.base, base_len, false, &parsed);
+      parsed = ParsePathUrl(cur_case.base, false);
 
     // First see if it is relative.
-    int test_len = static_cast<int>(strlen(cur_case.test));
     bool is_relative;
     Component relative_component;
-    bool succeed_is_rel = IsRelativeURL(
-        cur_case.base, parsed, cur_case.test, test_len, cur_case.is_base_hier,
-        &is_relative, &relative_component);
+    bool succeed_is_rel =
+        IsRelativeUrl(cur_case.base, parsed, cur_case.test,
+                      cur_case.is_base_hier, &is_relative, &relative_component);
 
     EXPECT_EQ(cur_case.succeed_relative, succeed_is_rel) <<
         "succeed is rel failure on " << cur_case.test;
@@ -2675,7 +2839,7 @@
       StdStringCanonOutput output(&resolved);
       Parsed resolved_parsed;
 
-      bool succeed_resolve = ResolveRelativeURL(
+      bool succeed_resolve = ResolveRelativeUrl(
           cur_case.base, parsed, cur_case.is_base_file, cur_case.test,
           relative_component, nullptr, &output, &resolved_parsed);
       output.Complete();
@@ -2686,28 +2850,34 @@
       // Verify that the output parsed structure is the same as parsing a
       // the URL freshly.
       Parsed ref_parsed;
-      int resolved_len = static_cast<int>(resolved.size());
       if (cur_case.is_base_file) {
-        ParseFileURL(resolved.c_str(), resolved_len, &ref_parsed);
+        ref_parsed = ParseFileUrl(resolved);
       } else if (cur_case.is_base_hier) {
-        ParseStandardURL(resolved.c_str(), resolved_len, &ref_parsed);
+        ref_parsed = ParseStandardUrl(resolved);
       } else {
-        ParsePathURL(resolved.c_str(), resolved_len, false, &ref_parsed);
+        ref_parsed = ParsePathUrl(resolved, false);
       }
       EXPECT_TRUE(ParsedIsEqual(ref_parsed, resolved_parsed));
     }
   }
 }
 
+TEST_F(URLCanonTest, NonSpecialResolveRelativeUrl) {
+  static constexpr ResolveRelativeURLCase cases[] = {
+      {"git://host", "path", true, true, true, true, "git://host/path"},
+  };
+  for (const auto& i : cases) {
+    TestNonSpecialResolveRelativeURL(i);
+  }
+}
+
 // It used to be the case that when we did a replacement with a long buffer of
 // UTF-16 characters, we would get invalid data in the URL. This is because the
 // buffer that it used to hold the UTF-8 data was resized, while some pointers
 // were still kept to the old buffer that was removed.
-TEST(URLCanonTest, ReplacementOverflow) {
+TEST_F(URLCanonTest, ReplacementOverflow) {
   const char src[] = "file:///C:/foo/bar";
-  int src_len = static_cast<int>(strlen(src));
-  Parsed parsed;
-  ParseFileURL(src, src_len, &parsed);
+  Parsed parsed = ParseFileUrl(src);
 
   // Override two components, the path with something short, and the query with
   // something long enough to trigger the bug.
@@ -2727,7 +2897,7 @@
   Parsed repl_parsed;
   std::string repl_str;
   StdStringCanonOutput repl_output(&repl_str);
-  ReplaceFileURL(src, parsed, repl, nullptr, &repl_output, &repl_parsed);
+  ReplaceFileUrl(src, parsed, repl, nullptr, &repl_output, &repl_parsed);
   repl_output.Complete();
 
   // Generate the expected string and check.
@@ -2737,7 +2907,7 @@
   EXPECT_TRUE(expected == repl_str);
 }
 
-TEST(URLCanonTest, DefaultPortForScheme) {
+TEST_F(URLCanonTest, DefaultPortForScheme) {
   struct TestCases {
     const char* scheme;
     const int expected_port;
@@ -2758,11 +2928,12 @@
   for (const auto& test_case : cases) {
     SCOPED_TRACE(test_case.scheme);
     EXPECT_EQ(test_case.expected_port,
-              DefaultPortForScheme(test_case.scheme, strlen(test_case.scheme)));
+              DefaultPortForScheme(std::string_view(test_case.scheme,
+                                                    strlen(test_case.scheme))));
   }
 }
 
-TEST(URLCanonTest, FindWindowsDriveLetter) {
+TEST_F(URLCanonTest, FindWindowsDriveLetter) {
   struct TestCase {
     std::string_view spec;
     int begin;
@@ -2803,7 +2974,7 @@
   }
 }
 
-TEST(URLCanonTest, IDNToASCII) {
+TEST_F(URLCanonTest, IDNToASCII) {
   RawCanonOutputW<1024> output;
 
   // Basic ASCII test.
@@ -2854,4 +3025,175 @@
   output.set_length(0);
 }
 
+void ComponentCaseMatches(bool success,
+                          std::string_view out_str,
+                          const Component& out_comp,
+                          const DualComponentCase& expected) {
+  EXPECT_EQ(success, expected.expected_success);
+  EXPECT_STREQ(out_str.data(), expected.expected);
+  EXPECT_EQ(out_comp, expected.expected_component);
+}
+
+TEST_F(URLCanonTest, OpaqueHost) {
+  DualComponentCase host_cases[] = {
+      {"", L"", "", Component(), true},
+      {"google.com", L"google.com", "google.com", Component(0, 10), true},
+      // Upper case letters should be preserved.
+      {"gooGle.com", L"gooGle.com", "gooGle.com", Component(0, 10), true},
+      {"\x41", L"\x41", "A", Component(0, 1), true},
+      {"\x61", L"\x61", "a", Component(0, 1), true},
+      // Percent encode.
+      {"\x10", L"\x10", "%10", Component(0, 3), true},
+      // A valid percent encoding should be preserved.
+      {"%41", L"%41", "%41", Component(0, 3), true},
+      // An invalid percent encoding should be preserved too.
+      {"%zz", L"%zz", "%zz", Component(0, 3), true},
+      // UTF-16 HIRAGANA LETTER A (codepoint U+3042, "\xe3\x81\x82" in UTF-8).
+      {"\xe3\x81\x82", L"\x3042", "%E3%81%82", Component(0, 9), true},
+  };
+
+  for (const auto& host_case : host_cases) {
+    SCOPED_TRACE(testing::Message() << "url: \"" << host_case.input8 << "\"");
+    std::string_view input8(host_case.input8);
+    std::string out_str;
+    StdStringCanonOutput output(&out_str);
+    Component out_comp;
+    bool success = CanonicalizeNonSpecialHost(
+        input8, Component(0, static_cast<int>(input8.length())), output,
+        out_comp);
+    output.Complete();
+    ComponentCaseMatches(success, out_str, out_comp, host_case);
+  }
+
+  // UTF-16 version.
+  for (const auto& host_case : host_cases) {
+    SCOPED_TRACE(testing::Message() << "url: \"" << host_case.input16 << "\"");
+    std::u16string input16(
+        test_utils::TruncateWStringToUTF16(host_case.input16));
+    std::string out_str;
+    StdStringCanonOutput output(&out_str);
+    Component out_comp;
+    bool success = CanonicalizeNonSpecialHost(
+        input16, Component(0, static_cast<int>(input16.length())), output,
+        out_comp);
+    output.Complete();
+    ComponentCaseMatches(success, out_str, out_comp, host_case);
+  }
+}
+
+void IPAddressCaseMatches(std::string_view out_str,
+                          const CanonHostInfo& host_info,
+                          const IPAddressCase& expected) {
+  EXPECT_EQ(host_info.family, expected.expected_family);
+  EXPECT_STREQ(out_str.data(), expected.expected);
+  EXPECT_EQ(gurl_base::HexEncode(host_info.AddressSpan()),
+            expected.expected_address_hex);
+  if (expected.expected_family == CanonHostInfo::IPV4) {
+    EXPECT_EQ(host_info.num_ipv4_components,
+              expected.expected_num_ipv4_components);
+  }
+}
+
+TEST_F(URLCanonTest, NonSpecialHostIPv6Address) {
+  IPAddressCase ip_address_cases[] = {
+      // Non-special URLs don't support IPv4. Family must be NEUTRAL.
+      {"192.168.0.1", L"192.168.0.1", "192.168.0.1", Component(0, 11),
+       CanonHostInfo::NEUTRAL, 0, ""},
+      {"192", L"192", "192", Component(0, 3), CanonHostInfo::NEUTRAL, 0, ""},
+      // "257" is allowed since the number is not considered as a part of IPv4.
+      {"192.168.0.257", L"192.168.0.257", "192.168.0.257", Component(0, 13),
+       CanonHostInfo::NEUTRAL, 0, ""},
+      // IPv6.
+      {"[1:0:0:2::3:0]", L"[1:0:0:2::3:0]", "[1::2:0:0:3:0]", Component(0, 14),
+       CanonHostInfo::IPV6, -1, "00010000000000020000000000030000"},
+      {"[::]", L"[::]", "[::]", Component(0, 4), CanonHostInfo::IPV6, -1,
+       "00000000000000000000000000000000"},
+      // Invalid hosts.
+      {"#[::]", L"#[::]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[]", L"[]", "[]", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"a]", L"a]", "a]", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[a", L"[a", "[a", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"a[]", L"a[]", "a[]", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[]a", L"[]a", "[]a", Component(), CanonHostInfo::BROKEN, -1, ""},
+  };
+
+  for (const auto& ip_address_case : ip_address_cases) {
+    SCOPED_TRACE(testing::Message()
+                 << "url: \"" << ip_address_case.input8 << "\"");
+    std::string_view view8(ip_address_case.input8);
+    std::string out_str;
+    StdStringCanonOutput output(&out_str);
+    CanonHostInfo host_info;
+    CanonicalizeNonSpecialHostVerbose(
+        view8, Component(0, static_cast<int>(view8.length())), output,
+        host_info);
+    output.Complete();
+    IPAddressCaseMatches(out_str, host_info, ip_address_case);
+  }
+
+  // UTF-16 version.
+  for (const auto& ip_address_case : ip_address_cases) {
+    SCOPED_TRACE(testing::Message()
+                 << "url: \"" << ip_address_case.input16 << "\"");
+    std::u16string input16(
+        test_utils::TruncateWStringToUTF16(ip_address_case.input16));
+    std::string out_str;
+    StdStringCanonOutput output(&out_str);
+    CanonHostInfo host_info;
+    CanonicalizeNonSpecialHostVerbose(
+        input16, Component(0, static_cast<int>(input16.length())), output,
+        host_info);
+    output.Complete();
+    IPAddressCaseMatches(out_str, host_info, ip_address_case);
+  }
+}
+
+// Compile-time checks for StringToUint64WithBase function
+static_assert(StringToUint64WithBase("123", 10) == 123u);
+static_assert(StringToUint64WithBase("1A", 16) == 26u);
+
+// Test cases for StringToUint64WithBase function with various bases.
+TEST_F(URLCanonTest, StringToUint64WithBase) {
+  StringToUint64TestCase test_cases[] = {
+      {"1", 10, 1u},
+      {"-1", 10, 0u},
+      {"a", 10, 0u},
+      {"a", 16, 10u},
+      {"123", 10, 123u},
+      {"0", 10, 0u},
+      {"18446744073709551615", 10, 18446744073709551615ULL},  // Max uint64_t
+      {"1A", 16, 26u},
+      {"FF", 16, 255u},
+      {"FFFFFFFFFFFFFFFF", 16, 18446744073709551615ULL},  // Max uint64_t
+      {"10", 8, 8u},
+      {"77", 8, 63u},
+      {"1010", 2, 10u},
+      {"1111", 2, 15u},
+      {"1000000000000000000000000000000000000000000000000000000000000000", 2,
+       9223372036854775808ULL},  // 2^63
+      {"123Z", 10, 123u},        // Stops at 'Z'
+      {"1G", 16, 1u},            // Stops at 'G'
+      {"08", 8, 0u},             // Stops at '8'
+      {"", 10, 0u},              // Empty String
+      {" 1", 10, 0u},            // Doesn't ignore leading spaces.
+      {"+2", 10, 0u},            // Doesn't accept leading '+'.
+      {"0644", 10, 644u},        // Doesn't automatically switch to octal.
+      {"0xFF", 10, 0u},          // Doesn't automatically switch to hex.
+      {"0xFF", 16, 0u},          // Doesn't accept 0x prefix.
+      {"1e10", 10, 1u},          // Doesn't parse floating point.
+      {"eF", 16, 239u},          // Supports mixed case.
+      {"1z", 36, 71u},           // Supports base 36.
+      {"1 001", 10, 1u},         // Stops at spaces.
+      {"1,001", 10, 1u},         // Stops at commas.
+      {"1_001", 10, 1u},         // Stops at underlines.
+      {"1'001", 10, 1u},         // Stops at quote marks.
+      {"1.001", 10, 1u},         // Stops at periods.
+      {"1000000000000000F", 16, 15u}};  // Overflow is discarded.
+
+  for (const auto& test_case : test_cases) {
+    EXPECT_EQ(StringToUint64WithBase(test_case.input, test_case.base),
+              test_case.expected);
+  }
+}
+
 }  // namespace url
diff --git a/url/url_constants.cc b/url/url_constants.cc
deleted file mode 100644
index 850a31c..0000000
--- a/url/url_constants.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2014 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "url/url_constants.h"
-
-namespace url {
-
-const char kAboutBlankURL[] = "about:blank";
-const char16_t kAboutBlankURL16[] = u"about:blank";
-const char kAboutSrcdocURL[] = "about:srcdoc";
-const char16_t kAboutSrcdocURL16[] = u"about:srcdoc";
-
-const char kAboutBlankPath[] = "blank";
-const char16_t kAboutBlankPath16[] = u"blank";
-const char kAboutSrcdocPath[] = "srcdoc";
-const char16_t kAboutSrcdocPath16[] = u"srcdoc";
-
-const char kAboutScheme[] = "about";
-const char16_t kAboutScheme16[] = u"about";
-const char kBlobScheme[] = "blob";
-const char16_t kBlobScheme16[] = u"blob";
-const char kContentScheme[] = "content";
-const char16_t kContentScheme16[] = u"content";
-const char kContentIDScheme[] = "cid";
-const char16_t kContentIDScheme16[] = u"cid";
-const char kDataScheme[] = "data";
-const char16_t kDataScheme16[] = u"data";
-const char kFileScheme[] = "file";
-const char16_t kFileScheme16[] = u"file";
-const char kFileSystemScheme[] = "filesystem";
-const char16_t kFileSystemScheme16[] = u"filesystem";
-const char kFtpScheme[] = "ftp";
-const char16_t kFtpScheme16[] = u"ftp";
-const char kHttpScheme[] = "http";
-const char16_t kHttpScheme16[] = u"http";
-const char kHttpsScheme[] = "https";
-const char16_t kHttpsScheme16[] = u"https";
-const char kJavaScriptScheme[] = "javascript";
-const char16_t kJavaScriptScheme16[] = u"javascript";
-const char kMailToScheme[] = "mailto";
-const char16_t kMailToScheme16[] = u"mailto";
-const char kTelScheme[] = "tel";
-const char16_t kTelScheme16[] = u"tel";
-const char kUrnScheme[] = "urn";
-const char16_t kUrnScheme16[] = u"urn";
-const char kUuidInPackageScheme[] = "uuid-in-package";
-const char16_t kUuidInPackageScheme16[] = u"uuid-in-package";
-const char kWebcalScheme[] = "webcal";
-const char16_t kWebcalScheme16[] = u"webcal";
-const char kWsScheme[] = "ws";
-const char16_t kWsScheme16[] = u"ws";
-const char kWssScheme[] = "wss";
-const char16_t kWssScheme16[] = u"wss";
-
-const char kStandardSchemeSeparator[] = "://";
-const char16_t kStandardSchemeSeparator16[] = u"://";
-
-const size_t kMaxURLChars = 2 * 1024 * 1024;
-
-}  // namespace url
diff --git a/url/url_constants.h b/url/url_constants.h
index 0d58125..1c69603 100644
--- a/url/url_constants.h
+++ b/url/url_constants.h
@@ -7,63 +7,67 @@
 
 #include <stddef.h>
 
-#include "polyfills/base/component_export.h"
-
 namespace url {
 
-COMPONENT_EXPORT(URL) extern const char kAboutBlankURL[];
-COMPONENT_EXPORT(URL) extern const char16_t kAboutBlankURL16[];
-COMPONENT_EXPORT(URL) extern const char kAboutSrcdocURL[];
-COMPONENT_EXPORT(URL) extern const char16_t kAboutSrcdocURL16[];
+inline constexpr char kAboutBlankURL[] = "about:blank";
+inline constexpr char16_t kAboutBlankURL16[] = u"about:blank";
+inline constexpr char kAboutSrcdocURL[] = "about:srcdoc";
+inline constexpr char16_t kAboutSrcdocURL16[] = u"about:srcdoc";
 
-COMPONENT_EXPORT(URL) extern const char kAboutBlankPath[];
-COMPONENT_EXPORT(URL) extern const char16_t kAboutBlankPath16[];
-COMPONENT_EXPORT(URL) extern const char kAboutSrcdocPath[];
-COMPONENT_EXPORT(URL) extern const char16_t kAboutSrcdocPath16[];
+inline constexpr char kAboutBlankPath[] = "blank";
+inline constexpr char16_t kAboutBlankPath16[] = u"blank";
+inline constexpr char kAboutSrcdocPath[] = "srcdoc";
+inline constexpr char16_t kAboutSrcdocPath16[] = u"srcdoc";
 
-COMPONENT_EXPORT(URL) extern const char kAboutScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kAboutScheme16[];
-COMPONENT_EXPORT(URL) extern const char kBlobScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kBlobScheme16[];
-// The content scheme is specific to Android for identifying a stored file.
-COMPONENT_EXPORT(URL) extern const char kContentScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kContentScheme16[];
-COMPONENT_EXPORT(URL) extern const char kContentIDScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kContentIDScheme16[];
-COMPONENT_EXPORT(URL) extern const char kDataScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kDataScheme16[];
-COMPONENT_EXPORT(URL) extern const char kFileScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kFileScheme16[];
-COMPONENT_EXPORT(URL) extern const char kFileSystemScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kFileSystemScheme16[];
-COMPONENT_EXPORT(URL) extern const char kFtpScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kFtpScheme16[];
-COMPONENT_EXPORT(URL) extern const char kHttpScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kHttpScheme16[];
-COMPONENT_EXPORT(URL) extern const char kHttpsScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kHttpsScheme16[];
-COMPONENT_EXPORT(URL) extern const char kJavaScriptScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kJavaScriptScheme16[];
-COMPONENT_EXPORT(URL) extern const char kMailToScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kMailToScheme16[];
-COMPONENT_EXPORT(URL) extern const char kTelScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kTelScheme16[];
-COMPONENT_EXPORT(URL) extern const char kUrnScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kUrnScheme16[];
-COMPONENT_EXPORT(URL) extern const char kUuidInPackageScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kUuidInPackageScheme16[];
-COMPONENT_EXPORT(URL) extern const char kWebcalScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kWebcalScheme16[];
-COMPONENT_EXPORT(URL) extern const char kWsScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kWsScheme16[];
-COMPONENT_EXPORT(URL) extern const char kWssScheme[];
-COMPONENT_EXPORT(URL) extern const char16_t kWssScheme16[];
+inline constexpr char kAboutScheme[] = "about";
+inline constexpr char16_t kAboutScheme16[] = u"about";
+inline constexpr char kAndroidScheme[] = "android";
+inline constexpr char kBlobScheme[] = "blob";
+inline constexpr char16_t kBlobScheme16[] = u"blob";
+inline constexpr char kChromeosSteamScheme[] = "chromeos-steam";
+inline constexpr char kContentScheme[] = "content";
+inline constexpr char16_t kContentScheme16[] = u"content";
+inline constexpr char kContentIDScheme[] = "cid";
+inline constexpr char16_t kContentIDScheme16[] = u"cid";
+inline constexpr char kDataScheme[] = "data";
+inline constexpr char16_t kDataScheme16[] = u"data";
+inline constexpr char kDrivefsScheme[] = "drivefs";
+inline constexpr char kFileScheme[] = "file";
+inline constexpr char16_t kFileScheme16[] = u"file";
+inline constexpr char kFileSystemScheme[] = "filesystem";
+inline constexpr char16_t kFileSystemScheme16[] = u"filesystem";
+inline constexpr char kFtpScheme[] = "ftp";
+inline constexpr char16_t kFtpScheme16[] = u"ftp";
+inline constexpr char kHttpScheme[] = "http";
+inline constexpr char16_t kHttpScheme16[] = u"http";
+inline constexpr char kHttpsScheme[] = "https";
+inline constexpr char16_t kHttpsScheme16[] = u"https";
+inline constexpr char kJavaScriptScheme[] = "javascript";
+inline constexpr char16_t kJavaScriptScheme16[] = u"javascript";
+inline constexpr char kMailToScheme[] = "mailto";
+inline constexpr char16_t kMailToScheme16[] = u"mailto";
+inline constexpr char kMaterializedViewScheme[] = "materialized-view";
+inline constexpr char kSteamScheme[] = "steam";
+inline constexpr char kTelScheme[] = "tel";
+inline constexpr char16_t kTelScheme16[] = u"tel";
+inline constexpr char kUrnScheme[] = "urn";
+inline constexpr char16_t kUrnScheme16[] = u"urn";
+inline constexpr char kUuidInPackageScheme[] = "uuid-in-package";
+inline constexpr char16_t kUuidInPackageScheme16[] = u"uuid-in-package";
+inline constexpr char kWebcalScheme[] = "webcal";
+inline constexpr char16_t kWebcalScheme16[] = u"webcal";
+inline constexpr char kWsScheme[] = "ws";
+inline constexpr char16_t kWsScheme16[] = u"ws";
+inline constexpr char kWssScheme[] = "wss";
+inline constexpr char16_t kWssScheme16[] = u"wss";
 
 // Used to separate a standard scheme and the hostname: "://".
-COMPONENT_EXPORT(URL) extern const char kStandardSchemeSeparator[];
-COMPONENT_EXPORT(URL) extern const char16_t kStandardSchemeSeparator16[];
+inline constexpr char kStandardSchemeSeparator[] = "://";
+inline constexpr char16_t kStandardSchemeSeparator16[] = u"://";
 
-COMPONENT_EXPORT(URL) extern const size_t kMaxURLChars;
+// Max GURL length passed between processes. See url::mojom::kMaxURLChars, which
+// has the same value, for more details.
+inline constexpr size_t kMaxURLChars = 2 * 1024 * 1024;
 
 }  // namespace url
 
diff --git a/url/url_features.cc b/url/url_features.cc
index b649c86..8600f19 100644
--- a/url/url_features.cc
+++ b/url/url_features.cc
@@ -7,65 +7,29 @@
 
 namespace url {
 
-BASE_FEATURE(kUseIDNA2008NonTransitional,
-             "UseIDNA2008NonTransitional",
-             gurl_base::FEATURE_ENABLED_BY_DEFAULT);
-
-// Kill switch for crbug.com/1362507.
-BASE_FEATURE(kRecordIDNA2008Metrics,
-             "RecordIDNA2008Metrics",
-             gurl_base::FEATURE_ENABLED_BY_DEFAULT);
-
-// Kill switch for crbug.com/1220361.
-BASE_FEATURE(kResolveBareFragmentWithColonOnNonHierarchical,
-             "ResolveBareFragmentWithColonOnNonHierarchical",
-             gurl_base::FEATURE_ENABLED_BY_DEFAULT);
-
-// Kill switch for https://crbug.com/1416013.
-BASE_FEATURE(kStandardCompliantHostCharacters,
-             "StandardCompliantHostCharacters",
-             gurl_base::FEATURE_ENABLED_BY_DEFAULT);
-
-// Kill switch for crbug.com/1416006.
-BASE_FEATURE(kStandardCompliantNonSpecialSchemeURLParsing,
-             "StandardCompliantNonSpecialSchemeURLParsing",
+BASE_FEATURE(kDisallowSpaceCharacterInURLHostParsing,
              gurl_base::FEATURE_DISABLED_BY_DEFAULT);
 
-bool IsUsingIDNA2008NonTransitional() {
+bool IsDisallowingSpaceCharacterInURLHostParsing() {
   // If the FeatureList isn't available yet, fall back to the feature's default
   // state. This may happen during early startup, see crbug.com/1441956.
   if (!gurl_base::FeatureList::GetInstance()) {
-    return kUseIDNA2008NonTransitional.default_state ==
+    return kDisallowSpaceCharacterInURLHostParsing.default_state ==
            gurl_base::FEATURE_ENABLED_BY_DEFAULT;
   }
-
-  return gurl_base::FeatureList::IsEnabled(kUseIDNA2008NonTransitional);
+  return gurl_base::FeatureList::IsEnabled(kDisallowSpaceCharacterInURLHostParsing);
 }
 
-bool IsUsingStandardCompliantHostCharacters() {
+BASE_FEATURE(kUseIDNAContextJRules, gurl_base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool IsUsingIDNAContextJRules() {
   // If the FeatureList isn't available yet, fall back to the feature's default
   // state. This may happen during early startup, see crbug.com/1441956.
   if (!gurl_base::FeatureList::GetInstance()) {
-    return kStandardCompliantHostCharacters.default_state ==
+    return kUseIDNAContextJRules.default_state ==
            gurl_base::FEATURE_ENABLED_BY_DEFAULT;
   }
-
-  return gurl_base::FeatureList::IsEnabled(kStandardCompliantHostCharacters);
-}
-
-bool IsUsingStandardCompliantNonSpecialSchemeURLParsing() {
-  // If the FeatureList isn't available yet, fall back to the feature's default
-  // state. This may happen during early startup, see crbug.com/1441956.
-  if (!gurl_base::FeatureList::GetInstance()) {
-    return kStandardCompliantNonSpecialSchemeURLParsing.default_state ==
-           gurl_base::FEATURE_ENABLED_BY_DEFAULT;
-  }
-  return gurl_base::FeatureList::IsEnabled(
-      kStandardCompliantNonSpecialSchemeURLParsing);
-}
-
-bool IsRecordingIDNA2008Metrics() {
-  return gurl_base::FeatureList::IsEnabled(kRecordIDNA2008Metrics);
+  return gurl_base::FeatureList::IsEnabled(kUseIDNAContextJRules);
 }
 
 }  // namespace url
diff --git a/url/url_features.h b/url/url_features.h
index ca52b80..d2e38ca 100644
--- a/url/url_features.h
+++ b/url/url_features.h
@@ -10,37 +10,24 @@
 
 namespace url {
 
-COMPONENT_EXPORT(URL) BASE_DECLARE_FEATURE(kUseIDNA2008NonTransitional);
+// If you add or remove a feature related to URLs, you may need to
+// correspondingly update the EarlyAccess allow list in app shims/
+// (chrome/app_shim/app_shim_controller.mm). See https://crbug.com/1520386 for
+// more details.
 
-// Returns true if Chrome is using IDNA 2008 in Non-Transitional mode.
-COMPONENT_EXPORT(URL) bool IsUsingIDNA2008NonTransitional();
+// Returns true if space characters should be treated as invalid in URL host
+// parsing.
+COMPONENT_EXPORT(URL) bool IsDisallowingSpaceCharacterInURLHostParsing();
 
-// Returns true if Chrome is recording IDNA 2008 related metrics.
-COMPONENT_EXPORT(URL) bool IsRecordingIDNA2008Metrics();
-
-// Returns true if IsUsingStandardCompliantHostCharacters feature is enabled.
-// See url::kStandardCompliantHostCharacters for details.
-COMPONENT_EXPORT(URL) bool IsUsingStandardCompliantHostCharacters();
-
-// Returns true if kStandardCompliantNonSpecialSchemeURLParsing feature is
-// enabled. See url::kStandardCompliantNonSpecialSchemeURLParsing for details.
-COMPONENT_EXPORT(URL) bool IsUsingStandardCompliantNonSpecialSchemeURLParsing();
-
-// When enabled, allows resolving of a bare fragment containing a colon against
-// a non-hierarchical URL. (For example '#foo:bar' against 'about:blank'.)
+// When enabled, treat space characters as invalid in URL host parsing.
 COMPONENT_EXPORT(URL)
-BASE_DECLARE_FEATURE(kResolveBareFragmentWithColonOnNonHierarchical);
+BASE_DECLARE_FEATURE(kDisallowSpaceCharacterInURLHostParsing);
 
-// When enabled, Chrome uses URL Standard compliant mode to
-// handle punctuation characters in URL host part.
-// https://crbug.com/1416013 for details.
-COMPONENT_EXPORT(URL)
-BASE_DECLARE_FEATURE(kStandardCompliantHostCharacters);
+// Returns true if IDNA ContextJ rules are applied in URL host parsing.
+COMPONENT_EXPORT(URL) bool IsUsingIDNAContextJRules();
 
-// When enabled, Chrome uses standard-compliant URL parsing for non-special
-// scheme URLs. See https://crbug.com/1416006 for details.
-COMPONENT_EXPORT(URL)
-BASE_DECLARE_FEATURE(kStandardCompliantNonSpecialSchemeURLParsing);
+// When enabled, apply IDNA ContextJ rules in URL host parsing.
+COMPONENT_EXPORT(URL) BASE_DECLARE_FEATURE(kUseIDNAContextJRules);
 
 }  // namespace url
 
diff --git a/url/url_file.h b/url/url_file.h
index 114c46f..7c96bd2 100644
--- a/url/url_file.h
+++ b/url/url_file.h
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #ifndef URL_URL_FILE_H_
 #define URL_URL_FILE_H_
 
@@ -21,16 +26,6 @@
   return IsWindowsDriveSeparator(static_cast<char16_t>(ch));
 }
 
-// Returns the index of the next slash in the input after the given index, or
-// spec_len if the end of the input is reached.
-template<typename CHAR>
-inline int FindNextSlash(const CHAR* spec, int begin_index, int spec_len) {
-  int idx = begin_index;
-  while (idx < spec_len && !IsURLSlash(spec[idx]))
-    idx++;
-  return idx;
-}
-
 // DoesContainWindowsDriveSpecUntil returns the least number between
 // start_offset and max_offset such that the spec has a valid drive
 // specification starting at that offset. Otherwise it returns -1. This function
@@ -91,7 +86,8 @@
 
   if (strict_slashes)
     return text[start_offset] == '\\' && text[start_offset + 1] == '\\';
-  return IsURLSlash(text[start_offset]) && IsURLSlash(text[start_offset + 1]);
+  return IsSlashOrBackslash(text[start_offset]) &&
+         IsSlashOrBackslash(text[start_offset + 1]);
 }
 
 #endif  // WIN32
diff --git a/url/url_idna_icu.cc b/url/url_idna_icu.cc
index 7c1931c..f2faafc 100644
--- a/url/url_idna_icu.cc
+++ b/url/url_idna_icu.cc
@@ -11,6 +11,7 @@
 #include <ostream>
 
 #include "polyfills/base/check_op.h"
+#include "polyfills/base/notreached.h"
 #include "base/numerics/safe_conversions.h"
 #include <unicode/uidna.h>
 #include <unicode/utypes.h>
@@ -32,9 +33,8 @@
 // 1. Use the up-to-date Unicode data.
 // 2. Define a case folding/mapping with the up-to-date Unicode data as
 //    in IDNA 2003.
-// 3. If `use_idna_non_transitional` is true, use non-transitional mechanism for
-//    4 deviation characters (sharp-s, final sigma, ZWJ and ZWNJ) per
-//    url.spec.whatwg.org.
+// 3. Use non-transitional mechanism for 4 deviation characters (sharp-s, final
+//    sigma, ZWJ and ZWNJ) per url.spec.whatwg.org.
 // 4. Continue to allow symbols and punctuations.
 // 5. Apply new BiDi check rules more permissive than the IDNA 2003 BiDI rules.
 // 6. Do not apply STD3 rules
@@ -44,35 +44,26 @@
 // http://goo.gl/3XBhqw ).
 // See http://http://unicode.org/reports/tr46/ and references therein
 // for more details.
-UIDNA* CreateIDNA(bool use_idna_non_transitional) {
-  uint32_t options = UIDNA_CHECK_BIDI;
-  if (use_idna_non_transitional) {
-    // Use non-transitional processing if enabled. See
-    // https://url.spec.whatwg.org/#idna for details.
-    options |=
-        UIDNA_NONTRANSITIONAL_TO_ASCII | UIDNA_NONTRANSITIONAL_TO_UNICODE;
-  }
+UIDNA* CreateIDNA() {
+  // Enable options matching https://url.spec.whatwg.org/#idna.
+  // Note that ContextJ checks are enabled or disabled based on
+  // IsUsingIDNAContextJRules() in IDNToASCII().
+  uint32_t options = UIDNA_CHECK_BIDI | UIDNA_NONTRANSITIONAL_TO_ASCII |
+                     UIDNA_NONTRANSITIONAL_TO_UNICODE | UIDNA_CHECK_CONTEXTJ;
   UErrorCode err = U_ZERO_ERROR;
   UIDNA* idna = uidna_openUTS46(options, &err);
   if (U_FAILURE(err)) {
-    GURL_CHECK(false) << "failed to open UTS46 data with error: " << u_errorName(err)
+    GURL_NOTREACHED() << "failed to open UTS46 data with error: " << u_errorName(err)
                  << ". If you see this error message in a test environment "
                  << "your test environment likely lacks the required data "
                  << "tables for libicu. See https://crbug.com/778929.";
-    idna = nullptr;
   }
   return idna;
 }
 
 UIDNA* GetUIDNA() {
-  // This logic results in having two UIDNA instances in tests. This is okay.
-  if (IsUsingIDNA2008NonTransitional()) {
-    static UIDNA* uidna = CreateIDNA(/*use_idna_non_transitional=*/true);
-    return uidna;
-  } else {
-    static UIDNA* uidna = CreateIDNA(/*use_idna_non_transitional=*/false);
-    return uidna;
-  }
+  static UIDNA* uidna = CreateIDNA();
+  return uidna;
 }
 
 }  // namespace
@@ -112,6 +103,7 @@
     // Disable the "CheckHyphens" option in UTS #46. See
     //  - https://crbug.com/804688
     //  - https://github.com/whatwg/url/issues/267
+    //  - https://github.com/whatwg/url/issues/820 (beStrict is false)
     info.errors &= ~UIDNA_ERROR_HYPHEN_3_4;
     info.errors &= ~UIDNA_ERROR_LEADING_HYPHEN;
     info.errors &= ~UIDNA_ERROR_TRAILING_HYPHEN;
@@ -121,6 +113,11 @@
     info.errors &= ~UIDNA_ERROR_LABEL_TOO_LONG;
     info.errors &= ~UIDNA_ERROR_DOMAIN_NAME_TOO_LONG;
 
+    // Clear any ContextJ error if the feature isn't enabled.
+    if (!url::IsUsingIDNAContextJRules()) {
+      info.errors &= ~UIDNA_ERROR_CONTEXTJ;
+    }
+
     if (U_SUCCESS(err) && info.errors == 0) {
       // Per WHATWG URL, it is a failure if the ToASCII output is empty.
       //
diff --git a/url/url_parse_file.cc b/url/url_parse_file.cc
index 582f5d3..148ae4e 100644
--- a/url/url_parse_file.cc
+++ b/url/url_parse_file.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string_view>
+
 #include "polyfills/base/check.h"
 #include "url/third_party/mozilla/url_parse.h"
 #include "url/url_file.h"
@@ -42,46 +44,59 @@
 
 namespace {
 
-// A subcomponent of DoParseFileURL, the input of this function should be a UNC
+// Returns the index of the next slash in the input after the given index, or
+// `spec.size()` if the end of the input is reached.
+template <typename CharT>
+size_t FindNextSlash(std::basic_string_view<CharT> spec, size_t begin_index) {
+  size_t idx = begin_index;
+  while (idx < spec.size() && !IsSlashOrBackslash(spec[idx])) {
+    idx++;
+  }
+  return idx;
+}
+
+// A subcomponent of DoParseFileUrl, the input of this function should be a UNC
 // path name, with the index of the first character after the slashes following
-// the scheme given in |after_slashes|. This will initialize the host, path,
+// the scheme given in `after_slashes`. This will initialize the host, path,
 // query, and ref, and leave the other output components untouched
-// (DoParseFileURL handles these for us).
-template <typename CHAR>
-void DoParseUNC(const CHAR* spec,
-                int after_slashes,
-                int spec_len,
+// (DoParseFileUrl handles these for us).
+template <typename CharT>
+void DoParseUnc(std::basic_string_view<CharT> url,
+                size_t after_slashes,
                 Parsed* parsed) {
-  int next_slash = FindNextSlash(spec, after_slashes, spec_len);
+  size_t url_len = url.size();
+  // The cast is safe because `FindNextSlash` will never return anything longer
+  // than `url_len`.
+  size_t next_slash = FindNextSlash(url, after_slashes);
 
   // Everything up until that first slash we found (or end of string) is the
   // host name, which will end up being the UNC host. For example,
   // "file://foo/bar.txt" will get a server name of "foo" and a path of "/bar".
   // Later, on Windows, this should be treated as the filename "\\foo\bar.txt"
   // in proper UNC notation.
-  if (after_slashes < next_slash)
+  if (after_slashes < next_slash) {
     parsed->host = MakeRange(after_slashes, next_slash);
-  else
+  } else {
     parsed->host.reset();
-  if (next_slash < spec_len) {
-    ParsePathInternal(spec, MakeRange(next_slash, spec_len),
-                      &parsed->path, &parsed->query, &parsed->ref);
+  }
+  if (next_slash < url_len) {
+    ParsePathInternal(url.data(), MakeRange(next_slash, url_len), &parsed->path,
+                      &parsed->query, &parsed->ref);
   } else {
     parsed->path.reset();
   }
 }
 
-// A subcomponent of DoParseFileURL, the input should be a local file, with the
-// beginning of the path indicated by the index in |path_begin|. This will
+// A subcomponent of DoParseFileUrl, the input should be a local file, with the
+// beginning of the path indicated by the index in `path_begin`. This will
 // initialize the host, path, query, and ref, and leave the other output
 // components untouched (DoParseFileURL handles these for us).
-template<typename CHAR>
-void DoParseLocalFile(const CHAR* spec,
-                      int path_begin,
-                      int spec_len,
+template <typename CharT>
+void DoParseLocalFile(std::basic_string_view<CharT> url,
+                      size_t path_begin,
                       Parsed* parsed) {
   parsed->host.reset();
-  ParsePathInternal(spec, MakeRange(path_begin, spec_len),
+  ParsePathInternal(url.data(), MakeRange(path_begin, url.size()),
                     &parsed->path, &parsed->query, &parsed->ref);
 }
 
@@ -89,28 +104,17 @@
 // Handles cases where there is a scheme, but also when handed the first
 // character following the "file:" at the beginning of the spec. If so,
 // this is usually a slash, but needn't be; we allow paths like "file:c:\foo".
-template<typename CHAR>
-void DoParseFileURL(const CHAR* spec, int spec_len, Parsed* parsed) {
-  GURL_DCHECK(spec_len >= 0);
-
-  // Get the parts we never use for file URLs out of the way.
-  parsed->username.reset();
-  parsed->password.reset();
-  parsed->port.reset();
-
-  // Many of the code paths don't set these, so it's convenient to just clear
-  // them. We'll write them in those cases we need them.
-  parsed->query.reset();
-  parsed->ref.reset();
-
+template <typename CharT>
+Parsed DoParseFileUrl(std::basic_string_view<CharT> url) {
   // Strip leading & trailing spaces and control characters.
-  int begin = 0;
-  TrimURL(spec, &begin, &spec_len);
+  auto [begin, end] = TrimUrl(url);
+  url = url.substr(0, end);
 
   // Find the scheme, if any.
-  int num_slashes = CountConsecutiveSlashes(spec, begin, spec_len);
-  int after_scheme;
-  int after_slashes;
+  size_t num_slashes = CountConsecutiveSlashesOrBackslashes(url, begin);
+  size_t after_scheme;
+  size_t after_slashes;
+  Parsed parsed;
 #ifdef WIN32
   // See how many slashes there are. We want to handle cases like UNC but also
   // "/c:/foo". This is when there is no scheme, so we can allow pages to do
@@ -118,13 +122,11 @@
   // relative URL resolver when it determines there is an absolute URL, which
   // may give us input like "/c:/foo".
   after_slashes = begin + num_slashes;
-  if (DoesBeginWindowsDriveSpec(spec, after_slashes, spec_len)) {
+  if (DoesBeginWindowsDriveSpec(url.data(), after_slashes, url.length())) {
     // Windows path, don't try to extract the scheme (for example, "c:\foo").
-    parsed->scheme.reset();
     after_scheme = after_slashes;
-  } else if (DoesBeginUNCPath(spec, begin, spec_len, false)) {
+  } else if (DoesBeginUNCPath(url.data(), begin, url.length(), false)) {
     // Windows UNC path: don't try to extract the scheme, but keep the slashes.
-    parsed->scheme.reset();
     after_scheme = begin;
   } else
 #endif
@@ -133,46 +135,43 @@
     // colons in them, in which case it returns the entire spec up to the
     // colon as the scheme. So handle /foo.c:5 as a file but foo.c:5 as
     // the foo.c: scheme.
-    if (!num_slashes &&
-        ExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) {
+    if (!num_slashes && ExtractScheme(url.substr(begin), &parsed.scheme)) {
       // Offset the results since we gave ExtractScheme a substring.
-      parsed->scheme.begin += begin;
-      after_scheme = parsed->scheme.end() + 1;
+      parsed.scheme.begin += begin;
+      after_scheme = static_cast<size_t>(parsed.scheme.end() + 1);
     } else {
       // No scheme found, remember that.
-      parsed->scheme.reset();
+      parsed.scheme.reset();
       after_scheme = begin;
     }
   }
 
   // Handle empty specs ones that contain only whitespace or control chars,
   // or that are just the scheme (for example "file:").
-  if (after_scheme == spec_len) {
-    parsed->host.reset();
-    parsed->path.reset();
-    return;
+  if (after_scheme == url.length()) {
+    return parsed;
   }
 
-  num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len);
+  num_slashes = CountConsecutiveSlashesOrBackslashes(url, after_scheme);
   after_slashes = after_scheme + num_slashes;
 #ifdef WIN32
   // Check whether the input is a drive again. We checked above for windows
   // drive specs, but that's only at the very beginning to see if we have a
   // scheme at all. This test will be duplicated in that case, but will
   // additionally handle all cases with a real scheme such as "file:///C:/".
-  if (!DoesBeginWindowsDriveSpec(spec, after_slashes, spec_len) &&
+  if (!DoesBeginWindowsDriveSpec(url.data(), after_slashes, url.length()) &&
       num_slashes != 3) {
     // Anything not beginning with a drive spec ("c:\") on Windows is treated
     // as UNC, with the exception of three slashes which always means a file.
     // Even IE7 treats file:///foo/bar as "/foo/bar", which then fails.
-    DoParseUNC(spec, after_slashes, spec_len, parsed);
-    return;
+    DoParseUnc(url, after_slashes, &parsed);
+    return parsed;
   }
 #else
   // file: URL with exactly 2 slashes is considered to have a host component.
   if (num_slashes == 2) {
-    DoParseUNC(spec, after_slashes, spec_len, parsed);
-    return;
+    DoParseUnc(url, after_slashes, &parsed);
+    return parsed;
   }
 #endif  // WIN32
 
@@ -180,19 +179,20 @@
   // (modulo slashes), as in "file://c:/foo". Just treat everything from
   // there to the end as the path. Empty hosts have 0 length instead of -1.
   // We include the last slash as part of the path if there is one.
-  DoParseLocalFile(spec,
-      num_slashes > 0 ? after_scheme + num_slashes - 1 : after_scheme,
-      spec_len, parsed);
+  DoParseLocalFile(
+      url, num_slashes > 0 ? after_scheme + num_slashes - 1 : after_scheme,
+      &parsed);
+  return parsed;
 }
 
 }  // namespace
 
-void ParseFileURL(const char* url, int url_len, Parsed* parsed) {
-  DoParseFileURL(url, url_len, parsed);
+Parsed ParseFileUrl(std::string_view url) {
+  return DoParseFileUrl(url);
 }
 
-void ParseFileURL(const char16_t* url, int url_len, Parsed* parsed) {
-  DoParseFileURL(url, url_len, parsed);
+Parsed ParseFileUrl(std::u16string_view url) {
+  return DoParseFileUrl(url);
 }
 
 }  // namespace url
diff --git a/url/url_parse_internal.h b/url/url_parse_internal.h
index a73f13b..dd7377f 100644
--- a/url/url_parse_internal.h
+++ b/url/url_parse_internal.h
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #ifndef URL_URL_PARSE_INTERNAL_H_
 #define URL_URL_PARSE_INTERNAL_H_
 
@@ -11,12 +16,17 @@
 
 namespace url {
 
-// We treat slashes and backslashes the same for IE compatibility.
-inline bool IsURLSlash(char16_t ch) {
+// A helper function to handle a URL separator, which is '/' or '\'.
+//
+// The motivation: There are many condition checks in URL Standard like the
+// following:
+//
+// > If url is special and c is U+002F (/) or U+005C (\), ...
+inline bool IsSlashOrBackslash(char16_t ch) {
   return ch == '/' || ch == '\\';
 }
-inline bool IsURLSlash(char ch) {
-  return IsURLSlash(static_cast<char16_t>(ch));
+inline bool IsSlashOrBackslash(char ch) {
+  return IsSlashOrBackslash(static_cast<char16_t>(ch));
 }
 
 // Returns true if we should trim this character from the URL because it is a
@@ -28,36 +38,81 @@
   return ShouldTrimFromURL(static_cast<char16_t>(ch));
 }
 
-// Given an already-initialized begin index and length, this shrinks the range
-// to eliminate "should-be-trimmed" characters. Note that the length does *not*
-// indicate the length of untrimmed data from |*begin|, but rather the position
-// in the input string (so the string starts at character |*begin| in the spec,
-// and goes until |*len|).
-template<typename CHAR>
-inline void TrimURL(const CHAR* spec, int* begin, int* len,
-                    bool trim_path_end = true) {
+// This shrinks the input URL string to eliminate "should-be-trimmed"
+// characters. The returned value is a pair of the start index of the remaining
+// string and the start index of the trailing trimmed string in `spec`.
+template <typename CHAR>
+inline std::pair<size_t, size_t> TrimUrl(std::basic_string_view<CHAR> spec,
+                                         bool trim_path_end = true) {
+  size_t begin = 0;
+  size_t end = spec.length();
   // Strip leading whitespace and control characters.
-  while (*begin < *len && ShouldTrimFromURL(spec[*begin]))
-    (*begin)++;
+  while (begin < end && ShouldTrimFromURL(spec[begin])) {
+    ++begin;
+  }
 
   if (trim_path_end) {
-    // Strip trailing whitespace and control characters. We need the >i test
-    // for when the input string is all blanks; we don't want to back past the
-    // input.
-    while (*len > *begin && ShouldTrimFromURL(spec[*len - 1]))
-      (*len)--;
+    // Strip trailing whitespace and control characters. We need the `begin <
+    // end` test for when the input string is all blanks.
+    while (begin < end && ShouldTrimFromURL(spec[end - 1])) {
+      --end;
+    }
   }
+  return {begin, end};
+}
+
+// Counts the number of consecutive slashes or backslashes starting at the given
+// offset in the given string of the given length. A slash and backslash can be
+// mixed.
+//
+// TODO(crbug.com/40063064): Rename this function to
+// `CountConsecutiveSlashesOrBackslashes`.
+template <typename CHAR>
+inline int CountConsecutiveSlashes(const CHAR* str,
+                                   int begin_offset,
+                                   int str_len) {
+  int count = 0;
+  while (begin_offset + count < str_len &&
+         IsSlashOrBackslash(str[begin_offset + count])) {
+    ++count;
+  }
+  return count;
+}
+
+template <typename CHAR>
+inline size_t CountConsecutiveSlashesOrBackslashes(
+    std::basic_string_view<CHAR> str,
+    size_t begin_offset) {
+  size_t count = 0;
+  while (begin_offset < str.length() &&
+         IsSlashOrBackslash(str[begin_offset++])) {
+    ++count;
+  }
+  return count;
+}
+
+// Returns true if char is a slash.
+inline bool IsSlash(char16_t ch) {
+  return ch == '/';
+}
+inline bool IsSlash(char ch) {
+  return IsSlash(static_cast<char16_t>(ch));
 }
 
 // Counts the number of consecutive slashes starting at the given offset
 // in the given string of the given length.
-template<typename CHAR>
-inline int CountConsecutiveSlashes(const CHAR *str,
-                                   int begin_offset, int str_len) {
+//
+// TODO(crbug.com/40063064): Rename this function to
+// `CountConsecutiveSlashes` after the current `CountConsecutiveSlashes` is
+// renamed to CountConsecutiveSlashesOrBackslashes`.
+template <typename CHAR>
+inline int CountConsecutiveSlashesButNotCountBackslashes(const CHAR* str,
+                                                         int begin_offset,
+                                                         int str_len) {
   int count = 0;
-  while (begin_offset + count < str_len &&
-         IsURLSlash(str[begin_offset + count]))
+  while (begin_offset + count < str_len && IsSlash(str[begin_offset + count])) {
     ++count;
+  }
   return count;
 }
 
@@ -79,17 +134,31 @@
                        Component* query,
                        Component* ref);
 
+// Internal functions in url_parse.cc that parse non-special URLs, which are
+// similar to `ParseNonSpecialUrl` functions in url_parse.h, but with
+// `trim_path_end` parameter that controls whether to trim path end or not.
+Parsed ParseNonSpecialUrlInternal(std::string_view url, bool trim_path_end);
+Parsed ParseNonSpecialUrlInternal(std::u16string_view url, bool trim_path_end);
+
 // Given a spec and a pointer to the character after the colon following the
-// scheme, this parses it and fills in the structure, Every item in the parsed
-// structure is filled EXCEPT for the scheme, which is untouched.
-void ParseAfterScheme(const char* spec,
-                      int spec_len,
-                      int after_scheme,
-                      Parsed* parsed);
-void ParseAfterScheme(const char16_t* spec,
-                      int spec_len,
-                      int after_scheme,
-                      Parsed* parsed);
+// special scheme, this parses it and fills in the structure, Every item in the
+// parsed structure is filled EXCEPT for the scheme, which is untouched.
+void ParseAfterSpecialScheme(std::string_view spec,
+                             int after_scheme,
+                             Parsed* parsed);
+void ParseAfterSpecialScheme(std::u16string_view spec,
+                             int after_scheme,
+                             Parsed* parsed);
+
+// Given a spec and a pointer to the character after the colon following the
+// non-special scheme, this parses it and fills in the structure, Every item in
+// the parsed structure is filled EXCEPT for the scheme, which is untouched.
+void ParseAfterNonSpecialScheme(std::string_view spec,
+                                int after_scheme,
+                                Parsed* parsed);
+void ParseAfterNonSpecialScheme(std::u16string_view spec,
+                                int after_scheme,
+                                Parsed* parsed);
 
 }  // namespace url
 
diff --git a/url/url_parse_perftest.cc b/url/url_parse_perftest.cc
index 63bccdb..7a86761 100644
--- a/url/url_parse_perftest.cc
+++ b/url/url_parse_perftest.cc
@@ -21,7 +21,7 @@
   gurl_base::PerfTimeLogger timer("Full_URL_Parse_AMillion");
 
   for (int i = 0; i < 1000000; i++)
-    url::ParseStandardURL(kUrl.data(), kUrl.size(), &parsed);
+    parsed = url::ParseStandardUrl(kUrl);
   timer.Done();
 }
 
@@ -46,41 +46,34 @@
   // Do this 1/3 of a million times since we do 3 different URLs.
   gurl_base::PerfTimeLogger parse_timer("Typical_URL_Parse_AMillion");
   for (int i = 0; i < 333333; i++) {
-    url::ParseStandardURL(kTypicalUrl1.data(), kTypicalUrl1.size(), &parsed1);
-    url::ParseStandardURL(kTypicalUrl2.data(), kTypicalUrl2.size(), &parsed2);
-    url::ParseStandardURL(kTypicalUrl3.data(), kTypicalUrl3.size(), &parsed3);
+    parsed1 = url::ParseStandardUrl(kTypicalUrl1);
+    parsed2 = url::ParseStandardUrl(kTypicalUrl2);
+    parsed3 = url::ParseStandardUrl(kTypicalUrl3);
   }
   parse_timer.Done();
 }
 
 // Includes both parsing and canonicalization with no mallocs.
 TEST(URLParse, TypicalURLParseCanon) {
-  url::Parsed parsed1;
-  url::Parsed parsed2;
-  url::Parsed parsed3;
-
   gurl_base::PerfTimeLogger canon_timer("Typical_Parse_Canon_AMillion");
   url::Parsed out_parsed;
   url::RawCanonOutput<1024> output;
   for (int i = 0; i < 333333; i++) {  // divide by 3 so we get 1M
-    url::ParseStandardURL(kTypicalUrl1.data(), kTypicalUrl1.size(), &parsed1);
     output.set_length(0);
-    url::CanonicalizeStandardURL(
-        kTypicalUrl1.data(), kTypicalUrl1.size(), parsed1,
+    url::CanonicalizeStandardUrl(
+        kTypicalUrl1, url::ParseStandardUrl(kTypicalUrl1),
         url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr, &output,
         &out_parsed);
 
-    url::ParseStandardURL(kTypicalUrl2.data(), kTypicalUrl2.size(), &parsed2);
     output.set_length(0);
-    url::CanonicalizeStandardURL(
-        kTypicalUrl2.data(), kTypicalUrl2.size(), parsed2,
+    url::CanonicalizeStandardUrl(
+        kTypicalUrl2, url::ParseStandardUrl(kTypicalUrl2),
         url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr, &output,
         &out_parsed);
 
-    url::ParseStandardURL(kTypicalUrl3.data(), kTypicalUrl3.size(), &parsed3);
     output.set_length(0);
-    url::CanonicalizeStandardURL(
-        kTypicalUrl3.data(), kTypicalUrl3.size(), parsed3,
+    url::CanonicalizeStandardUrl(
+        kTypicalUrl3, url::ParseStandardUrl(kTypicalUrl3),
         url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr, &output,
         &out_parsed);
   }
@@ -89,34 +82,27 @@
 
 // Includes both parsing and canonicalization, and mallocs for the output.
 TEST(URLParse, TypicalURLParseCanonStdString) {
-  url::Parsed parsed1;
-  url::Parsed parsed2;
-  url::Parsed parsed3;
-
   gurl_base::PerfTimeLogger canon_timer("Typical_Parse_Canon_AMillion");
   url::Parsed out_parsed;
   for (int i = 0; i < 333333; i++) {  // divide by 3 so we get 1M
-    url::ParseStandardURL(kTypicalUrl1.data(), kTypicalUrl1.size(), &parsed1);
     std::string out1;
     url::StdStringCanonOutput output1(&out1);
-    url::CanonicalizeStandardURL(
-        kTypicalUrl1.data(), kTypicalUrl1.size(), parsed1,
+    url::CanonicalizeStandardUrl(
+        kTypicalUrl1, url::ParseStandardUrl(kTypicalUrl1),
         url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr, &output1,
         &out_parsed);
 
-    url::ParseStandardURL(kTypicalUrl2.data(), kTypicalUrl2.size(), &parsed2);
     std::string out2;
     url::StdStringCanonOutput output2(&out2);
-    url::CanonicalizeStandardURL(
-        kTypicalUrl2.data(), kTypicalUrl2.size(), parsed2,
+    url::CanonicalizeStandardUrl(
+        kTypicalUrl2, url::ParseStandardUrl(kTypicalUrl2),
         url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr, &output2,
         &out_parsed);
 
-    url::ParseStandardURL(kTypicalUrl3.data(), kTypicalUrl3.size(), &parsed3);
     std::string out3;
     url::StdStringCanonOutput output3(&out3);
-    url::CanonicalizeStandardURL(
-        kTypicalUrl3.data(), kTypicalUrl3.size(), parsed3,
+    url::CanonicalizeStandardUrl(
+        kTypicalUrl3, url::ParseStandardUrl(kTypicalUrl3),
         url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, nullptr, &output3,
         &out_parsed);
   }
diff --git a/url/url_parse_unittest.cc b/url/url_parse_unittest.cc
index d67becf..5a56df8 100644
--- a/url/url_parse_unittest.cc
+++ b/url/url_parse_unittest.cc
@@ -2,10 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
+#include "url/third_party/mozilla/url_parse.h"
+
 #include <stddef.h>
 
+#include <array>
+
 #include "testing/gtest/include/gtest/gtest.h"
-#include "url/third_party/mozilla/url_parse.h"
 
 // Interesting IE file:isms...
 //
@@ -38,8 +46,13 @@
 //      equally valid here.
 
 namespace url {
+
 namespace {
 
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
 // Used for regular URL parse cases.
 struct URLParseCase {
   const char* input;
@@ -86,26 +99,36 @@
   const char* ref;
 };
 
-bool ComponentMatches(const char* input,
-                      const char* reference,
-                      const Component& component) {
+AssertionResult ComponentMatches(const char* input,
+                                 const char* reference,
+                                 const Component& component) {
   // Check that the -1 sentinel is the only allowed negative value.
-  EXPECT_TRUE(component.is_valid() || component.len == -1);
+  if (!component.is_valid() && component.len != -1) {
+    return AssertionFailure()
+           << "-1 is the only allowed negative value for len";
+  }
 
   // Begin should be valid.
-  EXPECT_LE(0, component.begin);
+  if (component.begin < 0) {
+    return AssertionFailure() << "begin must be non-negative";
+  }
 
   // A NULL reference means the component should be nonexistent.
   if (!reference)
-    return component.len == -1;
+    return component.len == -1 ? AssertionSuccess()
+                               : AssertionFailure() << "len should be -1";
   if (!component.is_valid())
-    return false;  // Reference is not NULL but we don't have anything
+    return AssertionFailure()
+           << "for a non null reference, the component should be valid";
 
-  if (strlen(reference) != static_cast<size_t>(component.len))
-    return false;  // Lengths don't match
+  if (strlen(reference) != static_cast<size_t>(component.len)) {
+    return AssertionFailure() << "lengths do not match";
+  }
 
   // Now check the actual characters.
-  return strncmp(reference, &input[component.begin], component.len) == 0;
+  return strncmp(reference, &input[component.begin], component.len) == 0
+             ? AssertionSuccess()
+             : AssertionFailure() << "characters do not match";
 }
 
 void ExpectInvalidComponent(const Component& component) {
@@ -113,6 +136,21 @@
   EXPECT_EQ(-1, component.len);
 }
 
+void URLParseCaseMatches(const URLParseCase& expected, const Parsed& parsed) {
+  const char* url = expected.input;
+  SCOPED_TRACE(testing::Message()
+               << "url: \"" << url << "\", parsed: " << parsed);
+  int port = ParsePort(url, parsed.port);
+  EXPECT_TRUE(ComponentMatches(url, expected.scheme, parsed.scheme));
+  EXPECT_TRUE(ComponentMatches(url, expected.username, parsed.username));
+  EXPECT_TRUE(ComponentMatches(url, expected.password, parsed.password));
+  EXPECT_TRUE(ComponentMatches(url, expected.host, parsed.host));
+  EXPECT_EQ(expected.port, port);
+  EXPECT_TRUE(ComponentMatches(url, expected.path, parsed.path));
+  EXPECT_TRUE(ComponentMatches(url, expected.query, parsed.query));
+  EXPECT_TRUE(ComponentMatches(url, expected.ref, parsed.ref));
+}
+
 // Parsed ----------------------------------------------------------------------
 
 TEST(URLParser, Length) {
@@ -136,9 +174,7 @@
   };
   for (const char* length_case : length_cases) {
     int true_length = static_cast<int>(strlen(length_case));
-
-    Parsed parsed;
-    ParseStandardURL(length_case, true_length, &parsed);
+    Parsed parsed = ParseStandardUrl(length_case);
 
     EXPECT_EQ(true_length, parsed.Length());
   }
@@ -194,15 +230,9 @@
     {"file:///c:/foo", Parsed::PATH, true, 7},
   };
   for (const auto& count_case : count_cases) {
-    int length = static_cast<int>(strlen(count_case.url));
-
     // Simple test to distinguish file and standard URLs.
-    Parsed parsed;
-    if (length > 0 && count_case.url[0] == 'f') {
-      ParseFileURL(count_case.url, length, &parsed);
-    } else {
-      ParseStandardURL(count_case.url, length, &parsed);
-    }
+    Parsed parsed = count_case.url[0] == 'f' ? ParseFileUrl(count_case.url)
+                                             : ParseStandardUrl(count_case.url);
 
     int chars_before = parsed.CountCharactersBefore(
         count_case.component, count_case.include_delimiter);
@@ -313,20 +343,9 @@
 TEST(URLParser, Standard) {
   // Declared outside for loop to try to catch cases in init() where we forget
   // to reset something that is reset by the constructor.
-  Parsed parsed;
   for (const auto& i : cases) {
-    const char* url = i.input;
-    ParseStandardURL(url, static_cast<int>(strlen(url)), &parsed);
-    int port = ParsePort(url, parsed.port);
-
-    EXPECT_TRUE(ComponentMatches(url, i.scheme, parsed.scheme));
-    EXPECT_TRUE(ComponentMatches(url, i.username, parsed.username));
-    EXPECT_TRUE(ComponentMatches(url, i.password, parsed.password));
-    EXPECT_TRUE(ComponentMatches(url, i.host, parsed.host));
-    EXPECT_EQ(i.port, port);
-    EXPECT_TRUE(ComponentMatches(url, i.path, parsed.path));
-    EXPECT_TRUE(ComponentMatches(url, i.query, parsed.query));
-    EXPECT_TRUE(ComponentMatches(url, i.ref, parsed.ref));
+    Parsed parsed = ParseStandardUrl(i.input);
+    URLParseCaseMatches(i, parsed);
   }
 }
 
@@ -334,7 +353,7 @@
 
 // Various incarnations of path URLs.
 // clang-format off
-static PathURLParseCase path_cases[] = {
+auto path_cases = std::to_array<PathURLParseCase>({
 {"",                                        nullptr,       nullptr},
 {":",                                       "",            nullptr},
 {":/",                                      "",            "/"},
@@ -344,16 +363,15 @@
 {"about:blank",                             "about",       "blank"},
 {"  about: blank ",                         "about",       " blank "},
 {"javascript :alert(\"He:/l\\l#o?foo\"); ", "javascript ", "alert(\"He:/l\\l#o?foo\"); "},
-};
+});
 // clang-format on
 
-TEST(URLParser, PathURL) {
+TEST(URLParser, PathUrl) {
   // Declared outside for loop to try to catch cases in init() where we forget
   // to reset something that is reset by the constructor.
-  Parsed parsed;
   for (size_t i = 0; i < std::size(path_cases); i++) {
     const char* url = path_cases[i].input;
-    ParsePathURL(url, static_cast<int>(strlen(url)), false, &parsed);
+    Parsed parsed = ParsePathUrl(url, false);
 
     EXPECT_TRUE(ComponentMatches(url, path_cases[i].scheme, parsed.scheme))
         << i;
@@ -448,49 +466,16 @@
 };
 // clang-format on
 
-TEST(URLParser, ParseFileURL) {
+TEST(URLParser, ParseFileUrl) {
   // Declared outside for loop to try to catch cases in init() where we forget
   // to reset something that is reset by the construtor.
-  Parsed parsed;
-  for (size_t i = 0; i < std::size(file_cases); i++) {
-    const char* url = file_cases[i].input;
-    ParseFileURL(url, static_cast<int>(strlen(url)), &parsed);
-    int port = ParsePort(url, parsed.port);
-
-    EXPECT_TRUE(ComponentMatches(url, file_cases[i].scheme, parsed.scheme))
-        << " for case #" << i << " [" << url << "] "
-        << parsed.scheme.begin << ", " << parsed.scheme.len;
-
-    EXPECT_TRUE(ComponentMatches(url, file_cases[i].username, parsed.username))
-        << " for case #" << i << " [" << url << "] "
-        << parsed.username.begin << ", " << parsed.username.len;
-
-    EXPECT_TRUE(ComponentMatches(url, file_cases[i].password, parsed.password))
-        << " for case #" << i << " [" << url << "] "
-        << parsed.password.begin << ", " << parsed.password.len;
-
-    EXPECT_TRUE(ComponentMatches(url, file_cases[i].host, parsed.host))
-        << " for case #" << i << " [" << url << "] "
-        << parsed.host.begin << ", " << parsed.host.len;
-
-    EXPECT_EQ(file_cases[i].port, port)
-        << " for case #" << i << " [ " << url << "] " << port;
-
-    EXPECT_TRUE(ComponentMatches(url, file_cases[i].path, parsed.path))
-        << " for case #" << i << " [" << url << "] "
-        << parsed.path.begin << ", " << parsed.path.len;
-
-    EXPECT_TRUE(ComponentMatches(url, file_cases[i].query, parsed.query))
-        << " for case #" << i << " [" << url << "] "
-        << parsed.query.begin << ", " << parsed.query.len;
-
-    EXPECT_TRUE(ComponentMatches(url, file_cases[i].ref, parsed.ref))
-        << " for case #" << i << " [ "<< url << "] "
-        << parsed.query.begin << ", " << parsed.scheme.len;
+  for (const auto& file_case : file_cases) {
+    Parsed parsed = ParseFileUrl(file_case.input);
+    URLParseCaseMatches(file_case, parsed);
+    EXPECT_FALSE(parsed.has_opaque_path);
   }
 }
 
-
 TEST(URLParser, ExtractFileName) {
   struct FileCase {
     const char* input;
@@ -515,10 +500,7 @@
 
   for (const auto& extract_case : extract_cases) {
     const char* url = extract_case.input;
-    int len = static_cast<int>(strlen(url));
-
-    Parsed parsed;
-    ParseStandardURL(url, len, &parsed);
+    Parsed parsed = ParseStandardUrl(url);
 
     Component file_name;
     ExtractFileName(url, parsed.path, &file_name);
@@ -534,8 +516,7 @@
                            int parameter,
                            const char* expected_key,
                            const char* expected_value) {
-  Parsed parsed;
-  ParseStandardURL(url, static_cast<int>(strlen(url)), &parsed);
+  Parsed parsed = ParseStandardUrl(url);
 
   Component query = parsed.query;
 
@@ -622,16 +603,16 @@
 TEST(URLParser, MailtoUrl) {
   // Declared outside for loop to try to catch cases in init() where we forget
   // to reset something that is reset by the constructor.
-  Parsed parsed;
   for (const auto& mailto_case : mailto_cases) {
     const char* url = mailto_case.input;
-    ParseMailtoURL(url, static_cast<int>(strlen(url)), &parsed);
+    Parsed parsed = ParseMailtoUrl(url);
     int port = ParsePort(url, parsed.port);
 
     EXPECT_TRUE(ComponentMatches(url, mailto_case.scheme, parsed.scheme));
     EXPECT_TRUE(ComponentMatches(url, mailto_case.path, parsed.path));
     EXPECT_TRUE(ComponentMatches(url, mailto_case.query, parsed.query));
     EXPECT_EQ(PORT_UNSPECIFIED, port);
+    EXPECT_FALSE(parsed.has_opaque_path);
 
     // The remaining components are never used for mailto URLs.
     ExpectInvalidComponent(parsed.username);
@@ -654,15 +635,16 @@
      nullptr, nullptr, -1, "/persistent", "/bar;par/", "query", "ref"},
     {"filesystem:file:///persistent", "file", nullptr, nullptr, nullptr, -1,
      "/persistent", "", nullptr, nullptr},
+    {"filesystem:", nullptr, nullptr, nullptr, nullptr, -1, nullptr, nullptr,
+     nullptr, nullptr},
 };
 
-TEST(URLParser, FileSystemURL) {
+TEST(URLParser, FileSystemUrl) {
   // Declared outside for loop to try to catch cases in init() where we forget
   // to reset something that is reset by the constructor.
-  Parsed parsed;
   for (const auto& filesystem_case : filesystem_cases) {
     const char* url = filesystem_case.input;
-    ParseFileSystemURL(url, static_cast<int>(strlen(url)), &parsed);
+    Parsed parsed = ParseFileSystemUrl(url);
 
     EXPECT_TRUE(ComponentMatches(url, "filesystem", parsed.scheme));
     EXPECT_EQ(!filesystem_case.inner_scheme, !parsed.inner_parsed());
@@ -687,6 +669,7 @@
     EXPECT_TRUE(ComponentMatches(url, filesystem_case.path, parsed.path));
     EXPECT_TRUE(ComponentMatches(url, filesystem_case.query, parsed.query));
     EXPECT_TRUE(ComponentMatches(url, filesystem_case.ref, parsed.ref));
+    EXPECT_FALSE(parsed.has_opaque_path);
 
     // The remaining components are never used for filesystem URLs.
     ExpectInvalidComponent(parsed.username);
@@ -696,5 +679,67 @@
   }
 }
 
+// Non-special URLs which don't have an opaque path.
+static URLParseCase non_special_cases[] = {
+    {"git://user:pass@foo:21/bar;par?b#c", "git", "user", "pass", "foo", 21,
+     "/bar;par", "b", "c"},
+    {"git://host", "git", nullptr, nullptr, "host", -1, nullptr, nullptr,
+     nullptr},
+    {"git://host/a/../b", "git", nullptr, nullptr, "host", -1, "/a/../b",
+     nullptr, nullptr},
+    {"git://host/a b", "git", nullptr, nullptr, "host", -1, "/a b", nullptr,
+     nullptr},
+    {"git://ho\\st/", "git", nullptr, nullptr, "ho\\st", -1, "/", nullptr,
+     nullptr},
+    // Empty users
+    {"git://@host", "git", "", nullptr, "host", -1, nullptr, nullptr, nullptr},
+    // Empty user and invalid host. "git://@" is an invalid URL.
+    {"git://@", "git", "", nullptr, nullptr, -1, nullptr, nullptr, nullptr},
+    // Invalid host and non-empty port. "git://:80" is an invalid URL.
+    {"git://:80", "git", nullptr, nullptr, nullptr, 80, nullptr, nullptr,
+     nullptr},
+    // Empty host cases
+    {"git://", "git", nullptr, nullptr, "", -1, nullptr, nullptr, nullptr},
+    {"git:///", "git", nullptr, nullptr, "", -1, "/", nullptr, nullptr},
+    {"git:////", "git", nullptr, nullptr, "", -1, "//", nullptr, nullptr},
+    // Null host cases
+    {"git:/", "git", nullptr, nullptr, nullptr, -1, "/", nullptr, nullptr},
+    {"git:/trailing-space ", "git", nullptr, nullptr, nullptr, -1,
+     "/trailing-space", nullptr, nullptr},
+};
+
+TEST(URLParser, NonSpecial) {
+  // Declared outside for loop to try to catch cases in init() where we forget
+  // to reset something that is reset by the constructor.
+  for (const auto& i : non_special_cases) {
+    Parsed parsed = ParseNonSpecialUrl(i.input);
+    URLParseCaseMatches(i, parsed);
+    EXPECT_FALSE(parsed.has_opaque_path) << "url: " << i.input;
+  }
+}
+
+// Non-special URLs which have an opaque path.
+static URLParseCase non_special_opaque_path_cases[] = {
+    {"git:", "git", nullptr, nullptr, nullptr, -1, nullptr, nullptr, nullptr},
+    {"git:opaque", "git", nullptr, nullptr, nullptr, -1, "opaque", nullptr,
+     nullptr},
+    {"git:opaque?a=b#c", "git", nullptr, nullptr, nullptr, -1, "opaque", "a=b",
+     "c"},
+    {"git: o p a q u e ", "git", nullptr, nullptr, nullptr, -1, " o p a q u e",
+     nullptr, nullptr},
+    {"git:opa\\que", "git", nullptr, nullptr, nullptr, -1, "opa\\que", nullptr,
+     nullptr},
+};
+
+TEST(URLParser, NonSpecialOpaquePath) {
+  // Declared outside for loop to try to catch cases in init() where we forget
+  // to reset something that is reset by the constructor.
+  for (const auto& i : non_special_opaque_path_cases) {
+    Parsed parsed = ParseNonSpecialUrl(i.input);
+    URLParseCaseMatches(i, parsed);
+    EXPECT_TRUE(parsed.has_opaque_path) << "url: " << i.input;
+  }
+}
+
 }  // namespace
 }  // namespace url
diff --git a/url/url_test_utils.h b/url/url_test_utils.h
index e1be7fc..df253c7 100644
--- a/url/url_test_utils.h
+++ b/url/url_test_utils.h
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #ifndef URL_URL_TEST_UTILS_H_
 #define URL_URL_TEST_UTILS_H_
 
diff --git a/url/url_util.cc b/url/url_util.cc
index 267e4b9..aa5a1dd 100644
--- a/url/url_util.cc
+++ b/url/url_util.cc
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
 #include "url/url_util.h"
 
 #include <stddef.h>
@@ -9,6 +14,7 @@
 
 #include <atomic>
 #include <ostream>
+#include <string_view>
 
 #include "polyfills/base/check_op.h"
 #include "base/compiler_specific.h"
@@ -17,7 +23,9 @@
 #include "base/strings/string_util.h"
 #include "url/url_canon_internal.h"
 #include "url/url_constants.h"
+#include "url/url_features.h"
 #include "url/url_file.h"
+#include "url/url_parse_internal.h"
 #include "url/url_util_internal.h"
 
 namespace url {
@@ -114,6 +122,20 @@
       kAboutScheme,
   };
 
+  // Non-special schemes that should be treated as opaque path URLs for
+  // compatibility reasons.
+  std::vector<std::string> opaque_non_special_schemes = {
+      // See https://crrev.com/c/5465607 for the reason.
+      kAndroidScheme,
+      // Temporarily opted-out. See https://crrev.com/c/5569365.
+      kDrivefsScheme,
+      // Temporarily opted-out. See https://crrev.com/c/5568919.
+      kChromeosSteamScheme,
+      kSteamScheme,
+      // Temporarily opted-out. See https://crrev.com/c/5578066.
+      kMaterializedViewScheme,
+  };
+
   // Schemes with a predefined default custom handler.
   std::vector<SchemeWithHandler> predefined_handler_schemes;
 
@@ -149,31 +171,30 @@
 
 // Given a string and a range inside the string, compares it to the given
 // lower-case |compare_to| buffer.
-template<typename CHAR>
-inline bool DoCompareSchemeComponent(const CHAR* spec,
+template <typename CHAR>
+inline bool DoCompareSchemeComponent(std::basic_string_view<CHAR> spec,
                                      const Component& component,
                                      const char* compare_to) {
   if (component.is_empty())
     return compare_to[0] == 0;  // When component is empty, match empty scheme.
-  return gurl_base::EqualsCaseInsensitiveASCII(
-      std::basic_string_view(&spec[component.begin], component.len),
-      compare_to);
+  return gurl_base::EqualsCaseInsensitiveASCII(component.AsViewOn(spec), compare_to);
 }
 
 // Returns true and sets |type| to the SchemeType of the given scheme
 // identified by |scheme| within |spec| if in |schemes|.
-template<typename CHAR>
-bool DoIsInSchemes(const CHAR* spec,
-                   const Component& scheme,
+template <typename CHAR>
+bool DoIsInSchemes(std::optional<std::basic_string_view<CHAR>> input,
                    SchemeType* type,
                    const std::vector<SchemeWithType>& schemes) {
-  if (scheme.is_empty())
+  if (!input.has_value() || input->empty()) {
     return false;  // Empty or invalid schemes are non-standard.
+  }
+
+  auto input_value = input.value();
 
   for (const SchemeWithType& scheme_with_type : schemes) {
-    if (gurl_base::EqualsCaseInsensitiveASCII(
-            std::basic_string_view(&spec[scheme.begin], scheme.len),
-            scheme_with_type.scheme)) {
+    if (gurl_base::EqualsCaseInsensitiveASCII(input_value,
+                                         scheme_with_type.scheme)) {
       *type = scheme_with_type.type;
       return true;
     }
@@ -181,27 +202,38 @@
   return false;
 }
 
-template<typename CHAR>
-bool DoIsStandard(const CHAR* spec, const Component& scheme, SchemeType* type) {
-  return DoIsInSchemes(spec, scheme, type,
-                       GetSchemeRegistry().standard_schemes);
+template <typename CHAR>
+bool DoIsStandard(std::optional<std::basic_string_view<CHAR>> input,
+                  SchemeType* type) {
+  return DoIsInSchemes(input, type, GetSchemeRegistry().standard_schemes);
 }
 
+template <typename CHAR>
+bool DoIsOpaqueNonSpecial(const CHAR* spec, const Component& scheme) {
+  if (scheme.is_empty()) {
+    return false;
+  }
+  for (const std::string& s : GetSchemeRegistry().opaque_non_special_schemes) {
+    if (gurl_base::EqualsCaseInsensitiveASCII(
+            std::basic_string_view(&spec[scheme.begin], scheme.len), s)) {
+      return true;
+    }
+  }
+  return false;
+}
 
-template<typename CHAR>
-bool DoFindAndCompareScheme(const CHAR* str,
-                            int str_len,
+template <typename CHAR>
+bool DoFindAndCompareScheme(std::basic_string_view<CHAR> str,
                             const char* compare,
                             Component* found_scheme) {
   // Before extracting scheme, canonicalize the URL to remove any whitespace.
   // This matches the canonicalization done in DoCanonicalize function.
   STACK_UNINITIALIZED RawCanonOutputT<CHAR> whitespace_buffer;
-  int spec_len;
-  const CHAR* spec =
-      RemoveURLWhitespace(str, str_len, &whitespace_buffer, &spec_len, nullptr);
+  std::basic_string_view<CHAR> spec =
+      RemoveUrlWhitespace(str, &whitespace_buffer, nullptr);
 
   Component our_scheme;
-  if (!ExtractScheme(spec, spec_len, &our_scheme)) {
+  if (!ExtractScheme(spec, &our_scheme)) {
     // No scheme.
     if (found_scheme)
       *found_scheme = Component();
@@ -213,31 +245,26 @@
 }
 
 template <typename CHAR>
-bool DoCanonicalize(const CHAR* spec,
-                    int spec_len,
+bool DoCanonicalize(std::basic_string_view<CHAR> spec,
                     bool trim_path_end,
                     WhitespaceRemovalPolicy whitespace_policy,
                     CharsetConverter* charset_converter,
                     CanonOutput* output,
                     Parsed* output_parsed) {
   // Trim leading C0 control characters and spaces.
-  int begin = 0;
-  TrimURL(spec, &begin, &spec_len, trim_path_end);
-  GURL_DCHECK(0 <= begin && begin <= spec_len);
-  spec += begin;
-  spec_len -= begin;
+  auto [begin, end] = TrimUrl(spec, trim_path_end);
+  spec = spec.substr(begin, end - begin);
 
-  output->ReserveSizeIfNeeded(spec_len);
+  output->ReserveSizeIfNeeded(spec.length());
 
   // Remove any whitespace from the middle of the relative URL if necessary.
   // Possibly this will result in copying to the new buffer.
   STACK_UNINITIALIZED RawCanonOutputT<CHAR> whitespace_buffer;
   if (whitespace_policy == REMOVE_WHITESPACE) {
-    spec = RemoveURLWhitespace(spec, spec_len, &whitespace_buffer, &spec_len,
+    spec = RemoveUrlWhitespace(spec, &whitespace_buffer,
                                &output_parsed->potentially_dangling_markup);
   }
 
-  Parsed parsed_input;
 #ifdef WIN32
   // For Windows, we allow things that look like absolute Windows paths to be
   // fixed up magically to file URLs. This is done for IE compatibility. For
@@ -249,17 +276,17 @@
   // has no meaning as an absolute path name. This is because browsers on Mac
   // & Unix don't generally do this, so there is no compatibility reason for
   // doing so.
-  if (DoesBeginUNCPath(spec, 0, spec_len, false) ||
-      DoesBeginWindowsDriveSpec(spec, 0, spec_len)) {
-    ParseFileURL(spec, spec_len, &parsed_input);
-    return CanonicalizeFileURL(spec, spec_len, parsed_input, charset_converter,
+  if (DoesBeginUNCPath(spec.data(), 0, spec.length(), false) ||
+      DoesBeginWindowsDriveSpec(spec.data(), 0, spec.length())) {
+    return CanonicalizeFileUrl(spec, ParseFileUrl(spec), charset_converter,
                                output, output_parsed);
   }
 #endif
 
   Component scheme;
-  if (!ExtractScheme(spec, spec_len, &scheme))
+  if (!ExtractScheme(spec, &scheme)) {
     return false;
+  }
 
   // This is the parsed version of the input URL, we have to canonicalize it
   // before storing it in our object.
@@ -267,75 +294,64 @@
   SchemeType scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
   if (DoCompareSchemeComponent(spec, scheme, url::kFileScheme)) {
     // File URLs are special.
-    ParseFileURL(spec, spec_len, &parsed_input);
-    success = CanonicalizeFileURL(spec, spec_len, parsed_input,
-                                  charset_converter, output, output_parsed);
+    success = CanonicalizeFileUrl(spec, ParseFileUrl(spec), charset_converter,
+                                  output, output_parsed);
   } else if (DoCompareSchemeComponent(spec, scheme, url::kFileSystemScheme)) {
     // Filesystem URLs are special.
-    ParseFileSystemURL(spec, spec_len, &parsed_input);
-    success = CanonicalizeFileSystemURL(spec, spec_len, parsed_input,
-                                        charset_converter, output,
-                                        output_parsed);
+    success =
+        CanonicalizeFileSystemUrl(spec, ParseFileSystemUrl(spec),
+                                  charset_converter, output, output_parsed);
 
-  } else if (DoIsStandard(spec, scheme, &scheme_type)) {
+  } else if (DoIsStandard(std::optional(scheme.AsViewOn(spec)), &scheme_type)) {
     // All "normal" URLs.
-    ParseStandardURL(spec, spec_len, &parsed_input);
-    success = CanonicalizeStandardURL(spec, spec_len, parsed_input, scheme_type,
+    success = CanonicalizeStandardUrl(spec, ParseStandardUrl(spec), scheme_type,
                                       charset_converter, output, output_parsed);
 
-  } else if (DoCompareSchemeComponent(spec, scheme, url::kMailToScheme)) {
-    // Mailto URLs are treated like standard URLs, with only a scheme, path,
-    // and query.
-    ParseMailtoURL(spec, spec_len, &parsed_input);
-    success = CanonicalizeMailtoURL(spec, spec_len, parsed_input, output,
-                                    output_parsed);
-
   } else {
-    // "Weird" URLs like data: and javascript:.
-    ParsePathURL(spec, spec_len, trim_path_end, &parsed_input);
-    success = CanonicalizePathURL(spec, spec_len, parsed_input, output,
-                                  output_parsed);
+    // Non-special scheme URLs like data:, mailto: and javascript:.
+    if (!DoIsOpaqueNonSpecial(spec.data(), scheme)) {
+      success = CanonicalizeNonSpecialUrl(
+          spec, ParseNonSpecialUrlInternal(spec, trim_path_end),
+          charset_converter, *output, *output_parsed);
+    } else {
+      success = CanonicalizePathUrl(spec, ParsePathUrl(spec, trim_path_end),
+                                    output, output_parsed);
+    }
   }
   return success;
 }
 
-template<typename CHAR>
-bool DoResolveRelative(const char* base_spec,
-                       int base_spec_len,
+template <typename CHAR>
+bool DoResolveRelative(std::string_view base_spec,
                        const Parsed& base_parsed,
-                       const CHAR* in_relative,
-                       int in_relative_length,
+                       std::basic_string_view<CHAR> in_relative,
                        CharsetConverter* charset_converter,
                        CanonOutput* output,
                        Parsed* output_parsed) {
   // Remove any whitespace from the middle of the relative URL, possibly
   // copying to the new buffer.
   STACK_UNINITIALIZED RawCanonOutputT<CHAR> whitespace_buffer;
-  int relative_length;
-  const CHAR* relative = RemoveURLWhitespace(
-      in_relative, in_relative_length, &whitespace_buffer, &relative_length,
-      &output_parsed->potentially_dangling_markup);
+  std::basic_string_view<CHAR> relative =
+      RemoveUrlWhitespace(in_relative, &whitespace_buffer,
+                          &output_parsed->potentially_dangling_markup);
 
   bool base_is_authority_based = false;
   bool base_is_hierarchical = false;
-  if (base_spec &&
-      base_parsed.scheme.is_nonempty()) {
-    int after_scheme = base_parsed.scheme.end() + 1;  // Skip past the colon.
-    int num_slashes = CountConsecutiveSlashes(base_spec, after_scheme,
-                                              base_spec_len);
+  if (base_spec.data() && base_parsed.scheme.is_nonempty()) {
+    size_t after_scheme = base_parsed.scheme.end() + 1;  // Skip past the colon.
+    size_t num_slashes =
+        CountConsecutiveSlashesOrBackslashes(base_spec, after_scheme);
     base_is_authority_based = num_slashes > 1;
     base_is_hierarchical = num_slashes > 0;
   }
 
-  SchemeType unused_scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
-  bool standard_base_scheme =
-      base_parsed.scheme.is_nonempty() &&
-      DoIsStandard(base_spec, base_parsed.scheme, &unused_scheme_type);
+  bool is_hierarchical_base =
+      base_parsed.scheme.is_nonempty() && !base_parsed.has_opaque_path;
 
   bool is_relative;
   Component relative_component;
-  if (!IsRelativeURL(base_spec, base_parsed, relative, relative_length,
-                     (base_is_hierarchical || standard_base_scheme),
+  if (!IsRelativeUrl(base_spec, base_parsed, relative,
+                     (base_is_hierarchical || is_hierarchical_base),
                      &is_relative, &relative_component)) {
     // Error resolving.
     return false;
@@ -347,41 +363,37 @@
   // Pretend for a moment that |base_spec| is a standard URL. Normally
   // non-standard URLs are treated as PathURLs, but if the base has an
   // authority we would like to preserve it.
-  if (is_relative && base_is_authority_based && !standard_base_scheme) {
-    Parsed base_parsed_authority;
-    ParseStandardURL(base_spec, base_spec_len, &base_parsed_authority);
+  if (is_relative && base_is_authority_based && !is_hierarchical_base) {
+    Parsed base_parsed_authority = ParseStandardUrl(base_spec);
     if (base_parsed_authority.host.is_nonempty()) {
       STACK_UNINITIALIZED RawCanonOutputT<char> temporary_output;
-      bool did_resolve_succeed =
-          ResolveRelativeURL(base_spec, base_parsed_authority, false, relative,
-                             relative_component, charset_converter,
-                             &temporary_output, output_parsed);
+      bool did_resolve_succeed = ResolveRelativeUrl(
+          base_spec, base_parsed_authority, false, relative, relative_component,
+          charset_converter, &temporary_output, output_parsed);
       // The output_parsed is incorrect at this point (because it was built
       // based on base_parsed_authority instead of base_parsed) and needs to be
       // re-created.
-      DoCanonicalize(temporary_output.data(), temporary_output.length(), true,
-                     REMOVE_WHITESPACE, charset_converter, output,
-                     output_parsed);
+      DoCanonicalize(temporary_output.view(), true, REMOVE_WHITESPACE,
+                     charset_converter, output, output_parsed);
       return did_resolve_succeed;
     }
   } else if (is_relative) {
     // Relative, resolve and canonicalize.
-    bool file_base_scheme = base_parsed.scheme.is_nonempty() &&
+    bool file_base_scheme =
+        base_parsed.scheme.is_nonempty() &&
         DoCompareSchemeComponent(base_spec, base_parsed.scheme, kFileScheme);
-    return ResolveRelativeURL(base_spec, base_parsed, file_base_scheme, relative,
-                              relative_component, charset_converter, output,
-                              output_parsed);
+    return ResolveRelativeUrl(base_spec, base_parsed, file_base_scheme,
+                              relative, relative_component, charset_converter,
+                              output, output_parsed);
   }
 
   // Not relative, canonicalize the input.
-  return DoCanonicalize(relative, relative_length, true,
-                        DO_NOT_REMOVE_WHITESPACE, charset_converter, output,
-                        output_parsed);
+  return DoCanonicalize(relative, true, DO_NOT_REMOVE_WHITESPACE,
+                        charset_converter, output, output_parsed);
 }
 
-template<typename CHAR>
-bool DoReplaceComponents(const char* spec,
-                         int spec_len,
+template <typename CHAR>
+bool DoReplaceComponents(std::string_view spec,
                          const Parsed& parsed,
                          const Replacements<CHAR>& replacements,
                          CharsetConverter* charset_converter,
@@ -405,25 +417,25 @@
     // the existing spec.
     STACK_UNINITIALIZED RawCanonOutput<128> scheme_replaced;
     Component scheme_replaced_parsed;
-    CanonicalizeScheme(replacements.sources().scheme,
-                       replacements.components().scheme,
+    CanonicalizeScheme(replacements.components().scheme.as_string_view_on(
+                           replacements.sources().scheme),
                        &scheme_replaced, &scheme_replaced_parsed);
 
     // We can assume that the input is canonicalized, which means it always has
     // a colon after the scheme (or where the scheme would be).
-    int spec_after_colon = parsed.scheme.is_valid() ? parsed.scheme.end() + 1
-                                                    : 1;
-    if (spec_len - spec_after_colon > 0) {
+    size_t spec_after_colon =
+        parsed.scheme.is_valid() ? parsed.scheme.end() + 1 : 1;
+    if (spec.length() > spec_after_colon) {
       scheme_replaced.Append(&spec[spec_after_colon],
-                             spec_len - spec_after_colon);
+                             spec.length() - spec_after_colon);
     }
 
     // We now need to completely re-parse the resulting string since its meaning
     // may have changed with the different scheme.
     STACK_UNINITIALIZED RawCanonOutput<128> recanonicalized;
     Parsed recanonicalized_parsed;
-    DoCanonicalize(scheme_replaced.data(), scheme_replaced.length(), true,
-                   REMOVE_WHITESPACE, charset_converter, &recanonicalized,
+    DoCanonicalize(scheme_replaced.view(), true, REMOVE_WHITESPACE,
+                   charset_converter, &recanonicalized,
                    &recanonicalized_parsed);
 
     // Recurse using the version with the scheme already replaced. This will now
@@ -448,38 +460,40 @@
     if (parsed.potentially_dangling_markup) {
       out_parsed->potentially_dangling_markup = true;
     }
-    return DoReplaceComponents(recanonicalized.data(), recanonicalized.length(),
-                               recanonicalized_parsed, replacements_no_scheme,
-                               charset_converter, output, out_parsed);
+    return DoReplaceComponents(recanonicalized.view(), recanonicalized_parsed,
+                               replacements_no_scheme, charset_converter,
+                               output, out_parsed);
   }
 
   // TODO(csharrison): We could be smarter about size to reserve if this is done
   // in callers below, and the code checks to see which components are being
   // replaced, and with what length. If this ends up being a hot spot it should
   // be changed.
-  output->ReserveSizeIfNeeded(spec_len);
+  output->ReserveSizeIfNeeded(spec.length());
 
   // If we get here, then we know the scheme doesn't need to be replaced, so can
   // just key off the scheme in the spec to know how to do the replacements.
   if (DoCompareSchemeComponent(spec, parsed.scheme, url::kFileScheme)) {
-    return ReplaceFileURL(spec, parsed, replacements, charset_converter, output,
+    return ReplaceFileUrl(spec, parsed, replacements, charset_converter, output,
                           out_parsed);
   }
   if (DoCompareSchemeComponent(spec, parsed.scheme, url::kFileSystemScheme)) {
-    return ReplaceFileSystemURL(spec, parsed, replacements, charset_converter,
+    return ReplaceFileSystemUrl(spec, parsed, replacements, charset_converter,
                                 output, out_parsed);
   }
   SchemeType scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
-  if (DoIsStandard(spec, parsed.scheme, &scheme_type)) {
-    return ReplaceStandardURL(spec, parsed, replacements, scheme_type,
+  if (DoIsStandard(parsed.scheme.MaybeAsViewOn(spec), &scheme_type)) {
+    return ReplaceStandardUrl(spec, parsed, replacements, scheme_type,
                               charset_converter, output, out_parsed);
   }
-  if (DoCompareSchemeComponent(spec, parsed.scheme, url::kMailToScheme)) {
-    return ReplaceMailtoURL(spec, parsed, replacements, output, out_parsed);
-  }
 
-  // Default is a path URL.
-  return ReplacePathURL(spec, parsed, replacements, output, out_parsed);
+  // TODO(crbug.com/350788890): We should not use spec.data().
+  const char* spec_ptr = spec.data();
+  if (!DoIsOpaqueNonSpecial(spec_ptr, parsed.scheme)) {
+    return ReplaceNonSpecialUrl(spec, parsed, replacements, charset_converter,
+                                *output, *out_parsed);
+  }
+  return ReplacePathUrl(spec, parsed, replacements, output, out_parsed);
 }
 
 void DoSchemeModificationPreamble() {
@@ -503,36 +517,37 @@
       << "Trying to add a scheme after the lists have been locked.";
 }
 
-void DoAddSchemeWithHandler(const char* new_scheme,
-                            const char* handler,
+void DoAddSchemeWithHandler(std::string_view new_scheme,
+                            std::string_view handler,
                             std::vector<SchemeWithHandler>* schemes) {
   DoSchemeModificationPreamble();
   GURL_DCHECK(schemes);
-  GURL_DCHECK(strlen(new_scheme) > 0);
-  GURL_DCHECK(strlen(handler) > 0);
+  GURL_DCHECK(!new_scheme.empty());
+  GURL_DCHECK(!handler.empty());
   GURL_DCHECK_EQ(gurl_base::ToLowerASCII(new_scheme), new_scheme);
   GURL_DCHECK(!gurl_base::Contains(*schemes, new_scheme, &SchemeWithHandler::scheme));
-  schemes->push_back({new_scheme, handler});
+  schemes->push_back({std::string(new_scheme), std::string(handler)});
 }
 
-void DoAddScheme(const char* new_scheme, std::vector<std::string>* schemes) {
+void DoAddScheme(std::string_view new_scheme,
+                 std::vector<std::string>* schemes) {
   DoSchemeModificationPreamble();
   GURL_DCHECK(schemes);
-  GURL_DCHECK(strlen(new_scheme) > 0);
+  GURL_DCHECK(!new_scheme.empty());
   GURL_DCHECK_EQ(gurl_base::ToLowerASCII(new_scheme), new_scheme);
   GURL_DCHECK(!gurl_base::Contains(*schemes, new_scheme));
-  schemes->push_back(new_scheme);
+  schemes->push_back(std::string(new_scheme));
 }
 
-void DoAddSchemeWithType(const char* new_scheme,
+void DoAddSchemeWithType(std::string_view new_scheme,
                          SchemeType type,
                          std::vector<SchemeWithType>* schemes) {
   DoSchemeModificationPreamble();
   GURL_DCHECK(schemes);
-  GURL_DCHECK(strlen(new_scheme) > 0);
+  GURL_DCHECK(!new_scheme.empty());
   GURL_DCHECK_EQ(gurl_base::ToLowerASCII(new_scheme), new_scheme);
   GURL_DCHECK(!gurl_base::Contains(*schemes, new_scheme, &SchemeWithType::scheme));
-  schemes->push_back({new_scheme, type});
+  schemes->push_back({std::string(new_scheme), type});
 }
 
 }  // namespace
@@ -579,7 +594,7 @@
   return GetSchemeRegistry().allow_non_standard_schemes;
 }
 
-void AddStandardScheme(const char* new_scheme, SchemeType type) {
+void AddStandardScheme(std::string_view new_scheme, SchemeType type) {
   DoAddSchemeWithType(new_scheme, type,
                       &GetSchemeRegistryWithoutLocking()->standard_schemes);
 }
@@ -593,12 +608,12 @@
   return result;
 }
 
-void AddReferrerScheme(const char* new_scheme, SchemeType type) {
+void AddReferrerScheme(std::string_view new_scheme, SchemeType type) {
   DoAddSchemeWithType(new_scheme, type,
                       &GetSchemeRegistryWithoutLocking()->referrer_schemes);
 }
 
-void AddSecureScheme(const char* new_scheme) {
+void AddSecureScheme(std::string_view new_scheme) {
   DoAddScheme(new_scheme, &GetSchemeRegistryWithoutLocking()->secure_schemes);
 }
 
@@ -606,7 +621,7 @@
   return GetSchemeRegistry().secure_schemes;
 }
 
-void AddLocalScheme(const char* new_scheme) {
+void AddLocalScheme(std::string_view new_scheme) {
   DoAddScheme(new_scheme, &GetSchemeRegistryWithoutLocking()->local_schemes);
 }
 
@@ -614,7 +629,7 @@
   return GetSchemeRegistry().local_schemes;
 }
 
-void AddNoAccessScheme(const char* new_scheme) {
+void AddNoAccessScheme(std::string_view new_scheme) {
   DoAddScheme(new_scheme,
               &GetSchemeRegistryWithoutLocking()->no_access_schemes);
 }
@@ -623,7 +638,7 @@
   return GetSchemeRegistry().no_access_schemes;
 }
 
-void AddCorsEnabledScheme(const char* new_scheme) {
+void AddCorsEnabledScheme(std::string_view new_scheme) {
   DoAddScheme(new_scheme,
               &GetSchemeRegistryWithoutLocking()->cors_enabled_schemes);
 }
@@ -632,7 +647,7 @@
   return GetSchemeRegistry().cors_enabled_schemes;
 }
 
-void AddWebStorageScheme(const char* new_scheme) {
+void AddWebStorageScheme(std::string_view new_scheme) {
   DoAddScheme(new_scheme,
               &GetSchemeRegistryWithoutLocking()->web_storage_schemes);
 }
@@ -641,7 +656,7 @@
   return GetSchemeRegistry().web_storage_schemes;
 }
 
-void AddCSPBypassingScheme(const char* new_scheme) {
+void AddCSPBypassingScheme(std::string_view new_scheme) {
   DoAddScheme(new_scheme,
               &GetSchemeRegistryWithoutLocking()->csp_bypassing_schemes);
 }
@@ -650,7 +665,7 @@
   return GetSchemeRegistry().csp_bypassing_schemes;
 }
 
-void AddEmptyDocumentScheme(const char* new_scheme) {
+void AddEmptyDocumentScheme(std::string_view new_scheme) {
   DoAddScheme(new_scheme,
               &GetSchemeRegistryWithoutLocking()->empty_document_schemes);
 }
@@ -659,7 +674,8 @@
   return GetSchemeRegistry().empty_document_schemes;
 }
 
-void AddPredefinedHandlerScheme(const char* new_scheme, const char* handler) {
+void AddPredefinedHandlerScheme(std::string_view new_scheme,
+                                std::string_view handler) {
   DoAddSchemeWithHandler(
       new_scheme, handler,
       &GetSchemeRegistryWithoutLocking()->predefined_handler_schemes);
@@ -679,46 +695,54 @@
   scheme_registries_locked = true;
 }
 
+// TODO(crbug.com/351564777): Delete this after //third_party/openscreen
+// transition is complete.
 bool IsStandard(const char* spec, const Component& scheme) {
   SchemeType unused_scheme_type;
-  return DoIsStandard(spec, scheme, &unused_scheme_type);
+  return DoIsStandard(scheme.maybe_as_string_view_on(spec),
+                      &unused_scheme_type);
 }
 
-bool GetStandardSchemeType(const char* spec,
-                           const Component& scheme,
-                           SchemeType* type) {
-  return DoIsStandard(spec, scheme, type);
-}
-
-bool GetStandardSchemeType(const char16_t* spec,
-                           const Component& scheme,
-                           SchemeType* type) {
-  return DoIsStandard(spec, scheme, type);
-}
-
-bool IsStandard(const char16_t* spec, const Component& scheme) {
+bool IsStandard(std::optional<std::string_view> scheme) {
   SchemeType unused_scheme_type;
-  return DoIsStandard(spec, scheme, &unused_scheme_type);
+  return DoIsStandard(scheme, &unused_scheme_type);
 }
 
-bool IsReferrerScheme(const char* spec, const Component& scheme) {
+bool IsStandardScheme(std::string_view scheme) {
+  return IsStandard(scheme);
+}
+
+bool GetStandardSchemeType(std::optional<std::string_view> scheme,
+                           SchemeType* type) {
+  return DoIsStandard(scheme, type);
+}
+
+bool GetStandardSchemeType(std::optional<std::u16string_view> scheme,
+                           SchemeType* type) {
+  return DoIsStandard(scheme, type);
+}
+
+bool IsStandard(std::optional<std::u16string_view> scheme) {
   SchemeType unused_scheme_type;
-  return DoIsInSchemes(spec, scheme, &unused_scheme_type,
+  return DoIsStandard(scheme, &unused_scheme_type);
+}
+
+bool IsReferrerScheme(std::optional<std::string_view> scheme) {
+  SchemeType unused_scheme_type;
+  return DoIsInSchemes(scheme, &unused_scheme_type,
                        GetSchemeRegistry().referrer_schemes);
 }
 
-bool FindAndCompareScheme(const char* str,
-                          int str_len,
+bool FindAndCompareScheme(std::string_view str,
                           const char* compare,
                           Component* found_scheme) {
-  return DoFindAndCompareScheme(str, str_len, compare, found_scheme);
+  return DoFindAndCompareScheme(str, compare, found_scheme);
 }
 
-bool FindAndCompareScheme(const char16_t* str,
-                          int str_len,
+bool FindAndCompareScheme(std::u16string_view str,
                           const char* compare,
                           Component* found_scheme) {
-  return DoFindAndCompareScheme(str, str_len, compare, found_scheme);
+  return DoFindAndCompareScheme(str, compare, found_scheme);
 }
 
 bool DomainIs(std::string_view canonical_host,
@@ -760,77 +784,66 @@
 bool HostIsIPAddress(std::string_view host) {
   STACK_UNINITIALIZED url::RawCanonOutputT<char, 128> ignored_output;
   url::CanonHostInfo host_info;
-  url::CanonicalizeIPAddress(host.data(), Component(0, host.length()),
-                             &ignored_output, &host_info);
+  url::CanonicalizeIPAddress(host, &ignored_output, &host_info);
   return host_info.IsIPAddress();
 }
 
-bool Canonicalize(const char* spec,
-                  int spec_len,
+bool Canonicalize(std::string_view spec,
                   bool trim_path_end,
                   CharsetConverter* charset_converter,
                   CanonOutput* output,
                   Parsed* output_parsed) {
-  return DoCanonicalize(spec, spec_len, trim_path_end, REMOVE_WHITESPACE,
+  return DoCanonicalize(spec, trim_path_end, REMOVE_WHITESPACE,
                         charset_converter, output, output_parsed);
 }
 
-bool Canonicalize(const char16_t* spec,
-                  int spec_len,
+bool Canonicalize(std::u16string_view spec,
                   bool trim_path_end,
                   CharsetConverter* charset_converter,
                   CanonOutput* output,
                   Parsed* output_parsed) {
-  return DoCanonicalize(spec, spec_len, trim_path_end, REMOVE_WHITESPACE,
+  return DoCanonicalize(spec, trim_path_end, REMOVE_WHITESPACE,
                         charset_converter, output, output_parsed);
 }
 
-bool ResolveRelative(const char* base_spec,
-                     int base_spec_len,
+bool ResolveRelative(std::string_view base_spec,
                      const Parsed& base_parsed,
-                     const char* relative,
-                     int relative_length,
+                     std::string_view relative,
                      CharsetConverter* charset_converter,
                      CanonOutput* output,
                      Parsed* output_parsed) {
-  return DoResolveRelative(base_spec, base_spec_len, base_parsed,
-                           relative, relative_length,
-                           charset_converter, output, output_parsed);
+  return DoResolveRelative(base_spec, base_parsed, relative, charset_converter,
+                           output, output_parsed);
 }
 
-bool ResolveRelative(const char* base_spec,
-                     int base_spec_len,
+bool ResolveRelative(std::string_view base_spec,
                      const Parsed& base_parsed,
-                     const char16_t* relative,
-                     int relative_length,
+                     std::u16string_view relative,
                      CharsetConverter* charset_converter,
                      CanonOutput* output,
                      Parsed* output_parsed) {
-  return DoResolveRelative(base_spec, base_spec_len, base_parsed,
-                           relative, relative_length,
-                           charset_converter, output, output_parsed);
+  return DoResolveRelative(base_spec, base_parsed, relative, charset_converter,
+                           output, output_parsed);
 }
 
-bool ReplaceComponents(const char* spec,
-                       int spec_len,
+bool ReplaceComponents(std::string_view spec,
                        const Parsed& parsed,
                        const Replacements<char>& replacements,
                        CharsetConverter* charset_converter,
                        CanonOutput* output,
                        Parsed* out_parsed) {
-  return DoReplaceComponents(spec, spec_len, parsed, replacements,
-                             charset_converter, output, out_parsed);
+  return DoReplaceComponents(spec, parsed, replacements, charset_converter,
+                             output, out_parsed);
 }
 
-bool ReplaceComponents(const char* spec,
-                       int spec_len,
+bool ReplaceComponents(std::string_view spec,
                        const Parsed& parsed,
                        const Replacements<char16_t>& replacements,
                        CharsetConverter* charset_converter,
                        CanonOutput* output,
                        Parsed* out_parsed) {
-  return DoReplaceComponents(spec, spec_len, parsed, replacements,
-                             charset_converter, output, out_parsed);
+  return DoReplaceComponents(spec, parsed, replacements, charset_converter,
+                             output, out_parsed);
 }
 
 void DecodeURLEscapeSequences(std::string_view input,
@@ -907,13 +920,13 @@
   return IsComponentChar(c);
 }
 
-bool CompareSchemeComponent(const char* spec,
+bool CompareSchemeComponent(std::string_view spec,
                             const Component& component,
                             const char* compare_to) {
   return DoCompareSchemeComponent(spec, component, compare_to);
 }
 
-bool CompareSchemeComponent(const char16_t* spec,
+bool CompareSchemeComponent(std::u16string_view spec,
                             const Component& component,
                             const char* compare_to) {
   return DoCompareSchemeComponent(spec, component, compare_to);
@@ -931,4 +944,9 @@
   return false;
 }
 
+bool IsAndroidWebViewHackEnabledScheme(std::string_view scheme) {
+  return AllowNonStandardSchemesForAndroidWebView() &&
+         !IsStandardScheme(scheme);
+}
+
 }  // namespace url
diff --git a/url/url_util.h b/url/url_util.h
index 4254426..5c1e32b 100644
--- a/url/url_util.h
+++ b/url/url_util.h
@@ -60,7 +60,7 @@
 // URI syntax" (https://tools.ietf.org/html/rfc3986#section-3).
 
 COMPONENT_EXPORT(URL)
-void AddStandardScheme(const char* new_scheme, SchemeType scheme_type);
+void AddStandardScheme(std::string_view new_scheme, SchemeType scheme_type);
 
 // Returns the list of schemes registered for "standard" URLs.  Note, this
 // should not be used if you just need to check if your protocol is standard
@@ -73,28 +73,28 @@
 // Adds an application-defined scheme to the internal list of schemes allowed
 // for referrers.
 COMPONENT_EXPORT(URL)
-void AddReferrerScheme(const char* new_scheme, SchemeType scheme_type);
+void AddReferrerScheme(std::string_view new_scheme, SchemeType scheme_type);
 
 // Adds an application-defined scheme to the list of schemes that do not trigger
 // mixed content warnings.
-COMPONENT_EXPORT(URL) void AddSecureScheme(const char* new_scheme);
+COMPONENT_EXPORT(URL) void AddSecureScheme(std::string_view new_scheme);
 COMPONENT_EXPORT(URL) const std::vector<std::string>& GetSecureSchemes();
 
 // Adds an application-defined scheme to the list of schemes that normal pages
 // cannot link to or access (i.e., with the same security rules as those applied
 // to "file" URLs).
-COMPONENT_EXPORT(URL) void AddLocalScheme(const char* new_scheme);
+COMPONENT_EXPORT(URL) void AddLocalScheme(std::string_view new_scheme);
 COMPONENT_EXPORT(URL) const std::vector<std::string>& GetLocalSchemes();
 
 // Adds an application-defined scheme to the list of schemes that cause pages
 // loaded with them to not have access to pages loaded with any other URL
 // scheme.
-COMPONENT_EXPORT(URL) void AddNoAccessScheme(const char* new_scheme);
+COMPONENT_EXPORT(URL) void AddNoAccessScheme(std::string_view new_scheme);
 COMPONENT_EXPORT(URL) const std::vector<std::string>& GetNoAccessSchemes();
 
 // Adds an application-defined scheme to the list of schemes that can be sent
 // CORS requests.
-COMPONENT_EXPORT(URL) void AddCorsEnabledScheme(const char* new_scheme);
+COMPONENT_EXPORT(URL) void AddCorsEnabledScheme(std::string_view new_scheme);
 COMPONENT_EXPORT(URL) const std::vector<std::string>& GetCorsEnabledSchemes();
 
 // Adds an application-defined scheme to the list of web schemes that can be
@@ -102,17 +102,17 @@
 // to differentiate them from schemes that can store data but are not used on
 // web (e.g. application's internal schemes) or schemes that are used on web but
 // cannot store data.
-COMPONENT_EXPORT(URL) void AddWebStorageScheme(const char* new_scheme);
+COMPONENT_EXPORT(URL) void AddWebStorageScheme(std::string_view new_scheme);
 COMPONENT_EXPORT(URL) const std::vector<std::string>& GetWebStorageSchemes();
 
 // Adds an application-defined scheme to the list of schemes that can bypass the
 // Content-Security-Policy (CSP) checks.
-COMPONENT_EXPORT(URL) void AddCSPBypassingScheme(const char* new_scheme);
+COMPONENT_EXPORT(URL) void AddCSPBypassingScheme(std::string_view new_scheme);
 COMPONENT_EXPORT(URL) const std::vector<std::string>& GetCSPBypassingSchemes();
 
 // Adds an application-defined scheme to the list of schemes that are strictly
 // empty documents, allowing them to commit synchronously.
-COMPONENT_EXPORT(URL) void AddEmptyDocumentScheme(const char* new_scheme);
+COMPONENT_EXPORT(URL) void AddEmptyDocumentScheme(std::string_view new_scheme);
 COMPONENT_EXPORT(URL) const std::vector<std::string>& GetEmptyDocumentSchemes();
 
 // Adds a scheme with a predefined default handler.
@@ -121,7 +121,8 @@
 // described in the Custom Handler specification.
 // https://html.spec.whatwg.org/multipage/system-state.html#normalize-protocol-handler-parameters
 COMPONENT_EXPORT(URL)
-void AddPredefinedHandlerScheme(const char* new_scheme, const char* handler);
+void AddPredefinedHandlerScheme(std::string_view new_scheme,
+                                std::string_view handler);
 COMPONENT_EXPORT(URL)
 std::vector<std::pair<std::string, std::string>> GetPredefinedHandlerSchemes();
 
@@ -146,50 +147,48 @@
 // input (if any). The |compare| scheme must be a valid canonical scheme or
 // the result of the comparison is undefined.
 COMPONENT_EXPORT(URL)
-bool FindAndCompareScheme(const char* str,
-                          int str_len,
+bool FindAndCompareScheme(std::string_view str,
                           const char* compare,
                           Component* found_scheme);
+inline bool FindAndCompareScheme(const char* str,
+                                 int str_len,
+                                 const char* compare,
+                                 Component* found_scheme) {
+  return FindAndCompareScheme(
+      std::string_view(str, static_cast<size_t>(str_len)), compare,
+      found_scheme);
+}
 COMPONENT_EXPORT(URL)
-bool FindAndCompareScheme(const char16_t* str,
-                          int str_len,
+bool FindAndCompareScheme(std::u16string_view str,
                           const char* compare,
                           Component* found_scheme);
-inline bool FindAndCompareScheme(const std::string& str,
-                                 const char* compare,
-                                 Component* found_scheme) {
-  return FindAndCompareScheme(str.data(), static_cast<int>(str.size()),
-                              compare, found_scheme);
-}
-inline bool FindAndCompareScheme(const std::u16string& str,
-                                 const char* compare,
-                                 Component* found_scheme) {
-  return FindAndCompareScheme(str.data(), static_cast<int>(str.size()),
-                              compare, found_scheme);
-}
 
 // Returns true if the given scheme identified by |scheme| within |spec| is in
 // the list of known standard-format schemes (see AddStandardScheme).
+// TODO(crbug.com/351564777): Delete this after //third_party/openscreen
+// transition is complete.
 COMPONENT_EXPORT(URL)
 bool IsStandard(const char* spec, const Component& scheme);
 COMPONENT_EXPORT(URL)
-bool IsStandard(const char16_t* spec, const Component& scheme);
+bool IsStandard(std::optional<std::string_view> scheme);
+COMPONENT_EXPORT(URL)
+bool IsStandard(std::optional<std::u16string_view> scheme);
+
+bool IsStandardScheme(std::string_view scheme);
 
 // Returns true if the given scheme identified by |scheme| within |spec| is in
 // the list of allowed schemes for referrers (see AddReferrerScheme).
 COMPONENT_EXPORT(URL)
-bool IsReferrerScheme(const char* spec, const Component& scheme);
+bool IsReferrerScheme(std::optional<std::string_view> scheme);
 
 // Returns true and sets |type| to the SchemeType of the given scheme
 // identified by |scheme| within |spec| if the scheme is in the list of known
 // standard-format schemes (see AddStandardScheme).
 COMPONENT_EXPORT(URL)
-bool GetStandardSchemeType(const char* spec,
-                           const Component& scheme,
+bool GetStandardSchemeType(std::optional<std::string_view> scheme,
                            SchemeType* type);
 COMPONENT_EXPORT(URL)
-bool GetStandardSchemeType(const char16_t* spec,
-                           const Component& scheme,
+bool GetStandardSchemeType(std::optional<std::u16string_view> scheme,
                            SchemeType* type);
 
 // Hosts  ----------------------------------------------------------------------
@@ -223,15 +222,13 @@
 // output and parsed structures will still be filled and will be consistent,
 // but they will not represent a loadable URL.
 COMPONENT_EXPORT(URL)
-bool Canonicalize(const char* spec,
-                  int spec_len,
+bool Canonicalize(std::string_view spec,
                   bool trim_path_end,
                   CharsetConverter* charset_converter,
                   CanonOutput* output,
                   Parsed* output_parsed);
 COMPONENT_EXPORT(URL)
-bool Canonicalize(const char16_t* spec,
-                  int spec_len,
+bool Canonicalize(std::u16string_view spec,
                   bool trim_path_end,
                   CharsetConverter* charset_converter,
                   CanonOutput* output,
@@ -248,20 +245,16 @@
 // Returns true if the output is valid, false if the input could not produce
 // a valid URL.
 COMPONENT_EXPORT(URL)
-bool ResolveRelative(const char* base_spec,
-                     int base_spec_len,
+bool ResolveRelative(std::string_view base_spec,
                      const Parsed& base_parsed,
-                     const char* relative,
-                     int relative_length,
+                     std::string_view relative,
                      CharsetConverter* charset_converter,
                      CanonOutput* output,
                      Parsed* output_parsed);
 COMPONENT_EXPORT(URL)
-bool ResolveRelative(const char* base_spec,
-                     int base_spec_len,
+bool ResolveRelative(std::string_view base_spec,
                      const Parsed& base_parsed,
-                     const char16_t* relative,
-                     int relative_length,
+                     std::u16string_view relative,
                      CharsetConverter* charset_converter,
                      CanonOutput* output,
                      Parsed* output_parsed);
@@ -271,16 +264,14 @@
 //
 // Returns true if the resulting URL is valid.
 COMPONENT_EXPORT(URL)
-bool ReplaceComponents(const char* spec,
-                       int spec_len,
+bool ReplaceComponents(std::string_view spec,
                        const Parsed& parsed,
                        const Replacements<char>& replacements,
                        CharsetConverter* charset_converter,
                        CanonOutput* output,
                        Parsed* out_parsed);
 COMPONENT_EXPORT(URL)
-bool ReplaceComponents(const char* spec,
-                       int spec_len,
+bool ReplaceComponents(std::string_view spec,
                        const Parsed& parsed,
                        const Replacements<char16_t>& replacements,
                        CharsetConverter* charset_converter,
@@ -310,7 +301,7 @@
 
 // Returns true if `c` is a character that does not require escaping in
 // encodeURIComponent.
-// TODO(crbug.com/1481056): Remove this when event-level reportEvent is removed
+// TODO(crbug.com/40281561): Remove this when event-level reportEvent is removed
 // (if it is still this function's only consumer).
 COMPONENT_EXPORT(URL)
 bool IsURIComponentChar(char c);
@@ -323,6 +314,8 @@
 COMPONENT_EXPORT(URL)
 bool HasInvalidURLEscapeSequences(std::string_view input);
 
+// Check if a scheme is affected by the Android WebView Hack.
+bool IsAndroidWebViewHackEnabledScheme(std::string_view scheme);
 }  // namespace url
 
 #endif  // URL_URL_UTIL_H_
diff --git a/url/url_util_internal.h b/url/url_util_internal.h
index fe2a4d9..db8f29a 100644
--- a/url/url_util_internal.h
+++ b/url/url_util_internal.h
@@ -5,16 +5,18 @@
 #ifndef URL_URL_UTIL_INTERNAL_H_
 #define URL_URL_UTIL_INTERNAL_H_
 
+#include <string_view>
+
 #include "url/third_party/mozilla/url_parse.h"
 
 namespace url {
 
 // Given a string and a range inside the string, compares it to the given
 // lower-case |compare_to| buffer.
-bool CompareSchemeComponent(const char* spec,
+bool CompareSchemeComponent(std::string_view spec,
                             const Component& component,
                             const char* compare_to);
-bool CompareSchemeComponent(const char16_t* spec,
+bool CompareSchemeComponent(std::u16string_view spec,
                             const Component& component,
                             const char* compare_to);
 
diff --git a/url/url_util_unittest.cc b/url/url_util_unittest.cc
index edf3563..3c6c7bf 100644
--- a/url/url_util_unittest.cc
+++ b/url/url_util_unittest.cc
@@ -2,13 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
+#pragma allow_unsafe_libc_calls
+#endif
+
 #include "url/url_util.h"
 
 #include <stddef.h>
 
+#include <optional>
 #include <string_view>
 
-#include <optional>
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest-message.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -28,6 +33,54 @@
 
   ~URLUtilTest() override = default;
 
+ protected:
+  struct URLCase {
+    const std::string_view input;
+    const std::string_view expected;
+    bool expected_success;
+  };
+
+  struct ResolveRelativeCase {
+    const std::string_view base;
+    const std::string_view rel;
+    std::optional<std::string_view> expected;
+  };
+
+  void TestCanonicalize(const URLCase& url_case) {
+    std::string canonicalized;
+    StdStringCanonOutput output(&canonicalized);
+    Parsed parsed;
+    bool success =
+        Canonicalize(url_case.input,
+                     /*trim_path_end=*/false,
+                     /*charset_converter=*/nullptr, &output, &parsed);
+    output.Complete();
+    EXPECT_EQ(success, url_case.expected_success);
+    EXPECT_EQ(output.view(), url_case.expected);
+  }
+
+  void TestResolveRelative(const ResolveRelativeCase& test) {
+    SCOPED_TRACE(testing::Message()
+                 << "base: " << test.base << ", rel: " << test.rel);
+
+    Parsed base_parsed = ParseNonSpecialUrl(test.base);
+
+    std::string resolved;
+    StdStringCanonOutput output(&resolved);
+
+    Parsed resolved_parsed;
+    bool valid = ResolveRelative(test.base, base_parsed, test.rel, nullptr,
+                                 &output, &resolved_parsed);
+    output.Complete();
+
+    if (valid) {
+      ASSERT_TRUE(test.expected);
+      EXPECT_EQ(resolved, *test.expected);
+    } else {
+      EXPECT_FALSE(test.expected);
+    }
+  }
+
  private:
   ScopedSchemeRegistryForTests scoped_registry_;
 };
@@ -37,86 +90,77 @@
 
   // Simple case where the scheme is found and matches.
   const char kStr1[] = "http://www.com/";
-  EXPECT_TRUE(FindAndCompareScheme(kStr1, static_cast<int>(strlen(kStr1)),
-                                   "http", nullptr));
-  EXPECT_TRUE(FindAndCompareScheme(
-      kStr1, static_cast<int>(strlen(kStr1)), "http", &found_scheme));
+  EXPECT_TRUE(FindAndCompareScheme(kStr1, "http", nullptr));
+  EXPECT_TRUE(FindAndCompareScheme(kStr1, "http", &found_scheme));
   EXPECT_TRUE(found_scheme == Component(0, 4));
 
   // A case where the scheme is found and doesn't match.
-  EXPECT_FALSE(FindAndCompareScheme(
-      kStr1, static_cast<int>(strlen(kStr1)), "https", &found_scheme));
+  EXPECT_FALSE(FindAndCompareScheme(kStr1, "https", &found_scheme));
   EXPECT_TRUE(found_scheme == Component(0, 4));
 
   // A case where there is no scheme.
   const char kStr2[] = "httpfoobar";
-  EXPECT_FALSE(FindAndCompareScheme(
-      kStr2, static_cast<int>(strlen(kStr2)), "http", &found_scheme));
+  EXPECT_FALSE(FindAndCompareScheme(kStr2, "http", &found_scheme));
   EXPECT_TRUE(found_scheme == Component());
 
   // When there is an empty scheme, it should match the empty scheme.
   const char kStr3[] = ":foo.com/";
-  EXPECT_TRUE(FindAndCompareScheme(
-      kStr3, static_cast<int>(strlen(kStr3)), "", &found_scheme));
+  EXPECT_TRUE(FindAndCompareScheme(kStr3, "", &found_scheme));
   EXPECT_TRUE(found_scheme == Component(0, 0));
 
   // But when there is no scheme, it should fail.
-  EXPECT_FALSE(FindAndCompareScheme("", 0, "", &found_scheme));
+  EXPECT_FALSE(FindAndCompareScheme("", "", &found_scheme));
   EXPECT_TRUE(found_scheme == Component());
 
   // When there is a whitespace char in scheme, it should canonicalize the URL
   // before comparison.
   const char whtspc_str[] = " \r\n\tjav\ra\nscri\tpt:alert(1)";
-  EXPECT_TRUE(FindAndCompareScheme(whtspc_str,
-                                   static_cast<int>(strlen(whtspc_str)),
-                                   "javascript", &found_scheme));
+  EXPECT_TRUE(FindAndCompareScheme(whtspc_str, "javascript", &found_scheme));
   EXPECT_TRUE(found_scheme == Component(1, 10));
 
   // Control characters should be stripped out on the ends, and kept in the
   // middle.
   const char ctrl_str[] = "\02jav\02scr\03ipt:alert(1)";
-  EXPECT_FALSE(FindAndCompareScheme(ctrl_str,
-                                    static_cast<int>(strlen(ctrl_str)),
-                                    "javascript", &found_scheme));
+  EXPECT_FALSE(FindAndCompareScheme(ctrl_str, "javascript", &found_scheme));
   EXPECT_TRUE(found_scheme == Component(1, 11));
 }
 
 TEST_F(URLUtilTest, IsStandard) {
   const char kHTTPScheme[] = "http";
-  EXPECT_TRUE(IsStandard(kHTTPScheme, Component(0, strlen(kHTTPScheme))));
+  EXPECT_TRUE(IsStandard(kHTTPScheme));
 
   const char kFooScheme[] = "foo";
-  EXPECT_FALSE(IsStandard(kFooScheme, Component(0, strlen(kFooScheme))));
+  EXPECT_FALSE(IsStandard(kFooScheme));
 }
 
 TEST_F(URLUtilTest, IsReferrerScheme) {
   const char kHTTPScheme[] = "http";
-  EXPECT_TRUE(IsReferrerScheme(kHTTPScheme, Component(0, strlen(kHTTPScheme))));
+  EXPECT_TRUE(IsReferrerScheme(kHTTPScheme));
 
   const char kFooScheme[] = "foo";
-  EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
+  EXPECT_FALSE(IsReferrerScheme(kFooScheme));
 }
 
 TEST_F(URLUtilTest, AddReferrerScheme) {
   static const char kFooScheme[] = "foo";
-  EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
+  EXPECT_FALSE(IsReferrerScheme(kFooScheme));
 
   url::ScopedSchemeRegistryForTests scoped_registry;
   AddReferrerScheme(kFooScheme, url::SCHEME_WITH_HOST);
-  EXPECT_TRUE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
+  EXPECT_TRUE(IsReferrerScheme(kFooScheme));
 }
 
 TEST_F(URLUtilTest, ShutdownCleansUpSchemes) {
   static const char kFooScheme[] = "foo";
-  EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
+  EXPECT_FALSE(IsReferrerScheme(kFooScheme));
 
   {
     url::ScopedSchemeRegistryForTests scoped_registry;
     AddReferrerScheme(kFooScheme, url::SCHEME_WITH_HOST);
-    EXPECT_TRUE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
+    EXPECT_TRUE(IsReferrerScheme(kFooScheme));
   }
 
-  EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
+  EXPECT_FALSE(IsReferrerScheme(kFooScheme));
 }
 
 TEST_F(URLUtilTest, GetStandardSchemeType) {
@@ -125,21 +169,18 @@
   const char kHTTPScheme[] = "http";
   scheme_type = url::SCHEME_WITHOUT_AUTHORITY;
   EXPECT_TRUE(GetStandardSchemeType(kHTTPScheme,
-                                    Component(0, strlen(kHTTPScheme)),
                                     &scheme_type));
   EXPECT_EQ(url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, scheme_type);
 
   const char kFilesystemScheme[] = "filesystem";
   scheme_type = url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
   EXPECT_TRUE(GetStandardSchemeType(kFilesystemScheme,
-                                    Component(0, strlen(kFilesystemScheme)),
                                     &scheme_type));
   EXPECT_EQ(url::SCHEME_WITHOUT_AUTHORITY, scheme_type);
 
   const char kFooScheme[] = "foo";
   scheme_type = url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
   EXPECT_FALSE(GetStandardSchemeType(kFooScheme,
-                                     Component(0, strlen(kFooScheme)),
                                      &scheme_type));
 }
 
@@ -160,22 +201,22 @@
   // Check that the following calls do not cause crash
   Replacements<char> replacements;
   replacements.SetRef("test", Component(0, 4));
-  ReplaceComponents(nullptr, 0, parsed, replacements, nullptr, &output,
+  ReplaceComponents(std::string_view(), parsed, replacements, nullptr, &output,
                     &new_parsed);
-  ReplaceComponents("", 0, parsed, replacements, nullptr, &output, &new_parsed);
+  ReplaceComponents("", parsed, replacements, nullptr, &output, &new_parsed);
   replacements.ClearRef();
   replacements.SetHost("test", Component(0, 4));
-  ReplaceComponents(nullptr, 0, parsed, replacements, nullptr, &output,
+  ReplaceComponents(std::string_view(), parsed, replacements, nullptr, &output,
                     &new_parsed);
-  ReplaceComponents("", 0, parsed, replacements, nullptr, &output, &new_parsed);
+  ReplaceComponents("", parsed, replacements, nullptr, &output, &new_parsed);
 
   replacements.ClearHost();
-  ReplaceComponents(nullptr, 0, parsed, replacements, nullptr, &output,
+  ReplaceComponents(std::string_view(), parsed, replacements, nullptr, &output,
                     &new_parsed);
-  ReplaceComponents("", 0, parsed, replacements, nullptr, &output, &new_parsed);
-  ReplaceComponents(nullptr, 0, parsed, replacements, nullptr, &output,
+  ReplaceComponents("", parsed, replacements, nullptr, &output, &new_parsed);
+  ReplaceComponents(std::string_view(), parsed, replacements, nullptr, &output,
                     &new_parsed);
-  ReplaceComponents("", 0, parsed, replacements, nullptr, &output, &new_parsed);
+  ReplaceComponents("", parsed, replacements, nullptr, &output, &new_parsed);
 }
 
 static std::string CheckReplaceScheme(const char* base_url,
@@ -183,8 +224,7 @@
   // Make sure the input is canonicalized.
   RawCanonOutput<32> original;
   Parsed original_parsed;
-  Canonicalize(base_url, strlen(base_url), true, nullptr, &original,
-               &original_parsed);
+  Canonicalize(base_url, true, nullptr, &original, &original_parsed);
 
   Replacements<char> replacements;
   replacements.SetScheme(scheme, Component(0, strlen(scheme)));
@@ -192,8 +232,8 @@
   std::string output_string;
   StdStringCanonOutput output(&output_string);
   Parsed output_parsed;
-  ReplaceComponents(original.data(), original.length(), original_parsed,
-                    replacements, nullptr, &output, &output_parsed);
+  ReplaceComponents(original.view(), original_parsed, replacements, nullptr,
+                    &output, &output_parsed);
 
   output.Complete();
   return output_string;
@@ -348,116 +388,6 @@
   }
 }
 
-TEST_F(URLUtilTest, TestResolveRelativeWithNonStandardBase) {
-  // This tests non-standard (in the sense that IsStandard() == false)
-  // hierarchical schemes.
-  struct ResolveRelativeCase {
-    const char* base;
-    const char* rel;
-    bool is_valid;
-    const char* out;
-  } resolve_non_standard_cases[] = {
-      // Resolving a relative path against a non-hierarchical URL should fail.
-      {"scheme:opaque_data", "/path", false, ""},
-      // Resolving a relative path against a non-standard authority-based base
-      // URL doesn't alter the authority section.
-      {"scheme://Authority/", "../path", true, "scheme://Authority/path"},
-      // A non-standard hierarchical base is resolved with path URL
-      // canonicalization rules.
-      {"data:/Blah:Blah/", "file.html", true, "data:/Blah:Blah/file.html"},
-      {"data:/Path/../part/part2", "file.html", true,
-       "data:/Path/../part/file.html"},
-      {"data://text/html,payload", "//user:pass@host:33////payload22", true,
-       "data://user:pass@host:33////payload22"},
-      // Path URL canonicalization rules also apply to non-standard authority-
-      // based URLs.
-      {"custom://Authority/", "file.html", true,
-       "custom://Authority/file.html"},
-      {"custom://Authority/", "other://Auth/", true, "other://Auth/"},
-      {"custom://Authority/", "../../file.html", true,
-       "custom://Authority/file.html"},
-      {"custom://Authority/path/", "file.html", true,
-       "custom://Authority/path/file.html"},
-      {"custom://Authority:NoCanon/path/", "file.html", true,
-       "custom://Authority:NoCanon/path/file.html"},
-      // It's still possible to get an invalid path URL.
-      {"custom://Invalid:!#Auth/", "file.html", false, ""},
-      // A path with an authority section gets canonicalized under standard URL
-      // rules, even though the base was non-standard.
-      {"content://content.Provider/", "//other.Provider", true,
-       "content://other.provider/"},
-
-      // Resolving an absolute URL doesn't cause canonicalization of the
-      // result.
-      {"about:blank", "custom://Authority", true, "custom://Authority"},
-      // Fragment URLs can be resolved against a non-standard base.
-      {"scheme://Authority/path", "#fragment", true,
-       "scheme://Authority/path#fragment"},
-      {"scheme://Authority/", "#fragment", true,
-       "scheme://Authority/#fragment"},
-      // Resolving should fail if the base URL is authority-based but is
-      // missing a path component (the '/' at the end).
-      {"scheme://Authority", "path", false, ""},
-      // Test resolving a fragment (only) against any kind of base-URL.
-      {"about:blank", "#id42", true, "about:blank#id42"},
-      {"about:blank", " #id42", true, "about:blank#id42"},
-      {"about:blank#oldfrag", "#newfrag", true, "about:blank#newfrag"},
-      {"about:blank", " #id:42", true, "about:blank#id:42"},
-      // A surprising side effect of allowing fragments to resolve against
-      // any URL scheme is we might break javascript: URLs by doing so...
-      {"javascript:alert('foo#bar')", "#badfrag", true,
-       "javascript:alert('foo#badfrag"},
-      // In this case, the backslashes will not be canonicalized because it's a
-      // non-standard URL, but they will be treated as a path separators,
-      // giving the base URL here a path of "\".
-      //
-      // The result here is somewhat arbitrary. One could argue it should be
-      // either "aaa://a\" or "aaa://a/" since the path is being replaced with
-      // the "current directory". But in the context of resolving on data URLs,
-      // adding the requested dot doesn't seem wrong either.
-      {"aaa://a\\", "aaa:.", true, "aaa://a\\."}};
-
-  for (const auto& test : resolve_non_standard_cases) {
-    SCOPED_TRACE(testing::Message()
-                 << "base: " << test.base << ", rel: " << test.rel);
-
-    Parsed base_parsed;
-    ParsePathURL(test.base, strlen(test.base), false, &base_parsed);
-
-    std::string resolved;
-    StdStringCanonOutput output(&resolved);
-    Parsed resolved_parsed;
-    bool valid =
-        ResolveRelative(test.base, strlen(test.base), base_parsed, test.rel,
-                        strlen(test.rel), nullptr, &output, &resolved_parsed);
-    output.Complete();
-
-    EXPECT_EQ(test.is_valid, valid);
-    if (test.is_valid && valid) {
-      EXPECT_EQ(test.out, resolved);
-    }
-  }
-}
-
-TEST_F(URLUtilTest, TestNoRefComponent) {
-  // The hash-mark must be ignored when mailto: scheme is parsed,
-  // even if the URL has a base and relative part.
-  const char* base = "mailto://to/";
-  const char* rel = "any#body";
-
-  Parsed base_parsed;
-  ParsePathURL(base, strlen(base), false, &base_parsed);
-
-  std::string resolved;
-  StdStringCanonOutput output(&resolved);
-  Parsed resolved_parsed;
-
-  bool valid = ResolveRelative(base, strlen(base), base_parsed, rel,
-                               strlen(rel), nullptr, &output, &resolved_parsed);
-  EXPECT_TRUE(valid);
-  EXPECT_FALSE(resolved_parsed.ref.is_valid());
-}
-
 TEST_F(URLUtilTest, PotentiallyDanglingMarkup) {
   struct ResolveRelativeCase {
     const char* base;
@@ -488,15 +418,13 @@
 
   for (const auto& test : cases) {
     SCOPED_TRACE(::testing::Message() << test.base << ", " << test.rel);
-    Parsed base_parsed;
-    ParseStandardURL(test.base, strlen(test.base), &base_parsed);
+    Parsed base_parsed = ParseStandardUrl(test.base);
 
     std::string resolved;
     StdStringCanonOutput output(&resolved);
     Parsed resolved_parsed;
-    bool valid =
-        ResolveRelative(test.base, strlen(test.base), base_parsed, test.rel,
-                        strlen(test.rel), nullptr, &output, &resolved_parsed);
+    bool valid = ResolveRelative(test.base, base_parsed, test.rel, nullptr,
+                                 &output, &resolved_parsed);
     ASSERT_TRUE(valid);
     output.Complete();
 
@@ -511,7 +439,7 @@
   Parsed original_parsed;
   RawCanonOutput<32> original;
   const char* url = "htt\nps://example.com/<path";
-  Canonicalize(url, strlen(url), false, nullptr, &original, &original_parsed);
+  Canonicalize(url, false, nullptr, &original, &original_parsed);
   ASSERT_TRUE(original_parsed.potentially_dangling_markup);
 
   // Perform a replacement, and validate that the potentially_dangling_markup
@@ -520,8 +448,8 @@
   replacements.ClearRef();
   Parsed replaced_parsed;
   RawCanonOutput<32> replaced;
-  ReplaceComponents(original.data(), original.length(), original_parsed,
-                    replacements, nullptr, &replaced, &replaced_parsed);
+  ReplaceComponents(original.view(), original_parsed, replacements, nullptr,
+                    &replaced, &replaced_parsed);
   EXPECT_TRUE(replaced_parsed.potentially_dangling_markup);
 }
 
@@ -530,7 +458,7 @@
   Parsed original_parsed;
   RawCanonOutput<32> original;
   const char* url = "http://example.com/\n/<path";
-  Canonicalize(url, strlen(url), false, nullptr, &original, &original_parsed);
+  Canonicalize(url, false, nullptr, &original, &original_parsed);
   ASSERT_TRUE(original_parsed.potentially_dangling_markup);
 
   // Perform a replacement, and validate that the potentially_dangling_markup
@@ -540,8 +468,8 @@
   replacements.SetScheme(new_scheme, Component(0, strlen(new_scheme)));
   Parsed replaced_parsed;
   RawCanonOutput<32> replaced;
-  ReplaceComponents(original.data(), original.length(), original_parsed,
-                    replacements, nullptr, &replaced, &replaced_parsed);
+  ReplaceComponents(original.view(), original_parsed, replacements, nullptr,
+                    &replaced, &replaced_parsed);
   EXPECT_TRUE(replaced_parsed.potentially_dangling_markup);
 }
 
@@ -594,7 +522,7 @@
   std::string canonicalized;
   StdStringCanonOutput output(&canonicalized);
   Parsed parsed;
-  if (!Canonicalize(spec.data(), spec.size(), trim_path_end,
+  if (!Canonicalize(spec, trim_path_end,
                     /*charset_converter=*/nullptr, &output, &parsed)) {
     return {};
   }
@@ -719,4 +647,119 @@
   }
 }
 
+TEST_F(URLUtilTest, TestResolveRelativeWithNonStandardBase) {
+  // This tests non-standard (in the sense that IsStandard() == false)
+  // hierarchical schemes.
+  struct ResolveRelativeCase {
+    const char* base;
+    const char* rel;
+    bool is_valid;
+    const char* out;
+  } resolve_non_standard_cases[] = {
+      // Resolving a relative path against a non-hierarchical URL should fail.
+      {"scheme:opaque_data", "/path", false, ""},
+      // Resolving a relative path against a non-standard authority-based base
+      // URL doesn't alter the authority section.
+      {"scheme://Authority/", "../path", true, "scheme://Authority/path"},
+      // A non-standard hierarchical base is resolved with path URL
+      // canonicalization rules.
+      {"data:/Blah:Blah/", "file.html", true, "data:/Blah:Blah/file.html"},
+      {"data:/Path/../part/part2", "file.html", true,
+       "data:/Path/../part/file.html"},
+      {"data://text/html,payload", "//user:pass@host:33////payload22", true,
+       "data://user:pass@host:33////payload22"},
+      // Path URL canonicalization rules also apply to non-standard authority-
+      // based URLs.
+      {"custom://Authority/", "file.html", true,
+       "custom://Authority/file.html"},
+      {"custom://Authority/", "other://Auth/", true, "other://Auth/"},
+      {"custom://Authority/", "../../file.html", true,
+       "custom://Authority/file.html"},
+      {"custom://Authority/path/", "file.html", true,
+       "custom://Authority/path/file.html"},
+      {"custom://Authority:NoCanon/path/", "file.html", true,
+       "custom://Authority:NoCanon/path/file.html"},
+      // A path with an authority section gets canonicalized under standard URL
+      // rules, even though the base was non-standard. Host case sensitivity
+      // should be preserved and trailing slash after a host soulld be removed.
+      {"content://content.Provider/", "//other.Provider", true,
+       "content://other.Provider"},
+      // Resolving an absolute URL doesn't cause canonicalization of the
+      // result.
+      {"about:blank", "custom://Authority", true, "custom://Authority"},
+      // Fragment URLs can be resolved against a non-standard base.
+      {"scheme://Authority/path", "#fragment", true,
+       "scheme://Authority/path#fragment"},
+      {"scheme://Authority/", "#fragment", true,
+       "scheme://Authority/#fragment"},
+      // Test resolving a fragment (only) against any kind of base-URL.
+      {"about:blank", "#id42", true, "about:blank#id42"},
+      {"about:blank", " #id42", true, "about:blank#id42"},
+      {"about:blank#oldfrag", "#newfrag", true, "about:blank#newfrag"},
+      {"about:blank", " #id:42", true, "about:blank#id:42"},
+      // A surprising side effect of allowing fragments to resolve against
+      // any URL scheme is we might break javascript: URLs by doing so...
+      {"javascript:alert('foo#bar')", "#badfrag", true,
+       "javascript:alert('foo#badfrag"},
+  };
+
+  for (const auto& test : resolve_non_standard_cases) {
+    SCOPED_TRACE(testing::Message()
+                 << "base: " << test.base << ", rel: " << test.rel);
+
+    Parsed base_parsed = ParseNonSpecialUrl(test.base);
+    std::string resolved;
+    StdStringCanonOutput output(&resolved);
+    Parsed resolved_parsed;
+    bool valid = ResolveRelative(test.base, base_parsed, test.rel, nullptr,
+                                 &output, &resolved_parsed);
+    output.Complete();
+
+    EXPECT_EQ(test.is_valid, valid);
+    if (test.is_valid && valid) {
+      EXPECT_EQ(test.out, resolved);
+    }
+  }
+}
+
+TEST_F(URLUtilTest, Cannolicalize) {
+  // Verify that the feature flag changes canonicalization behavior,
+  // focusing on key cases here as comprehesive testing is covered in other unit
+  // tests.
+  URLCase cases[] = {
+      {"git://host/..", "git://host/", true},
+      {"git:// /", "git:///", false},
+      {"git:/..", "git:/", true},
+      {"mailto:/..", "mailto:/", true},
+  };
+  for (const auto& i : cases) {
+    TestCanonicalize(i);
+  }
+}
+
+TEST_F(URLUtilTest, TestResolveRelativeWithNonSpecialBase) {
+  ResolveRelativeCase cases[] = {
+      {"scheme://Authority", "path", "scheme://Authority/path"},
+  };
+  for (const auto& i : cases) {
+    TestResolveRelative(i);
+  }
+}
+
+TEST_F(URLUtilTest, OpaqueNonSpecialScheme) {
+  // Ensure that the behavior of "android:" scheme URL is preserved, which is
+  // not URL Standard compliant.
+  //
+  // URL Standard-wise, "android://a b" is an invalid URL because the host part
+  // includes a space character, which is not allowed.
+  std::optional<std::string> res = CanonicalizeSpec("android://a b", false);
+  ASSERT_TRUE(res);
+  EXPECT_EQ(*res, "android://a b");
+
+  // Test a "git:" scheme URL for comparison.
+  res = CanonicalizeSpec("git://a b", false);
+  // This is correct behavior because "git://a b" is an invalid URL.
+  EXPECT_FALSE(res);
+}
+
 }  // namespace url