Switch to C++20 and update googleurl from Chromium upstream

Roll googleurl to the version 231d950fb5cecd889295c56244d77eb7b6ce50d1
from Thu Nov 16 17:52:53 2023 +0000

Change-Id: I474b633d0bc5f7b518b90bd27b470de0797e315c
diff --git a/AUTHORS b/AUTHORS
index 2ae5e98..d6a532a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -51,6 +51,7 @@
 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>
 Akos Kiss <akiss@inf.u-szeged.hu>
 Aku Kotkavuo <a.kotkavuo@partner.samsung.com>
 Aldo Culquicondor <alculquicondor@gmail.com>
@@ -233,6 +234,7 @@
 Cem Kocagil <cem.kocagil@gmail.com>
 Cezary Kułakowski <cezary.kulakowski@gmail.com>
 Chakshu Ahuja <chakshu.a@samsung.com>
+Chakshu Pragya <pragya.c1@samsung.com>
 Chamal De Silva <chamalsl@yahoo.com>
 Chandan Padhi <c.padhi@samsung.com>
 Chandra Shekar Vallala <brk376@motorola.com>
@@ -254,6 +256,7 @@
 Cheng Zhao <zcbenz@gmail.com>
 Cheng Yu <yuzichengcode@gmail.com>
 Chenguang Shao <chenguangshao1@gmail.com>
+Chengzhong Wu <legendecas@gmail.com>
 Choongwoo Han <cwhan.tunz@gmail.com>
 Choudhury M. Shamsujjoha <choudhury.s@samsung.com>
 Chris Dalton <chris@rive.app>
@@ -404,6 +407,7 @@
 Fabian Henneke <fabian.henneke@gmail.com>
 Fabien Tassin <fta@sofaraway.org>
 Fan Wen <fan.wen.dev@gmail.com>
+Fauzia Haque <fauzia.haque@samsung.com>
 Feifei Wang <alexswang@tencent.com>
 Felipe Erias Morandeira <felipeerias@gmail.com>
 Felix H. Dahlke <fhd@ubercode.de>
@@ -453,6 +457,7 @@
 Goutham Jagannatha <wrm364@motorola.com>
 Graham Sturmy <gsturmychromium@gmail.com>
 Graham Yoakum <gyoakum@skobalt.com>
+Grandhi Sri Nikhil <g1.nikhil@samsung.com>
 Greg Visser <gregvis@gmail.com>
 Gregory Davis <gpdavis.chromium@gmail.com>
 Grzegorz Czajkowski <g.czajkowski@samsung.com>
@@ -481,6 +486,7 @@
 He Qi <heqi899@gmail.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>
 Henry Lim <henry@limhenry.xyz>
@@ -542,6 +548,7 @@
 Ivan Naydonov <samogot@gmail.com>
 Ivan Pavlotskiy <ivan.pavlotskiy@lgepartner.com>
 Ivan Sham <ivansham@amazon.com>
+Ivan Sidorov <ivansid@gmail.com>
 Jack Bates <jack@nottheoilrig.com>
 Jackson Loeffler <j@jloeffler.com>
 Jacky Hu <flameddd@gmail.com>
@@ -552,6 +559,7 @@
 Jaehyun Lee <j-hyun.lee@samsung.com>
 Jaekyeom Kim <btapiz@gmail.com>
 Jaemin Seo <jaemin86.seo@samsung.com>
+Jaemo Koo <jaemok@amazon.com>
 Jaeseok Yoon <yjaeseok@gmail.com>
 Jaewon Choi <jaewon.james.choi@gmail.com>
 Jaewon Jung <jw.jung@navercorp.com>
@@ -609,11 +617,13 @@
 Jiahe Zhang <jiahe.zhang@intel.com>
 Jiajia Qin <jiajia.qin@intel.com>
 Jiajie Hu <jiajie.hu@intel.com>
+Jianfeng Liu <liujianfeng1994@gmail.com>
 Jiangzhen Hou <houjiangzhen@360.cn>
 Jianjun Zhu <jianjun.zhu@intel.com>
 Jianneng Zhong <muzuiget@gmail.com>
 Jiawei Shao <jiawei.shao@intel.com>
 Jiawei Chen <jiawei.chen@dolby.com>
+Jiawei Wang <hellojw513@gmail.com>
 Jiaxun Wei <leuisken@gmail.com>
 Jiaxun Yang <jiaxun.yang@flygoat.com>
 Jidong Qin <qinjidong@qianxin.com>
@@ -705,6 +715,7 @@
 Justin Ribeiro <justin@justinribeiro.com>
 Jüri Valdmann <juri.valdmann@qt.io>
 Juyoung Kim <chattank05@gmail.com>
+Jingge Yu <jinggeyu423@gmail.com>
 Jing Peiyang <jingpeiyang@eswincomputing.com>
 Kai Jiang <jiangkai@gmail.com>
 Kai Köhne <kai.koehne@qt.io>
@@ -724,6 +735,7 @@
 Kaustubh Atrawalkar <kaustubh.a@samsung.com>
 Kaustubh Atrawalkar <kaustubh.ra@gmail.com>
 Ke He <ke.he@intel.com>
+Ke Yu <kekely5692@gmail.com>
 Keeley Hammond <vertedinde@electronjs.org>
 Keeling <liqining.keeling@bytedance.com>
 Keene Pan <keenepan@linpus.com>
@@ -1085,6 +1097,7 @@
 Randy Posynick <randy.posynick@gmail.com>
 Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
 Raul Tambre <raul@tambre.ee>
+Rastislav Vašička <rastislav.vasicka@codetech.cc>
 Raveendra Karu <r.karu@samsung.com>
 Ravi Nanjundappa <nravi.n@samsung.com>
 Ravi Phaneendra Kasibhatla <r.kasibhatla@samsung.com>
@@ -1127,6 +1140,7 @@
 Ross Kirsling <rkirsling@gmail.com>
 Ross Wollman <ross.wollman@gmail.com>
 Roy Le <royle0502@gmail.com>
+Roy Le <royle@tencent.com>
 Ruan Beihong <ruanbeihong@gmail.com>
 ruben <chromium@hybridsource.org>
 Ruben Bridgewater <ruben@bridgewater.de>
@@ -1188,6 +1202,7 @@
 Seokho Song <0xdevssh@gmail.com>
 SeongTae Jeong <ferendevelop.gl@gmail.com>
 Sergei Romanov <rsv.981@gmail.com>
+Sergey Romanov <svromanov@sberdevices.ru>
 Sergey Kipet <sergey.kipet@gmail.com>
 Sergey Putilin <p.sergey@samsung.com>
 Sergey Shekyan <shekyan@gmail.com>
@@ -1212,6 +1227,8 @@
 Shen Yu <shenyu.tcv@gmail.com>
 Sherry Mou <wenjinm@amazon.com>
 Shez Baig <sbaig1@bloomberg.net>
+Shi Chunlong <shicl@dingdao.com>
+Shi Chunlong <shichunlong@gmail.com>
 Shigeki Ohtsu <shigeki.ohtsu@gmail.com>
 Shicheng Wang <wangshicheng@xiaomi.com>
 Shiliu Wang <aofdwsl@gmail.com>
@@ -1321,6 +1338,7 @@
 Teodora Novkovic <teodora.petrovic@gmail.com>
 Thiago Farina <thiago.farina@gmail.com>
 Thiago Marcos P. Santos <thiago.santos@intel.com>
+Thirumurugan <thiruak1024@gmail.com>
 Thomas Butter <tbutter@gmail.com>
 Thomas Conti <tomc@amazon.com>
 Thomas Nguyen <haitung.nguyen@avast.com>
@@ -1344,6 +1362,7 @@
 Tom Callaway <tcallawa@redhat.com>
 Tom Harwood <tfh@skip.org>
 Tomas Popela <tomas.popela@gmail.com>
+Tomasz Edward Posłuszny <tom@devpeer.net>
 Tony Shen <legendmastertony@gmail.com>
 Torsten Kurbad <google@tk-webart.de>
 Toshihito Kikuchi <leamovret@gmail.com>
@@ -1353,7 +1372,9 @@
 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 Jones <tylerjdev@github.com>
 U. Artie Eoff <ullysses.a.eoff@intel.com>
 Umar Hansa <umar.hansa@gmail.com>
 Upendra Gowda <upendrag.gowda@gmail.com>
@@ -1374,6 +1395,7 @@
 Victor Costan <costan@gmail.com>
 Victor Solonsky <victor.solonsky@gmail.com>
 Viet-Trung Luu <viettrungluu@gmail.com>
+Vikas Mundra <vikas.mundra@samsung.com>
 Vinay Anantharaman <vinaya@adobe.com>
 Vinoth Chandar <vinoth@uber.com>
 Vipul Bhasin <vipul.bhasin@gmail.com>
@@ -1591,7 +1613,6 @@
 Torchmobile Inc. Upwork <*@cloud.upwork.com>
 Venture 3 Systems LLC <*@venture3systems.com>
 Vewd Software AS <*@vewd.com>
-Vikas Mundra <vikas.mundra@samsung.com>
 Vivaldi Technologies AS <*@vivaldi.com>
 Wacom <*@wacom.com>
 Whist Technologies <*@whist.com>
diff --git a/README.md b/README.md
index 5965c48..6d5d2b6 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 [QUICHE](https://quiche.googlesource.com/quiche/+/refs/heads/master), but can be
 also used by other projects that use Bazel.
 
-In order to be used successfully, C++14 or later and `-fno-strict-aliasing`
+In order to be used successfully, C++20 or later and `-fno-strict-aliasing`
 compile flag are required.
 
 For questions, contact <proto-quic@chromium.org>.
@@ -17,8 +17,8 @@
 the following commands in the root of the checkout:
 
 1. `copybara copy.bara.sky import <path-to-chrome>/src --folder-dir .`
-1. `bazel test --cxxopt="-std=c++17" //...`
-   (C++17 is replacible with later C++ versions)
+1. `bazel test --cxxopt="-std=c++20" //...`
+   (C++20 is replacible with later C++ versions)
 1. Fix all of the compilation errors, potentially modifying the BUILD files and
    the polyfill headers in `polyfill/` as appropriate.
 1. Check the new version into Git.
diff --git a/base/BUILD b/base/BUILD
index 1d1efd2..eda7352 100644
--- a/base/BUILD
+++ b/base/BUILD
@@ -26,7 +26,6 @@
         "containers/span.h",
         "containers/util.h",
         "cxx20_is_constant_evaluated.h",
-        "cxx20_to_address.h",
         "debug/crash_logging.h",
         "debug/leak_annotations.h",
         "functional/identity.h",
@@ -51,7 +50,7 @@
         "stl_util.h",
         "template_util.h",
         "types/always_false.h",
-        "strings/string_piece_forward.h",
+        "types/supports_ostream_operator.h",
         "strings/string_piece.h",
         "strings/string_util.h",
         "strings/string_util_impl_helpers.h",
diff --git a/base/bits.h b/base/bits.h
index 3f1541a..6a30878 100644
--- a/base/bits.h
+++ b/base/bits.h
@@ -22,7 +22,7 @@
 // Returns true iff |value| is a power of 2.
 //
 // TODO(pkasting): When C++20 is available, replace with std::has_single_bit().
-template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
+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.
   //
@@ -42,7 +42,7 @@
 
 // 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 = typename std::enable_if<sizeof(T) == 1>::type>
+template <typename T, typename = std::enable_if_t<sizeof(T) == 1>>
 inline T* AlignDown(T* ptr, uintptr_t alignment) {
   return reinterpret_cast<T*>(
       AlignDown(reinterpret_cast<uintptr_t>(ptr), alignment));
@@ -57,7 +57,7 @@
 
 // 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 = typename std::enable_if<sizeof(T) == 1>::type>
+template <typename T, typename = std::enable_if_t<sizeof(T) == 1>>
 inline T* AlignUp(T* ptr, uintptr_t alignment) {
   return reinterpret_cast<T*>(
       AlignUp(reinterpret_cast<uintptr_t>(ptr), alignment));
@@ -85,8 +85,7 @@
 // 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<T>::value && sizeof(T) <= 8,
-                            int>::type
+    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)
@@ -98,8 +97,7 @@
 
 template <typename T, int bits = sizeof(T) * 8>
 ALWAYS_INLINE constexpr
-    typename std::enable_if<std::is_unsigned<T>::value && sizeof(T) <= 8,
-                            int>::type
+    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))
@@ -130,7 +128,7 @@
 // Can be used instead of manually shifting a 1 to the left.
 template <typename T>
 constexpr T LeftmostBit() {
-  static_assert(std::is_integral<T>::value,
+  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);
diff --git a/base/compiler_specific.h b/base/compiler_specific.h
index 5f7c31e..c8cb72a 100644
--- a/base/compiler_specific.h
+++ b/base/compiler_specific.h
@@ -51,6 +51,15 @@
 #define NOINLINE
 #endif
 
+// Annotate a function indicating it should not be optimized.
+#if defined(__clang__) && HAS_ATTRIBUTE(optnone)
+#define NOOPT [[clang::optnone]]
+#elif defined(COMPILER_GCC) && HAS_ATTRIBUTE(optimize)
+#define NOOPT __attribute__((optimize(0)))
+#else
+#define NOOPT
+#endif
+
 #if defined(__clang__) && defined(NDEBUG) && HAS_ATTRIBUTE(always_inline)
 #define ALWAYS_INLINE [[clang::always_inline]] inline
 #elif defined(COMPILER_GCC) && defined(NDEBUG) && HAS_ATTRIBUTE(always_inline)
@@ -101,7 +110,13 @@
 // References:
 // * https://en.cppreference.com/w/cpp/language/attributes/no_unique_address
 // * https://wg21.link/dcl.attr.nouniqueaddr
-#if HAS_CPP_ATTRIBUTE(no_unique_address)
+#if defined(COMPILER_MSVC) && HAS_CPP_ATTRIBUTE(msvc::no_unique_address)
+// 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.
+#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
+#elif HAS_CPP_ATTRIBUTE(no_unique_address)
 #define NO_UNIQUE_ADDRESS [[no_unique_address]]
 #else
 #define NO_UNIQUE_ADDRESS
diff --git a/base/containers/checked_iterators.h b/base/containers/checked_iterators.h
index 18f1006..d5a27ff 100644
--- a/base/containers/checked_iterators.h
+++ b/base/containers/checked_iterators.h
@@ -58,7 +58,7 @@
   // See https://wg21.link/n4042 for details.
   template <
       typename U,
-      std::enable_if_t<std::is_convertible<U (*)[], T (*)[]>::value>* = nullptr>
+      std::enable_if_t<std::is_convertible_v<U (*)[], T (*)[]>>* = nullptr>
   constexpr CheckedContiguousIterator(const CheckedContiguousIterator<U>& other)
       : start_(other.start_), current_(other.current_), end_(other.end_) {
     // We explicitly don't delegate to the 3-argument constructor here. Its
diff --git a/base/containers/span.h b/base/containers/span.h
index 09f6b6f..059a0c1 100644
--- a/base/containers/span.h
+++ b/base/containers/span.h
@@ -11,6 +11,7 @@
 #include <array>
 #include <iterator>
 #include <limits>
+#include <memory>
 #include <type_traits>
 #include <utility>
 
@@ -18,9 +19,8 @@
 #include "base/compiler_specific.h"
 #include "base/containers/checked_iterators.h"
 #include "base/containers/contiguous_iterator.h"
-#include "base/cxx20_to_address.h"
-#include "polyfills/base/memory/raw_ptr_exclusion.h"
-#include "base/numerics/safe_math.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/template_util.h"
 
 namespace gurl_base {
 
@@ -82,8 +82,8 @@
 
 template <typename Iter, typename T>
 using EnableIfCompatibleContiguousIterator = std::enable_if_t<
-    std::conjunction<IsContiguousIterator<Iter>,
-                     IteratorHasConvertibleReferenceType<Iter, T>>::value>;
+    std::conjunction_v<IsContiguousIterator<Iter>,
+                       IteratorHasConvertibleReferenceType<Iter, T>>>;
 
 template <typename Container, typename T>
 using ContainerHasConvertibleData = IsLegalDataConversion<
@@ -164,8 +164,8 @@
 // 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 StringPiece, but with arbitrary element types,
-// allowing mutation if T is non-const.
+// 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
@@ -228,6 +228,9 @@
 //   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.
 //
@@ -281,15 +284,14 @@
         // 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_(gurl_base::to_address(first)) {
+        data_(std::to_address(first)) {
     GURL_CHECK(Extent == dynamic_extent || Extent == count);
   }
 
-  template <
-      typename It,
-      typename End,
-      typename = internal::EnableIfCompatibleContiguousIterator<It, T>,
-      typename = std::enable_if_t<!std::is_convertible<End, size_t>::value>>
+  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.
@@ -489,15 +491,16 @@
 
 // [span.objectrep], views of object representation
 template <typename T, size_t X>
-span<const uint8_t, (X == dynamic_extent ? dynamic_extent : sizeof(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()};
 }
 
 template <typename T,
           size_t X,
-          typename = std::enable_if_t<!std::is_const<T>::value>>
-span<uint8_t, (X == dynamic_extent ? dynamic_extent : sizeof(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()};
 }
@@ -561,6 +564,16 @@
   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.
+template <typename T>
+inline span<const uint8_t> as_byte_span(const T& arg) {
+  return as_bytes(make_span(arg));
+}
+
 }  // namespace base
 
 // EXTENT returns the size of any type that can be converted to a |gurl_base::span|
diff --git a/base/cxx20_to_address.h b/base/cxx20_to_address.h
deleted file mode 100644
index 04d0f71..0000000
--- a/base/cxx20_to_address.h
+++ /dev/null
@@ -1,50 +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_CXX20_TO_ADDRESS_H_
-#define BASE_CXX20_TO_ADDRESS_H_
-
-#include <memory>
-#include <type_traits>
-
-namespace gurl_base {
-
-namespace {
-
-template <typename Ptr, typename = void>
-struct has_std_to_address : std::false_type {};
-
-template <typename Ptr>
-struct has_std_to_address<
-    Ptr,
-    std::void_t<decltype(std::pointer_traits<Ptr>::to_address(
-        std::declval<Ptr>()))>> : std::true_type {};
-
-}  // namespace
-
-// Implementation of C++20's std::to_address.
-// Note: This does consider specializations of pointer_traits<>::to_address,
-// even though it's a C++20 member function, because CheckedContiguousIterator
-// specializes pointer_traits<> with a to_address() member.
-//
-// Reference: https://wg21.link/pointer.conversion#lib:to_address
-template <typename T>
-constexpr T* to_address(T* p) noexcept {
-  static_assert(!std::is_function<T>::value,
-                "Error: T must not be a function type.");
-  return p;
-}
-
-template <typename Ptr>
-constexpr auto to_address(const Ptr& p) noexcept {
-  if constexpr (has_std_to_address<Ptr>::value) {
-    return std::pointer_traits<Ptr>::to_address(p);
-  } else {
-    return gurl_base::to_address(p.operator->());
-  }
-}
-
-}  // namespace base
-
-#endif  // BASE_CXX20_TO_ADDRESS_H_
diff --git a/base/debug/crash_logging.h b/base/debug/crash_logging.h
index 381a13d..b7073a5 100644
--- a/base/debug/crash_logging.h
+++ b/base/debug/crash_logging.h
@@ -172,7 +172,7 @@
                                    ::gurl_base::debug::CrashKeySize::Size1024)
 
 #define SCOPED_CRASH_KEY_BOOL(category, name, data)                       \
-  static_assert(std::is_same<std::decay_t<decltype(data)>, bool>::value,  \
+  static_assert(std::is_same_v<std::decay_t<decltype(data)>, bool>,       \
                 "SCOPED_CRASH_KEY_BOOL must be passed a boolean value."); \
   SCOPED_CRASH_KEY_STRING32(category, name, (data) ? "true" : "false")
 
diff --git a/base/functional/invoke.h b/base/functional/invoke.h
index 96dd088..c2161ce 100644
--- a/base/functional/invoke.h
+++ b/base/functional/invoke.h
@@ -34,17 +34,15 @@
 
 // Small helpers used below in internal::invoke to make the SFINAE more concise.
 template <typename F>
-const bool& IsMemFunPtr =
-    std::is_member_function_pointer<std::decay_t<F>>::value;
+const bool& IsMemFunPtr = std::is_member_function_pointer_v<std::decay_t<F>>;
 
 template <typename F>
-const bool& IsMemObjPtr = std::is_member_object_pointer<std::decay_t<F>>::value;
+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<MemPtrClass, std::decay_t<T>>::value;
+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;
diff --git a/base/numerics/checked_math.h b/base/numerics/checked_math.h
index 0e6ad4f..11156db 100644
--- a/base/numerics/checked_math.h
+++ b/base/numerics/checked_math.h
@@ -17,7 +17,7 @@
 
 template <typename T>
 class CheckedNumeric {
-  static_assert(std::is_arithmetic<T>::value,
+  static_assert(std::is_arithmetic_v<T>,
                 "CheckedNumeric<T>: T must be a numeric type.");
 
  public:
@@ -42,7 +42,7 @@
   // 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<Src>::value>>
+            typename = std::enable_if_t<std::is_arithmetic_v<Src>>>
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr CheckedNumeric(Src value) : state_(value) {}
 
@@ -144,14 +144,14 @@
 
   constexpr CheckedNumeric operator-() const {
     // Use an optimized code path for a known run-time variable.
-    if (!IsConstantEvaluated() && std::is_signed<T>::value &&
-        std::is_floating_point<T>::value) {
+    if (!IsConstantEvaluated() && std::is_signed_v<T> &&
+        std::is_floating_point_v<T>) {
       return FastRuntimeNegate();
     }
     // The negation of two's complement int min is int min.
     const bool is_valid =
         IsValid() &&
-        (!std::is_signed<T>::value || std::is_floating_point<T>::value ||
+        (!std::is_signed_v<T> || std::is_floating_point_v<T> ||
          NegateWrapper(state_.value()) != std::numeric_limits<T>::lowest());
     return CheckedNumeric<T>(NegateWrapper(state_.value()), is_valid);
   }
diff --git a/base/numerics/checked_math_impl.h b/base/numerics/checked_math_impl.h
index 30dd8e1..abba503 100644
--- a/base/numerics/checked_math_impl.h
+++ b/base/numerics/checked_math_impl.h
@@ -22,7 +22,7 @@
 
 template <typename T>
 constexpr bool CheckedAddImpl(T x, T y, T* result) {
-  static_assert(std::is_integral<T>::value, "Type must be integral");
+  static_assert(std::is_integral_v<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;
@@ -32,10 +32,11 @@
   const UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
   // Addition is valid if the sign of (x + y) is equal to either that of x or
   // that of y.
-  if (std::is_signed<T>::value
+  if (std::is_signed_v<T>
           ? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) < 0
-          : uresult < uy)  // Unsigned is either valid or underflow.
+          : uresult < uy) {  // Unsigned is either valid or underflow.
     return false;
+  }
   *result = static_cast<T>(uresult);
   return true;
 }
@@ -44,10 +45,10 @@
 struct CheckedAddOp {};
 
 template <typename T, typename U>
-struct CheckedAddOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
@@ -85,7 +86,7 @@
 
 template <typename T>
 constexpr bool CheckedSubImpl(T x, T y, T* result) {
-  static_assert(std::is_integral<T>::value, "Type must be integral");
+  static_assert(std::is_integral_v<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;
@@ -95,10 +96,11 @@
   const UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
   // Subtraction is valid if either x and y have same sign, or (x-y) and x have
   // the same sign.
-  if (std::is_signed<T>::value
+  if (std::is_signed_v<T>
           ? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) < 0
-          : x < y)
+          : x < y) {
     return false;
+  }
   *result = static_cast<T>(uresult);
   return true;
 }
@@ -107,10 +109,10 @@
 struct CheckedSubOp {};
 
 template <typename T, typename U>
-struct CheckedSubOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
@@ -148,7 +150,7 @@
 
 template <typename T>
 constexpr bool CheckedMulImpl(T x, T y, T* result) {
-  static_assert(std::is_integral<T>::value, "Type must be integral");
+  static_assert(std::is_integral_v<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;
@@ -157,13 +159,14 @@
   const UnsignedDst uy = SafeUnsignedAbs(y);
   const UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
   const bool is_negative =
-      std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
+      std::is_signed_v<T> && static_cast<SignedDst>(x ^ y) < 0;
   // We have a fast out for unsigned identity or zero on the second operand.
   // After that it's an unsigned overflow check on the absolute value, with
   // a +1 bound for a negative result.
-  if (uy > UnsignedDst(!std::is_signed<T>::value || is_negative) &&
-      ux > (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy)
+  if (uy > UnsignedDst(!std::is_signed_v<T> || is_negative) &&
+      ux > (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy) {
     return false;
+  }
   *result = static_cast<T>(is_negative ? 0 - uresult : uresult);
   return true;
 }
@@ -172,10 +175,10 @@
 struct CheckedMulOp {};
 
 template <typename T, typename U>
-struct CheckedMulOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
@@ -217,10 +220,10 @@
 struct CheckedDivOp {};
 
 template <typename T, typename U>
-struct CheckedDivOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
@@ -231,7 +234,7 @@
     // combination of types needed to trigger this case.
     using Promotion = typename BigEnoughPromotion<T, U>::type;
     if (BASE_NUMERICS_UNLIKELY(
-            (std::is_signed<T>::value && std::is_signed<U>::value &&
+            (std::is_signed_v<T> && std::is_signed_v<U> &&
              IsTypeInRangeForNumericType<T, Promotion>::value &&
              static_cast<Promotion>(x) ==
                  std::numeric_limits<Promotion>::lowest() &&
@@ -258,10 +261,10 @@
 struct CheckedModOp {};
 
 template <typename T, typename U>
-struct CheckedModOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
@@ -270,7 +273,7 @@
 
     using Promotion = typename BigEnoughPromotion<T, U>::type;
     if (BASE_NUMERICS_UNLIKELY(
-            (std::is_signed<T>::value && std::is_signed<U>::value &&
+            (std::is_signed_v<T> && std::is_signed_v<U> &&
              IsTypeInRangeForNumericType<T, Promotion>::value &&
              static_cast<Promotion>(x) ==
                  std::numeric_limits<Promotion>::lowest() &&
@@ -295,10 +298,10 @@
 // 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,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+struct CheckedLshOp<
+    T,
+    U,
+    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
   using result_type = T;
   template <typename V>
   static constexpr bool Do(T x, U shift, V* result) {
@@ -313,9 +316,10 @@
     }
 
     // Handle the legal corner-case of a full-width signed shift of zero.
-    if (!std::is_signed<T>::value || x ||
-        as_unsigned(shift) != as_unsigned(std::numeric_limits<T>::digits))
+    if (!std::is_signed_v<T> || x ||
+        as_unsigned(shift) != as_unsigned(std::numeric_limits<T>::digits)) {
       return false;
+    }
     *result = 0;
     return true;
   }
@@ -328,10 +332,10 @@
 // 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,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+struct CheckedRshOp<
+    T,
+    U,
+    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
   using result_type = T;
   template <typename V>
   static constexpr bool Do(T x, U shift, V* result) {
@@ -354,10 +358,10 @@
 
 // For simplicity we support only unsigned integer results.
 template <typename T, typename U>
-struct CheckedAndOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
@@ -376,10 +380,10 @@
 
 // For simplicity we support only unsigned integers.
 template <typename T, typename U>
-struct CheckedOrOp<T,
-                   U,
-                   typename std::enable_if<std::is_integral<T>::value &&
-                                           std::is_integral<U>::value>::type> {
+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;
   template <typename V>
@@ -398,10 +402,10 @@
 
 // For simplicity we support only unsigned integers.
 template <typename T, typename U>
-struct CheckedXorOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
@@ -424,8 +428,7 @@
 struct CheckedMaxOp<
     T,
     U,
-    typename std::enable_if<std::is_arithmetic<T>::value &&
-                            std::is_arithmetic<U>::value>::type> {
+    std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>>> {
   using result_type = typename MaxExponentPromotion<T, U>::type;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
@@ -448,8 +451,7 @@
 struct CheckedMinOp<
     T,
     U,
-    typename std::enable_if<std::is_arithmetic<T>::value &&
-                            std::is_arithmetic<U>::value>::type> {
+    std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>>> {
   using result_type = typename LowestValuePromotion<T, U>::type;
   template <typename V>
   static constexpr bool Do(T x, U y, V* result) {
@@ -465,22 +467,21 @@
 
 // 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,                                                              \
-      typename std::enable_if<std::is_floating_point<T>::value ||        \
-                              std::is_floating_point<U>::value>::type> { \
-    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>                                         \
+  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;                                                          \
+    }                                                                       \
   };
 
 BASE_FLOAT_ARITHMETIC_OPS(Add, +)
@@ -502,10 +503,10 @@
 template <typename NumericType>
 struct GetNumericRepresentation {
   static const NumericRepresentation value =
-      std::is_integral<NumericType>::value
+      std::is_integral_v<NumericType>
           ? NUMERIC_INTEGER
-          : (std::is_floating_point<NumericType>::value ? NUMERIC_FLOATING
-                                                        : NUMERIC_UNKNOWN);
+          : (std::is_floating_point_v<NumericType> ? NUMERIC_FLOATING
+                                                   : NUMERIC_UNKNOWN);
 };
 
 template <typename T,
@@ -520,7 +521,7 @@
   constexpr explicit CheckedNumericState(Src value = 0, bool is_valid = true)
       : is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
         value_(WellDefinedConversionOrZero(value, is_valid_)) {
-    static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+    static_assert(std::is_arithmetic_v<Src>, "Argument must be numeric.");
   }
 
   template <typename Src>
@@ -536,9 +537,8 @@
   template <typename Src>
   static constexpr T WellDefinedConversionOrZero(Src value, bool is_valid) {
     using SrcType = typename internal::UnderlyingType<Src>::type;
-    return (std::is_integral<SrcType>::value || is_valid)
-               ? static_cast<T>(value)
-               : 0;
+    return (std::is_integral_v<SrcType> || is_valid) ? static_cast<T>(value)
+                                                     : 0;
   }
 
   // is_valid_ precedes value_ because member initializers in the constructors
diff --git a/base/numerics/clamped_math.h b/base/numerics/clamped_math.h
index fe6b0fe..a4cbea4 100644
--- a/base/numerics/clamped_math.h
+++ b/base/numerics/clamped_math.h
@@ -17,7 +17,7 @@
 
 template <typename T>
 class ClampedNumeric {
-  static_assert(std::is_arithmetic<T>::value,
+  static_assert(std::is_arithmetic_v<T>,
                 "ClampedNumeric<T>: T must be a numeric type.");
 
  public:
diff --git a/base/numerics/clamped_math_impl.h b/base/numerics/clamped_math_impl.h
index 10023f0..8533046 100644
--- a/base/numerics/clamped_math_impl.h
+++ b/base/numerics/clamped_math_impl.h
@@ -21,9 +21,9 @@
 namespace gurl_base {
 namespace internal {
 
-template <typename T,
-          typename std::enable_if<std::is_integral<T>::value &&
-                                  std::is_signed<T>::value>::type* = nullptr>
+template <
+    typename T,
+    std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>>* = nullptr>
 constexpr T SaturatedNegWrapper(T value) {
   return IsConstantEvaluated() || !ClampedNegFastOp<T>::is_supported
              ? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
@@ -32,22 +32,19 @@
              : ClampedNegFastOp<T>::Do(value);
 }
 
-template <typename T,
-          typename std::enable_if<std::is_integral<T>::value &&
-                                  !std::is_signed<T>::value>::type* = nullptr>
+template <
+    typename T,
+    std::enable_if_t<std::is_integral_v<T> && !std::is_signed_v<T>>* = nullptr>
 constexpr T SaturatedNegWrapper(T value) {
   return T(0);
 }
 
-template <
-    typename T,
-    typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
 constexpr T SaturatedNegWrapper(T value) {
   return -value;
 }
 
-template <typename T,
-          typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
 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.
@@ -62,9 +59,7 @@
       IsValueNegative<T>(static_cast<T>(SafeUnsignedAbs(value))));
 }
 
-template <
-    typename T,
-    typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
 constexpr T SaturatedAbsWrapper(T value) {
   return value < 0 ? -value : value;
 }
@@ -73,17 +68,17 @@
 struct ClampedAddOp {};
 
 template <typename T, typename U>
-struct ClampedAddOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
     if (!IsConstantEvaluated() && ClampedAddFastOp<T, U>::is_supported)
       return ClampedAddFastOp<T, U>::template Do<V>(x, y);
 
-    static_assert(std::is_same<V, result_type>::value ||
+    static_assert(std::is_same_v<V, result_type> ||
                       IsTypeInRangeForNumericType<U, V>::value,
                   "The saturation result cannot be determined from the "
                   "provided types.");
@@ -99,17 +94,17 @@
 struct ClampedSubOp {};
 
 template <typename T, typename U>
-struct ClampedSubOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
     if (!IsConstantEvaluated() && ClampedSubFastOp<T, U>::is_supported)
       return ClampedSubFastOp<T, U>::template Do<V>(x, y);
 
-    static_assert(std::is_same<V, result_type>::value ||
+    static_assert(std::is_same_v<V, result_type> ||
                       IsTypeInRangeForNumericType<U, V>::value,
                   "The saturation result cannot be determined from the "
                   "provided types.");
@@ -125,10 +120,10 @@
 struct ClampedMulOp {};
 
 template <typename T, typename U>
-struct ClampedMulOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
@@ -148,10 +143,10 @@
 struct ClampedDivOp {};
 
 template <typename T, typename U>
-struct ClampedDivOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
@@ -168,10 +163,10 @@
 struct ClampedModOp {};
 
 template <typename T, typename U>
-struct ClampedModOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
@@ -188,14 +183,14 @@
 // 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,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+struct ClampedLshOp<
+    T,
+    U,
+    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
   using result_type = T;
   template <typename V = result_type>
   static constexpr V Do(T x, U shift) {
-    static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
+    static_assert(!std::is_signed_v<U>, "Shift value must be unsigned.");
     if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
       // Shift as unsigned to avoid undefined behavior.
       V result = static_cast<V>(as_unsigned(x) << shift);
@@ -212,14 +207,14 @@
 
 // Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
 template <typename T, typename U>
-struct ClampedRshOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+struct ClampedRshOp<
+    T,
+    U,
+    std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>> {
   using result_type = T;
   template <typename V = result_type>
   static constexpr V Do(T x, U shift) {
-    static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
+    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)
@@ -232,10 +227,10 @@
 struct ClampedAndOp {};
 
 template <typename T, typename U>
-struct ClampedAndOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
@@ -249,10 +244,10 @@
 
 // For simplicity we promote to unsigned integers.
 template <typename T, typename U>
-struct ClampedOrOp<T,
-                   U,
-                   typename std::enable_if<std::is_integral<T>::value &&
-                                           std::is_integral<U>::value>::type> {
+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;
   template <typename V>
@@ -266,10 +261,10 @@
 
 // For simplicity we support only unsigned integers.
 template <typename T, typename U>
-struct ClampedXorOp<T,
-                    U,
-                    typename std::enable_if<std::is_integral<T>::value &&
-                                            std::is_integral<U>::value>::type> {
+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;
   template <typename V>
@@ -285,8 +280,7 @@
 struct ClampedMaxOp<
     T,
     U,
-    typename std::enable_if<std::is_arithmetic<T>::value &&
-                            std::is_arithmetic<U>::value>::type> {
+    std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>>> {
   using result_type = typename MaxExponentPromotion<T, U>::type;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
@@ -302,8 +296,7 @@
 struct ClampedMinOp<
     T,
     U,
-    typename std::enable_if<std::is_arithmetic<T>::value &&
-                            std::is_arithmetic<U>::value>::type> {
+    std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>>> {
   using result_type = typename LowestValuePromotion<T, U>::type;
   template <typename V = result_type>
   static constexpr V Do(T x, U y) {
@@ -314,17 +307,16 @@
 
 // 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,                                                              \
-      typename std::enable_if<std::is_floating_point<T>::value ||        \
-                              std::is_floating_point<U>::value>::type> { \
-    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>                                         \
+  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);                                     \
+    }                                                                       \
   };
 
 BASE_FLOAT_ARITHMETIC_OPS(Add, +)
diff --git a/base/numerics/ranges.h b/base/numerics/ranges.h
index 98085eb..af4c21c 100644
--- a/base/numerics/ranges.h
+++ b/base/numerics/ranges.h
@@ -12,7 +12,7 @@
 
 template <typename T>
 constexpr bool IsApproximatelyEqual(T lhs, T rhs, T tolerance) {
-  static_assert(std::is_arithmetic<T>::value, "Argument must be arithmetic");
+  static_assert(std::is_arithmetic_v<T>, "Argument must be arithmetic");
   return std::abs(rhs - lhs) <= tolerance;
 }
 
diff --git a/base/numerics/safe_conversions.h b/base/numerics/safe_conversions.h
index 7f12916..d09237e 100644
--- a/base/numerics/safe_conversions.h
+++ b/base/numerics/safe_conversions.h
@@ -51,10 +51,9 @@
 struct IsValueInRangeFastOp<
     Dst,
     Src,
-    typename std::enable_if<
-        std::is_integral<Dst>::value && std::is_integral<Src>::value &&
-        std::is_signed<Dst>::value && std::is_signed<Src>::value &&
-        !IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
+    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>> {
   static constexpr bool is_supported = true;
 
   static constexpr bool Do(Src value) {
@@ -69,10 +68,9 @@
 struct IsValueInRangeFastOp<
     Dst,
     Src,
-    typename std::enable_if<
-        std::is_integral<Dst>::value && std::is_integral<Src>::value &&
-        !std::is_signed<Dst>::value && std::is_signed<Src>::value &&
-        !IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
+    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>> {
   static constexpr bool is_supported = true;
 
   static constexpr bool Do(Src value) {
@@ -148,7 +146,7 @@
              ? (!constraint.IsUnderflowFlagSet() ? static_cast<Dst>(value)
                                                  : S<Dst>::Underflow())
              // Skip this check for integral Src, which cannot be NaN.
-             : (std::is_integral<Src>::value || !constraint.IsUnderflowFlagSet()
+             : (std::is_integral_v<Src> || !constraint.IsUnderflowFlagSet()
                     ? S<Dst>::Overflow()
                     : S<Dst>::NaN());
 }
@@ -169,9 +167,8 @@
 struct SaturateFastOp<
     Dst,
     Src,
-    typename std::enable_if<std::is_integral<Src>::value &&
-                            std::is_integral<Dst>::value &&
-                            SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
+    std::enable_if_t<std::is_integral_v<Src> && std::is_integral_v<Dst> &&
+                     SaturateFastAsmOp<Dst, Src>::is_supported>> {
   static constexpr bool is_supported = true;
   static constexpr Dst Do(Src value) {
     return SaturateFastAsmOp<Dst, Src>::Do(value);
@@ -182,9 +179,8 @@
 struct SaturateFastOp<
     Dst,
     Src,
-    typename std::enable_if<std::is_integral<Src>::value &&
-                            std::is_integral<Dst>::value &&
-                            !SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
+    std::enable_if_t<std::is_integral_v<Src> && std::is_integral_v<Dst> &&
+                     !SaturateFastAsmOp<Dst, Src>::is_supported>> {
   static constexpr bool is_supported = true;
   static constexpr Dst Do(Src value) {
     // The exact order of the following is structured to hit the correct
@@ -209,8 +205,8 @@
 constexpr Dst saturated_cast(Src value) {
   using SrcType = typename UnderlyingType<Src>::type;
   return !IsConstantEvaluated() && SaturateFastOp<Dst, SrcType>::is_supported &&
-                 std::is_same<SaturationHandler<Dst>,
-                              SaturationDefaultLimits<Dst>>::value
+                 std::is_same_v<SaturationHandler<Dst>,
+                                SaturationDefaultLimits<Dst>>
              ? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
              : saturated_cast_impl<Dst, SaturationHandler, SrcType>(
                    static_cast<SrcType>(value),
@@ -225,7 +221,7 @@
 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<Dst>::value, "Result 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.
@@ -251,8 +247,8 @@
 struct IsNumericRangeContained<
     Dst,
     Src,
-    typename std::enable_if<ArithmeticOrUnderlyingEnum<Dst>::value &&
-                            ArithmeticOrUnderlyingEnum<Src>::value>::type> {
+    std::enable_if_t<ArithmeticOrUnderlyingEnum<Dst>::value &&
+                     ArithmeticOrUnderlyingEnum<Src>::value>> {
   static constexpr bool value =
       StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
       NUMERIC_RANGE_CONTAINED;
@@ -305,8 +301,7 @@
   // 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,
-            typename std::enable_if<
-                IsNumericRangeContained<Dst, T>::value>::type* = nullptr>
+            std::enable_if_t<IsNumericRangeContained<Dst, T>::value>* = nullptr>
   constexpr operator Dst() const {
     return static_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(value_);
   }
@@ -323,13 +318,12 @@
   return value;
 }
 
-#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP)              \
-  template <typename L, typename R,                                     \
-            typename std::enable_if<                                    \
-                internal::Is##CLASS##Op<L, R>::value>::type* = 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,                                            \
+            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);            \
   }
 
 BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <)
@@ -371,8 +365,8 @@
 // Rounds towards negative infinity (i.e., down).
 template <typename Dst = int,
           typename Src,
-          typename = std::enable_if_t<std::is_integral<Dst>::value &&
-                                      std::is_floating_point<Src>::value>>
+          typename = std::enable_if_t<std::is_integral_v<Dst> &&
+                                      std::is_floating_point_v<Src>>>
 Dst ClampFloor(Src value) {
   return saturated_cast<Dst>(std::floor(value));
 }
@@ -380,8 +374,8 @@
 // Rounds towards positive infinity (i.e., up).
 template <typename Dst = int,
           typename Src,
-          typename = std::enable_if_t<std::is_integral<Dst>::value &&
-                                      std::is_floating_point<Src>::value>>
+          typename = std::enable_if_t<std::is_integral_v<Dst> &&
+                                      std::is_floating_point_v<Src>>>
 Dst ClampCeil(Src value) {
   return saturated_cast<Dst>(std::ceil(value));
 }
@@ -397,8 +391,8 @@
 // -1.5 to -2.
 template <typename Dst = int,
           typename Src,
-          typename = std::enable_if_t<std::is_integral<Dst>::value &&
-                                      std::is_floating_point<Src>::value>>
+          typename = std::enable_if_t<std::is_integral_v<Dst> &&
+                                      std::is_floating_point_v<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 176af95..3e7d848 100644
--- a/base/numerics/safe_conversions_arm_impl.h
+++ b/base/numerics/safe_conversions_arm_impl.h
@@ -18,17 +18,17 @@
 template <typename Dst, typename Src>
 struct SaturateFastAsmOp {
   static constexpr bool is_supported =
-      kEnableAsmCode && std::is_signed<Src>::value &&
-      std::is_integral<Dst>::value && std::is_integral<Src>::value &&
+      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;
 
   __attribute__((always_inline)) static Dst Do(Src value) {
     int32_t src = value;
-    typename std::conditional<std::is_signed<Dst>::value, int32_t,
-                              uint32_t>::type result;
-    if (std::is_signed<Dst>::value) {
+    typename std::conditional<std::is_signed_v<Dst>, int32_t, uint32_t>::type
+        result;
+    if (std::is_signed_v<Dst>) {
       asm("ssat %[dst], %[shift], %[src]"
           : [dst] "=r"(result)
           : [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value <= 32
diff --git a/base/numerics/safe_conversions_impl.h b/base/numerics/safe_conversions_impl.h
index abe1706..6d5450f 100644
--- a/base/numerics/safe_conversions_impl.h
+++ b/base/numerics/safe_conversions_impl.h
@@ -25,7 +25,7 @@
 // we can compute an analog using std::numeric_limits<>::digits.
 template <typename NumericType>
 struct MaxExponent {
-  static const int value = std::is_floating_point<NumericType>::value
+  static const int value = std::is_floating_point_v<NumericType>
                                ? std::numeric_limits<NumericType>::max_exponent
                                : std::numeric_limits<NumericType>::digits + 1;
 };
@@ -34,8 +34,8 @@
 // hacks.
 template <typename NumericType>
 struct IntegerBitsPlusSign {
-  static const int value = std::numeric_limits<NumericType>::digits +
-                           std::is_signed<NumericType>::value;
+  static const int value =
+      std::numeric_limits<NumericType>::digits + std::is_signed_v<NumericType>;
 };
 
 // Helper templates for integer manipulations.
@@ -47,17 +47,15 @@
 
 // Determines if a numeric value is negative without throwing compiler
 // warnings on: unsigned(value) < 0.
-template <typename T,
-          typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_signed_v<T>>* = nullptr>
 constexpr bool IsValueNegative(T value) {
-  static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
+  static_assert(std::is_arithmetic_v<T>, "Argument must be numeric.");
   return value < 0;
 }
 
-template <typename T,
-          typename std::enable_if<!std::is_signed<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<!std::is_signed_v<T>>* = nullptr>
 constexpr bool IsValueNegative(T) {
-  static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
+  static_assert(std::is_arithmetic_v<T>, "Argument must be numeric.");
   return false;
 }
 
@@ -68,7 +66,7 @@
 constexpr typename std::make_signed<T>::type ConditionalNegate(
     T x,
     bool is_negative) {
-  static_assert(std::is_integral<T>::value, "Type must be integral");
+  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;
   return static_cast<SignedT>((static_cast<UnsignedT>(x) ^
@@ -79,7 +77,7 @@
 // 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<T>::value, "Type must be integral");
+  static_assert(std::is_integral_v<T>, "Type must be integral");
   using UnsignedT = typename std::make_unsigned<T>::type;
   return IsValueNegative(value)
              ? static_cast<UnsignedT>(0u - static_cast<UnsignedT>(value))
@@ -136,10 +134,10 @@
 
 template <typename Dst,
           typename Src,
-          IntegerRepresentation DstSign = std::is_signed<Dst>::value
+          IntegerRepresentation DstSign = std::is_signed_v<Dst>
                                               ? INTEGER_REPRESENTATION_SIGNED
                                               : INTEGER_REPRESENTATION_UNSIGNED,
-          IntegerRepresentation SrcSign = std::is_signed<Src>::value
+          IntegerRepresentation SrcSign = std::is_signed_v<Src>
                                               ? INTEGER_REPRESENTATION_SIGNED
                                               : INTEGER_REPRESENTATION_UNSIGNED>
 struct StaticDstRangeRelationToSrcRange;
@@ -236,14 +234,12 @@
        SrcLimits::digits < DstLimits::digits)
           ? (DstLimits::digits - SrcLimits::digits)
           : 0;
-  template <
-      typename T,
-      typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+  template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
 
   // 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<T, Dst>::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(
@@ -252,10 +248,9 @@
   }
 
   template <typename T,
-            typename std::enable_if<std::is_floating_point<T>::value>::type* =
-                nullptr>
+            std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
   static constexpr T Adjust(T value) {
-    static_assert(std::is_same<T, Dst>::value, "");
+    static_assert(std::is_same_v<T, Dst>, "");
     static_assert(kShift == 0, "");
     return value;
   }
@@ -268,10 +263,10 @@
           typename Src,
           template <typename>
           class Bounds,
-          IntegerRepresentation DstSign = std::is_signed<Dst>::value
+          IntegerRepresentation DstSign = std::is_signed_v<Dst>
                                               ? INTEGER_REPRESENTATION_SIGNED
                                               : INTEGER_REPRESENTATION_UNSIGNED,
-          IntegerRepresentation SrcSign = std::is_signed<Src>::value
+          IntegerRepresentation SrcSign = std::is_signed_v<Src>
                                               ? INTEGER_REPRESENTATION_SIGNED
                                               : INTEGER_REPRESENTATION_UNSIGNED,
           NumericRangeRepresentation DstRange =
@@ -373,7 +368,7 @@
     bool ge_zero = false;
     // 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<Src>::value) {
+    if (std::is_floating_point_v<Src>) {
       ge_zero = value > Src(-1);
     } else {
       ge_zero = value >= Src(0);
@@ -399,8 +394,8 @@
           template <typename> class Bounds = std::numeric_limits,
           typename Src>
 constexpr RangeCheck DstRangeRelationToSrcRange(Src value) {
-  static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
-  static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
+  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);
 }
@@ -412,7 +407,7 @@
 #define INTEGER_FOR_DIGITS_AND_SIGN(I)                          \
   template <>                                                   \
   struct IntegerForDigitsAndSign<IntegerBitsPlusSign<I>::value, \
-                                 std::is_signed<I>::value> {    \
+                                 std::is_signed_v<I>> {         \
     using type = I;                                             \
   }
 
@@ -432,7 +427,7 @@
 static_assert(IntegerBitsPlusSign<intmax_t>::value == 64,
               "Max integer size not supported for this toolchain.");
 
-template <typename Integer, bool IsSigned = std::is_signed<Integer>::value>
+template <typename Integer, bool IsSigned = std::is_signed_v<Integer>>
 struct TwiceWiderInteger {
   using type =
       typename IntegerForDigitsAndSign<IntegerBitsPlusSign<Integer>::value * 2,
@@ -467,13 +462,13 @@
 template <typename Lhs,
           typename Rhs,
           ArithmeticPromotionCategory Promotion =
-              std::is_signed<Lhs>::value
-                  ? (std::is_signed<Rhs>::value
+              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<Rhs>::value
+                  : (std::is_signed_v<Rhs>
                          ? RIGHT_PROMOTION
                          : (MaxExponent<Lhs>::value < MaxExponent<Rhs>::value
                                 ? LEFT_PROMOTION
@@ -495,16 +490,15 @@
     typename Lhs,
     typename Rhs = Lhs,
     bool is_intmax_type =
-        std::is_integral<typename MaxExponentPromotion<Lhs, Rhs>::type>::value&&
-            IntegerBitsPlusSign<typename MaxExponentPromotion<Lhs, Rhs>::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>
+    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.
@@ -519,8 +513,8 @@
 struct BigEnoughPromotion<Lhs, Rhs, false, false> {
   using type =
       typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
-                                 std::is_signed<Lhs>::value ||
-                                     std::is_signed<Rhs>::value>::type;
+                                 std::is_signed_v<Lhs> ||
+                                     std::is_signed_v<Rhs>>::type;
   static const bool is_contained = true;
 };
 
@@ -538,12 +532,11 @@
 template <typename T, typename Lhs, typename Rhs = Lhs>
 struct IsIntegerArithmeticSafe {
   static const bool value =
-      !std::is_floating_point<T>::value &&
-      !std::is_floating_point<Lhs>::value &&
-      !std::is_floating_point<Rhs>::value &&
-      std::is_signed<T>::value >= std::is_signed<Lhs>::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<T>::value >= std::is_signed<Rhs>::value &&
+      std::is_signed_v<T> >= std::is_signed_v<Rhs> &&
       IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Rhs>::value);
 };
 
@@ -552,8 +545,8 @@
 template <typename Lhs,
           typename Rhs,
           bool is_promotion_possible = IsIntegerArithmeticSafe<
-              typename std::conditional<std::is_signed<Lhs>::value ||
-                                            std::is_signed<Rhs>::value,
+              typename std::conditional<std::is_signed_v<Lhs> ||
+                                            std::is_signed_v<Rhs>,
                                         intmax_t,
                                         uintmax_t>::type,
               typename MaxExponentPromotion<Lhs, Rhs>::type>::value>
@@ -563,8 +556,8 @@
 struct FastIntegerArithmeticPromotion<Lhs, Rhs, true> {
   using type =
       typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
-                                 std::is_signed<Lhs>::value ||
-                                     std::is_signed<Rhs>::value>::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;
 };
@@ -576,19 +569,19 @@
 };
 
 // Extracts the underlying type from an enum.
-template <typename T, bool is_enum = std::is_enum<T>::value>
+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<type>::value;
+  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<type>::value;
+  static const bool value = std::is_arithmetic_v<type>;
 };
 
 // The following are helper templates used in the CheckedNumeric class.
@@ -605,7 +598,7 @@
 template <typename T>
 struct UnderlyingType {
   using type = typename ArithmeticOrUnderlyingEnum<T>::type;
-  static const bool is_numeric = std::is_arithmetic<type>::value;
+  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;
@@ -669,7 +662,7 @@
 constexpr typename std::make_signed<
     typename gurl_base::internal::UnderlyingType<Src>::type>::type
 as_signed(const Src value) {
-  static_assert(std::is_integral<decltype(as_signed(value))>::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);
 }
@@ -681,7 +674,7 @@
 constexpr typename std::make_unsigned<
     typename gurl_base::internal::UnderlyingType<Src>::type>::type
 as_unsigned(const Src value) {
-  static_assert(std::is_integral<decltype(as_unsigned(value))>::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);
 }
@@ -698,7 +691,7 @@
 
 template <typename L, typename R>
 struct IsLess {
-  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+  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),
@@ -718,7 +711,7 @@
 
 template <typename L, typename R>
 struct IsLessOrEqual {
-  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+  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),
@@ -738,7 +731,7 @@
 
 template <typename L, typename R>
 struct IsGreater {
-  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+  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),
@@ -758,7 +751,7 @@
 
 template <typename L, typename R>
 struct IsGreaterOrEqual {
-  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+  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),
@@ -768,7 +761,7 @@
 
 template <typename L, typename R>
 struct IsEqual {
-  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+  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 DstRangeRelationToSrcRange<R>(lhs) ==
@@ -780,7 +773,7 @@
 
 template <typename L, typename R>
 struct IsNotEqual {
-  static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
+  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 DstRangeRelationToSrcRange<R>(lhs) !=
@@ -794,7 +787,7 @@
 // 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<L>::value && std::is_arithmetic<R>::value,
+  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;
diff --git a/base/numerics/safe_math_clang_gcc_impl.h b/base/numerics/safe_math_clang_gcc_impl.h
index a11ab06..e27bdc4 100644
--- a/base/numerics/safe_math_clang_gcc_impl.h
+++ b/base/numerics/safe_math_clang_gcc_impl.h
@@ -136,7 +136,7 @@
 
 template <typename T>
 struct ClampedNegFastOp {
-  static const bool is_supported = std::is_signed<T>::value;
+  static const bool is_supported = std::is_signed_v<T>;
   __attribute__((always_inline)) static T Do(T value) {
     // Use this when there is no assembler path available.
     if (!ClampedSubFastAsmOp<T, T>::is_supported) {
diff --git a/base/numerics/safe_math_shared_impl.h b/base/numerics/safe_math_shared_impl.h
index d0030fc..f6955a1 100644
--- a/base/numerics/safe_math_shared_impl.h
+++ b/base/numerics/safe_math_shared_impl.h
@@ -115,8 +115,8 @@
 // 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<Numeric>::value,
-          bool IsFloat = std::is_floating_point<Numeric>::value>
+          bool IsInteger = std::is_integral_v<Numeric>,
+          bool IsFloat = std::is_floating_point_v<Numeric>>
 struct UnsignedOrFloatForSize;
 
 template <typename Numeric>
@@ -134,36 +134,29 @@
 // exhibit well-defined overflow semantics and rely on the caller to detect
 // if an overflow occurred.
 
-template <typename T,
-          typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
 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,
-    typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
 constexpr T NegateWrapper(T value) {
   return -value;
 }
 
-template <typename T,
-          typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
 constexpr typename std::make_unsigned<T>::type InvertWrapper(T value) {
   return ~value;
 }
 
-template <typename T,
-          typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
 constexpr T AbsWrapper(T value) {
   return static_cast<T>(SafeUnsignedAbs(value));
 }
 
-template <
-    typename T,
-    typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
 constexpr T AbsWrapper(T value) {
   return value < 0 ? -value : value;
 }
@@ -192,8 +185,7 @@
 #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,                                            \
-            typename std::enable_if<Is##CLASS##Op<L, R>::value>::type* =       \
-                nullptr>                                                       \
+            std::enable_if_t<Is##CLASS##Op<L, R>::value>* = nullptr>           \
   constexpr CLASS##Numeric<                                                    \
       typename MathWrapper<CLASS##OP_NAME##Op, L, R>::type>                    \
   operator OP(const L lhs, const R rhs) {                                      \
diff --git a/base/numerics/wrapping_math.h b/base/numerics/wrapping_math.h
new file mode 100644
index 0000000..36cfe5b
--- /dev/null
+++ b/base/numerics/wrapping_math.h
@@ -0,0 +1,42 @@
+// 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_NUMERICS_WRAPPING_MATH_H_
+#define BASE_NUMERICS_WRAPPING_MATH_H_
+
+#include <type_traits>
+
+namespace gurl_base {
+
+// Returns `a + b` with overflow defined to wrap around, i.e. modulo 2^N where N
+// is the bit width of `T`.
+template <typename T>
+inline constexpr T WrappingAdd(T a, T b) {
+  static_assert(std::is_integral_v<T>);
+  // Unsigned arithmetic wraps, so convert to the corresponding unsigned type.
+  // Note that, if `T` is smaller than `int`, e.g. `int16_t`, the values are
+  // promoted to `int`, which brings us back to undefined overflow. This is fine
+  // here because the sum of any two `int16_t`s fits in `int`, but `WrappingMul`
+  // will need a more complex implementation.
+  using Unsigned = std::make_unsigned_t<T>;
+  return static_cast<T>(static_cast<Unsigned>(a) + static_cast<Unsigned>(b));
+}
+
+// Returns `a - b` with overflow defined to wrap around, i.e. modulo 2^N where N
+// is the bit width of `T`.
+template <typename T>
+inline constexpr T WrappingSub(T a, T b) {
+  static_assert(std::is_integral_v<T>);
+  // Unsigned arithmetic wraps, so convert to the corresponding unsigned type.
+  // Note that, if `T` is smaller than `int`, e.g. `int16_t`, the values are
+  // promoted to `int`, which brings us back to undefined overflow. This is fine
+  // here because the difference of any two `int16_t`s fits in `int`, but
+  // `WrappingMul` will need a more complex implementation.
+  using Unsigned = std::make_unsigned_t<T>;
+  return static_cast<T>(static_cast<Unsigned>(a) - static_cast<Unsigned>(b));
+}
+
+}  // namespace base
+
+#endif  // BASE_NUMERICS_WRAPPING_MATH_H_
diff --git a/base/stl_util.h b/base/stl_util.h
index 3e75842..bc0118e 100644
--- a/base/stl_util.h
+++ b/base/stl_util.h
@@ -21,8 +21,8 @@
 
 template <typename Iter>
 constexpr bool IsRandomAccessIter =
-    std::is_same<typename std::iterator_traits<Iter>::iterator_category,
-                 std::random_access_iterator_tag>::value;
+    std::is_same_v<typename std::iterator_traits<Iter>::iterator_category,
+                   std::random_access_iterator_tag>;
 
 }  // namespace internal
 
diff --git a/base/strings/abseil_string_number_conversions.h b/base/strings/abseil_string_number_conversions.h
index 2c26752..fe2874b 100644
--- a/base/strings/abseil_string_number_conversions.h
+++ b/base/strings/abseil_string_number_conversions.h
@@ -6,7 +6,7 @@
 #define BASE_STRINGS_ABSEIL_STRING_NUMBER_CONVERSIONS_H_
 
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece_forward.h"
+#include "base/strings/string_piece.h"
 #include "absl/numeric/int128.h"
 
 namespace gurl_base {
diff --git a/base/strings/escape.cc b/base/strings/escape.cc
index d7d055c..6bed700 100644
--- a/base/strings/escape.cc
+++ b/base/strings/escape.cc
@@ -9,6 +9,7 @@
 #include "polyfills/base/check_op.h"
 #include "polyfills/base/feature_list.h"
 #include "base/features.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"
@@ -19,13 +20,6 @@
 
 namespace {
 
-const char kHexString[] = "0123456789ABCDEF";
-inline char IntToHex(int i) {
-  GURL_DCHECK_GE(i, 0) << i << " not a hex value";
-  GURL_DCHECK_LE(i, 15) << i << " not a hex value";
-  return kHexString[i];
-}
-
 // A fast bit-vector map for ascii characters.
 //
 // Internally stores 256 bits in an array of 8 ints.
@@ -58,8 +52,7 @@
       escaped.push_back('%');
     } else if (charmap.Contains(c)) {
       escaped.push_back('%');
-      escaped.push_back(IntToHex(c >> 4));
-      escaped.push_back(IntToHex(c & 0xf));
+      AppendHexEncodedByte(c, escaped);
     } else {
       escaped.push_back(static_cast<char>(c));
     }
diff --git a/base/strings/escape.h b/base/strings/escape.h
index 5ae4555..9943312 100644
--- a/base/strings/escape.h
+++ b/base/strings/escape.h
@@ -13,6 +13,7 @@
 #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"
 
 namespace gurl_base {
 
diff --git a/base/strings/levenshtein_distance.cc b/base/strings/levenshtein_distance.cc
new file mode 100644
index 0000000..da38f93
--- /dev/null
+++ b/base/strings/levenshtein_distance.cc
@@ -0,0 +1,102 @@
+// 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.
+
+#include "base/strings/levenshtein_distance.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <numeric>
+#include <optional>
+#include <string_view>
+#include <vector>
+
+namespace gurl_base {
+
+namespace {
+
+template <typename CharT>
+size_t LevenshteinDistanceImpl(std::basic_string_view<CharT> a,
+                               std::basic_string_view<CharT> b,
+                               std::optional<size_t> max_distance) {
+  if (a.size() > b.size()) {
+    a.swap(b);
+  }
+
+  // max(a.size(), b.size()) steps always suffice.
+  const size_t k = max_distance.value_or(b.size());
+  // If the string's lengths differ by more than `k`, so does their
+  // Levenshtein distance.
+  if (a.size() + k < b.size()) {
+    return k + 1;
+  }
+  // The classical Levenshtein distance DP defines dp[i][j] as the minimum
+  // number of insert, remove and replace operation to convert a[:i] to b[:j].
+  // To make this more efficient, one can define dp[i][d] as the distance of
+  // a[:i] and b[:i + d]. Intuitively, d represents the delta between j and i in
+  // the former dp. Since the Levenshtein distance is restricted by `k`, abs(d)
+  // can be bounded by `k`. Since dp[i][d] only depends on values from dp[i-1],
+  // it is not necessary to store the entire 2D table. Instead, this code just
+  // stores the d-dimension, which represents "the distance with the current
+  // prefix of the string, for a given delta d". Since d is between `-k` and
+  // `k`, the implementation shifts the d-index by `k`, bringing it in range
+  // [0, `2*k`].
+
+  // The algorithm only cares if the Levenshtein distance is at most `k`. Thus,
+  // any unreachable states and states in which the distance is certainly larger
+  // than `k` can be set to any value larger than `k`, without affecting the
+  // result.
+  const size_t kInfinity = k + 1;
+  std::vector<size_t> dp(2 * k + 1, kInfinity);
+  // Initially, `dp[d]` represents the Levenshtein distance of the empty prefix
+  // of `a` and the first j = d - k characters of `b`. Their distance is j,
+  // since j removals are required. States with negative d are not reachable,
+  // since that corresponds to a negative index into `b`.
+  std::iota(dp.begin() + static_cast<long>(k), dp.end(), 0);
+  for (size_t i = 0; i < a.size(); i++) {
+    // Right now, `dp` represents the Levenshtein distance when considering the
+    // first `i` characters (up to index `i-1`) of `a`. After the next loop,
+    // `dp` will represent the Levenshtein distance when considering the first
+    // `i+1` characters.
+    for (size_t d = 0; d <= 2 * k; d++) {
+      if (i + d < k || i + d >= b.size() + k) {
+        // `j = i + d - k` is out of range of `b`. Since j == -1 corresponds to
+        // the empty prefix of `b`, the distance is i + 1 in this case.
+        dp[d] = i + d + 1 == k ? i + 1 : kInfinity;
+        continue;
+      }
+      const size_t j = i + d - k;
+      // If `a[i] == `b[j]` the Levenshtein distance for `d` remained the same.
+      if (a[i] != b[j]) {
+        // (i, j) -> (i-1, j-1), `d` stays the same.
+        const size_t replace = dp[d];
+        // (i, j) -> (i-1, j), `d` increases by 1.
+        // If the distance between `i` and `j` becomes larger than `k`, their
+        // distance is at least `k + 1`. Same in the `insert` case.
+        const size_t remove = d != 2 * k ? dp[d + 1] : kInfinity;
+        // (i, j) -> (i, j-1), `d` decreases by 1. Since `i` stays the same,
+        // this is intentionally using the dp value updated in the previous
+        // iteration.
+        const size_t insert = d != 0 ? dp[d - 1] : kInfinity;
+        dp[d] = 1 + std::min({replace, remove, insert});
+      }
+    }
+  }
+  return std::min(dp[b.size() + k - a.size()], k + 1);
+}
+
+}  // namespace
+
+size_t LevenshteinDistance(std::string_view a,
+                           std::string_view b,
+                           std::optional<size_t> max_distance) {
+  return LevenshteinDistanceImpl(a, b, max_distance);
+}
+size_t LevenshteinDistance(std::u16string_view a,
+                           std::u16string_view b,
+                           std::optional<size_t> max_distance) {
+  return LevenshteinDistanceImpl(a, b, max_distance);
+}
+
+}  // namespace base
diff --git a/base/strings/levenshtein_distance.h b/base/strings/levenshtein_distance.h
new file mode 100644
index 0000000..7bdbf53
--- /dev/null
+++ b/base/strings/levenshtein_distance.h
@@ -0,0 +1,37 @@
+// 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_STRINGS_LEVENSHTEIN_DISTANCE_H_
+#define BASE_STRINGS_LEVENSHTEIN_DISTANCE_H_
+
+#include <stddef.h>
+
+#include <optional>
+#include <string_view>
+
+#include "polyfills/base/base_export.h"
+
+namespace gurl_base {
+
+// Returns the Levenshtein distance of `a` and `b`. Edits, inserts and removes
+// each count as one step.
+// If `k = max_distance` is provided, the distance is only correctly calculated
+// up to k. In case the actual Levenshtein distance is larger than k, k+1 is
+// returned instead. This is useful for checking whether the distance is at most
+// some small constant, since the algorithm is more efficient in this case.
+// Complexity:
+// - Without k: O(|a| * |b|) time and O(max(|a|, |b|)) memory.
+// - With k: O(min(|a|, |b|) * k + k) time and O(k) memory.
+BASE_EXPORT size_t
+LevenshteinDistance(std::string_view a,
+                    std::string_view b,
+                    std::optional<size_t> max_distance = std::nullopt);
+BASE_EXPORT size_t
+LevenshteinDistance(std::u16string_view a,
+                    std::u16string_view b,
+                    std::optional<size_t> max_distance = std::nullopt);
+
+}  // namespace base
+
+#endif  // BASE_STRINGS_LEVENSHTEIN_DISTANCE_H_
diff --git a/base/strings/levenshtein_distance_unittest.cc b/base/strings/levenshtein_distance_unittest.cc
new file mode 100644
index 0000000..67b9ff5
--- /dev/null
+++ b/base/strings/levenshtein_distance_unittest.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "base/strings/levenshtein_distance.h"
+
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gurl_base {
+
+namespace {
+
+TEST(LevenshteinDistanceTest, WithoutMaxDistance) {
+  EXPECT_EQ(0u, LevenshteinDistance("banana", "banana"));
+
+  EXPECT_EQ(2u, LevenshteinDistance("ab", "ba"));
+  EXPECT_EQ(2u, LevenshteinDistance("ba", "ab"));
+
+  EXPECT_EQ(2u, LevenshteinDistance("ananas", "banana"));
+  EXPECT_EQ(2u, LevenshteinDistance("banana", "ananas"));
+
+  EXPECT_EQ(2u, LevenshteinDistance("unclear", "nuclear"));
+  EXPECT_EQ(2u, LevenshteinDistance("nuclear", "unclear"));
+
+  EXPECT_EQ(3u, LevenshteinDistance("chrome", "chromium"));
+  EXPECT_EQ(3u, LevenshteinDistance("chromium", "chrome"));
+
+  EXPECT_EQ(4u, LevenshteinDistance("", "abcd"));
+  EXPECT_EQ(4u, LevenshteinDistance("abcd", ""));
+
+  // `std::u16string_view` version.
+  EXPECT_EQ(4u, LevenshteinDistance(u"xxx", u"xxxxxxx"));
+  EXPECT_EQ(4u, LevenshteinDistance(u"xxxxxxx", u"xxx"));
+
+  EXPECT_EQ(7u, LevenshteinDistance(u"yyy", u"xxxxxxx"));
+  EXPECT_EQ(7u, LevenshteinDistance(u"xxxxxxx", u"yyy"));
+}
+
+TEST(LevenshteinDistanceTest, WithMaxDistance) {
+  EXPECT_EQ(LevenshteinDistance("aa", "aa", 0), 0u);
+
+  EXPECT_EQ(LevenshteinDistance("a", "aa", 1), 1u);
+  EXPECT_EQ(LevenshteinDistance("aa", "a", 1), 1u);
+
+  // If k is less than `LevenshteinDistance()`, the function should return k+1.
+  EXPECT_EQ(LevenshteinDistance("", "12", 1), 2u);
+  EXPECT_EQ(LevenshteinDistance("12", "", 1), 2u);
+
+  EXPECT_EQ(LevenshteinDistance("street", "str.", 1), 2u);
+  EXPECT_EQ(LevenshteinDistance("str.", "street", 1), 2u);
+
+  EXPECT_EQ(LevenshteinDistance("asdf", "fdsa", 2), 3u);
+  EXPECT_EQ(LevenshteinDistance("fdsa", "asdf", 2), 3u);
+
+  EXPECT_EQ(LevenshteinDistance(std::u16string(100, 'a'),
+                                std::u16string(200, 'a'), 50),
+            51u);
+  EXPECT_EQ(LevenshteinDistance(std::u16string(200, 'a'),
+                                std::u16string(100, 'a'), 50),
+            51u);
+}
+
+}  // namespace
+
+}  // namespace base
diff --git a/base/strings/strcat_win.cc b/base/strings/strcat_win.cc
index 93cb2c8..74f3fbc 100644
--- a/base/strings/strcat_win.cc
+++ b/base/strings/strcat_win.cc
@@ -5,14 +5,14 @@
 #include "base/strings/strcat_win.h"
 
 #include <string>
+#include <string_view>
 
 #include "base/containers/span.h"
 #include "base/strings/strcat_internal.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 
-std::wstring StrCat(span<const WStringPiece> pieces) {
+std::wstring StrCat(span<const std::wstring_view> pieces) {
   return internal::StrCatT(pieces);
 }
 
@@ -20,7 +20,7 @@
   return internal::StrCatT(pieces);
 }
 
-void StrAppend(std::wstring* dest, span<const WStringPiece> pieces) {
+void StrAppend(std::wstring* dest, span<const std::wstring_view> pieces) {
   internal::StrAppendT(*dest, pieces);
 }
 
diff --git a/base/strings/strcat_win.h b/base/strings/strcat_win.h
index d9c00e9..b7dac19 100644
--- a/base/strings/strcat_win.h
+++ b/base/strings/strcat_win.h
@@ -7,27 +7,29 @@
 
 #include <initializer_list>
 #include <string>
+#include <string_view>
 
 #include "polyfills/base/base_export.h"
 #include "base/containers/span.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 
 // The following section contains overloads of the cross-platform APIs for
-// std::wstring and gurl_base::WStringPiece.
-BASE_EXPORT void StrAppend(std::wstring* dest, span<const WStringPiece> pieces);
+// std::wstring and std::wstring_view.
+BASE_EXPORT void StrAppend(std::wstring* dest,
+                           span<const std::wstring_view> pieces);
 BASE_EXPORT void StrAppend(std::wstring* dest, span<const std::wstring> pieces);
 
 inline void StrAppend(std::wstring* dest,
-                      std::initializer_list<WStringPiece> pieces) {
+                      std::initializer_list<std::wstring_view> pieces) {
   StrAppend(dest, make_span(pieces));
 }
 
-[[nodiscard]] BASE_EXPORT std::wstring StrCat(span<const WStringPiece> pieces);
+[[nodiscard]] BASE_EXPORT std::wstring StrCat(
+    span<const std::wstring_view> pieces);
 [[nodiscard]] BASE_EXPORT std::wstring StrCat(span<const std::wstring> pieces);
 
-inline std::wstring StrCat(std::initializer_list<WStringPiece> pieces) {
+inline std::wstring StrCat(std::initializer_list<std::wstring_view> pieces) {
   return StrCat(make_span(pieces));
 }
 
diff --git a/base/strings/string_number_conversions.cc b/base/strings/string_number_conversions.cc
index e7c692e..258db85 100644
--- a/base/strings/string_number_conversions.cc
+++ b/base/strings/string_number_conversions.cc
@@ -120,21 +120,18 @@
 }
 
 std::string HexEncode(const void* bytes, size_t size) {
-  static const char kHexChars[] = "0123456789ABCDEF";
-
-  // Each input byte creates two output hex characters.
-  std::string ret(size * 2, '\0');
-
-  for (size_t i = 0; i < size; ++i) {
-    char b = reinterpret_cast<const char*>(bytes)[i];
-    ret[(i * 2)] = kHexChars[(b >> 4) & 0xf];
-    ret[(i * 2) + 1] = kHexChars[b & 0xf];
-  }
-  return ret;
+  return HexEncode(span(reinterpret_cast<const uint8_t*>(bytes), size));
 }
 
-std::string HexEncode(gurl_base::span<const uint8_t> bytes) {
-  return HexEncode(bytes.data(), bytes.size());
+std::string HexEncode(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);
+  }
+  return ret;
 }
 
 bool HexStringToInt(StringPiece input, int* output) {
@@ -165,7 +162,7 @@
                                                   std::back_inserter(*output));
 }
 
-bool HexStringToSpan(StringPiece input, gurl_base::span<uint8_t> output) {
+bool HexStringToSpan(StringPiece input, span<uint8_t> output) {
   if (input.size() / 2 != output.size())
     return false;
 
diff --git a/base/strings/string_number_conversions.h b/base/strings/string_number_conversions.h
index 71d324e..c62e914 100644
--- a/base/strings/string_number_conversions.h
+++ b/base/strings/string_number_conversions.h
@@ -107,6 +107,22 @@
 BASE_EXPORT std::string HexEncode(const void* bytes, size_t size);
 BASE_EXPORT std::string HexEncode(gurl_base::span<const uint8_t> bytes);
 
+// Appends a hex representation of `byte`, as two uppercase (by default)
+// characters, to `output`. This is a useful primitive in larger conversion
+// routines.
+inline void AppendHexEncodedByte(uint8_t byte,
+                                 std::string& output,
+                                 bool uppercase = true) {
+  static constexpr char kHexCharsUpper[] = {'0', '1', '2', '3', '4', '5',
+                                            '6', '7', '8', '9', 'A', 'B',
+                                            'C', 'D', 'E', 'F'};
+  static constexpr char kHexCharsLower[] = {'0', '1', '2', '3', '4', '5',
+                                            '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]});
+}
+
 // Best effort conversion, see StringToInt above for restrictions.
 // Will only successful parse hex values that will fit into |output|, i.e.
 // -0x80000000 < |input| < 0x7FFFFFFF.
diff --git a/base/strings/string_number_conversions_unittest.cc b/base/strings/string_number_conversions_unittest.cc
index 349e2e4..8fbb00d 100644
--- a/base/strings/string_number_conversions_unittest.cc
+++ b/base/strings/string_number_conversions_unittest.cc
@@ -919,12 +919,29 @@
   EXPECT_EQ("1.33489033216e+12", NumberToString(input));
 }
 
+TEST(StringNumberConversionsTest, AppendHexEncodedByte) {
+  std::string hex;
+  AppendHexEncodedByte(0, hex);
+  AppendHexEncodedByte(0, hex, false);
+  AppendHexEncodedByte(1, hex);
+  AppendHexEncodedByte(1, hex, false);
+  AppendHexEncodedByte(0xf, hex);
+  AppendHexEncodedByte(0xf, hex, false);
+  AppendHexEncodedByte(0x8a, hex);
+  AppendHexEncodedByte(0x8a, hex, false);
+  AppendHexEncodedByte(0xe0, hex);
+  AppendHexEncodedByte(0xe0, hex, false);
+  AppendHexEncodedByte(0xff, hex);
+  AppendHexEncodedByte(0xff, hex, false);
+  EXPECT_EQ(hex, "000001010F0f8A8aE0e0FFff");
+}
+
 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.compare("01FF02FE038081"), 0);
+  EXPECT_EQ(hex, "01FF02FE038081");
 }
 
 // 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 9857dd4..256adf1 100644
--- a/base/strings/string_number_conversions_win.cc
+++ b/base/strings/string_number_conversions_win.cc
@@ -5,9 +5,9 @@
 
 
 #include <string>
+#include <string_view>
 
 #include "base/strings/string_number_conversions_internal.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 
@@ -39,27 +39,27 @@
   return internal::DoubleToStringT<std::wstring>(value);
 }
 
-bool StringToInt(WStringPiece input, int* output) {
+bool StringToInt(std::wstring_view input, int* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToUint(WStringPiece input, unsigned* output) {
+bool StringToUint(std::wstring_view input, unsigned* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToInt64(WStringPiece input, int64_t* output) {
+bool StringToInt64(std::wstring_view input, int64_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToUint64(WStringPiece input, uint64_t* output) {
+bool StringToUint64(std::wstring_view input, uint64_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToSizeT(WStringPiece input, size_t* output) {
+bool StringToSizeT(std::wstring_view input, size_t* output) {
   return internal::StringToIntImpl(input, *output);
 }
 
-bool StringToDouble(WStringPiece input, double* output) {
+bool StringToDouble(std::wstring_view input, double* output) {
   return internal::StringToDoubleImpl(
       input, reinterpret_cast<const uint16_t*>(input.data()), *output);
 }
diff --git a/base/strings/string_number_conversions_win.h b/base/strings/string_number_conversions_win.h
index 1d7e7a6..1de7e87 100644
--- a/base/strings/string_number_conversions_win.h
+++ b/base/strings/string_number_conversions_win.h
@@ -6,9 +6,9 @@
 #define BASE_STRINGS_STRING_NUMBER_CONVERSIONS_WIN_H_
 
 #include <string>
+#include <string_view>
 
 #include "polyfills/base/base_export.h"
-#include "base/strings/string_piece.h"
 
 namespace gurl_base {
 
@@ -21,13 +21,13 @@
 BASE_EXPORT std::wstring NumberToWString(double value);
 
 // The following section contains overloads of the cross-platform APIs for
-// std::wstring and gurl_base::WStringPiece.
-BASE_EXPORT bool StringToInt(WStringPiece input, int* output);
-BASE_EXPORT bool StringToUint(WStringPiece input, unsigned* output);
-BASE_EXPORT bool StringToInt64(WStringPiece input, int64_t* output);
-BASE_EXPORT bool StringToUint64(WStringPiece input, uint64_t* output);
-BASE_EXPORT bool StringToSizeT(WStringPiece input, size_t* output);
-BASE_EXPORT bool StringToDouble(WStringPiece input, double* output);
+// std::wstring and std::wstring_view.
+BASE_EXPORT bool StringToInt(std::wstring_view input, int* output);
+BASE_EXPORT bool StringToUint(std::wstring_view input, unsigned* output);
+BASE_EXPORT bool StringToInt64(std::wstring_view input, int64_t* output);
+BASE_EXPORT bool StringToUint64(std::wstring_view input, uint64_t* output);
+BASE_EXPORT bool StringToSizeT(std::wstring_view input, size_t* output);
+BASE_EXPORT bool StringToDouble(std::wstring_view input, double* output);
 
 }  // namespace base
 
diff --git a/base/strings/string_piece.h b/base/strings/string_piece.h
index 31feafc..a72af4c 100644
--- a/base/strings/string_piece.h
+++ b/base/strings/string_piece.h
@@ -10,16 +10,15 @@
 #ifndef BASE_STRINGS_STRING_PIECE_H_
 #define BASE_STRINGS_STRING_PIECE_H_
 
-// Many files including this header rely on these being included due to IWYU
-// violations. Preserve the includes for now. As code is migrated away from this
-// header, we can incrementally fix the IWYU violations.
-#include "polyfills/base/base_export.h"
-#include "polyfills/base/check.h"
-#include "polyfills/base/check_op.h"
-#include "base/compiler_specific.h"
-#include "base/cxx20_is_constant_evaluated.h"
-#include "base/strings/string_piece_forward.h"
-#include "base/strings/utf_ostream_operators.h"
-#include "build/build_config.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_forward.h b/base/strings/string_piece_forward.h
deleted file mode 100644
index 9138f3f..0000000
--- a/base/strings/string_piece_forward.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 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_FORWARD_H_
-#define BASE_STRINGS_STRING_PIECE_FORWARD_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;
-using WStringPiece = std::wstring_view;
-
-}  // namespace base
-
-#endif  // BASE_STRINGS_STRING_PIECE_FORWARD_H_
diff --git a/base/strings/string_piece_rust.h b/base/strings/string_piece_rust.h
index 39fb51b..8bcb766 100644
--- a/base/strings/string_piece_rust.h
+++ b/base/strings/string_piece_rust.h
@@ -8,8 +8,8 @@
 #include <stdint.h>
 
 #include "base/rust_buildflags.h"
-#include "base/strings/string_piece_forward.h"
-#include "third_party/rust/cxx/v1/crate/include/cxx.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"
diff --git a/base/strings/string_split_win.cc b/base/strings/string_split_win.cc
index 05c0541..e103db5 100644
--- a/base/strings/string_split_win.cc
+++ b/base/strings/string_split_win.cc
@@ -5,6 +5,7 @@
 #include "base/strings/string_split_win.h"
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "base/strings/string_piece.h"
@@ -15,42 +16,42 @@
 namespace internal {
 
 template <>
-inline WStringPiece WhitespaceForType<wchar_t>() {
+inline std::wstring_view WhitespaceForType<wchar_t>() {
   return kWhitespaceWide;
 }
 
 }  // namespace internal
 
-std::vector<std::wstring> SplitString(WStringPiece input,
-                                      WStringPiece separators,
+std::vector<std::wstring> SplitString(std::wstring_view input,
+                                      std::wstring_view separators,
                                       WhitespaceHandling whitespace,
                                       SplitResult result_type) {
   return internal::SplitStringT<std::wstring>(input, separators, whitespace,
                                               result_type);
 }
 
-std::vector<WStringPiece> SplitStringPiece(WStringPiece input,
-                                           WStringPiece separators,
-                                           WhitespaceHandling whitespace,
-                                           SplitResult result_type) {
-  return internal::SplitStringT<WStringPiece>(input, separators, whitespace,
-                                              result_type);
+std::vector<std::wstring_view> SplitStringPiece(std::wstring_view input,
+                                                std::wstring_view separators,
+                                                WhitespaceHandling whitespace,
+                                                SplitResult result_type) {
+  return internal::SplitStringT<std::wstring_view>(input, separators,
+                                                   whitespace, result_type);
 }
 
-std::vector<std::wstring> SplitStringUsingSubstr(WStringPiece input,
-                                                 WStringPiece delimiter,
+std::vector<std::wstring> SplitStringUsingSubstr(std::wstring_view input,
+                                                 std::wstring_view delimiter,
                                                  WhitespaceHandling whitespace,
                                                  SplitResult result_type) {
   return internal::SplitStringUsingSubstrT<std::wstring>(
       input, delimiter, whitespace, result_type);
 }
 
-std::vector<WStringPiece> SplitStringPieceUsingSubstr(
-    WStringPiece input,
-    WStringPiece delimiter,
+std::vector<std::wstring_view> SplitStringPieceUsingSubstr(
+    std::wstring_view input,
+    std::wstring_view delimiter,
     WhitespaceHandling whitespace,
     SplitResult result_type) {
-  return internal::SplitStringUsingSubstrT<WStringPiece>(
+  return internal::SplitStringUsingSubstrT<std::wstring_view>(
       input, delimiter, whitespace, result_type);
 }
 
diff --git a/base/strings/string_split_win.h b/base/strings/string_split_win.h
index 08b52b2..845de38 100644
--- a/base/strings/string_split_win.h
+++ b/base/strings/string_split_win.h
@@ -6,6 +6,7 @@
 #define BASE_STRINGS_STRING_SPLIT_WIN_H_
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "polyfills/base/base_export.h"
@@ -15,30 +16,30 @@
 namespace gurl_base {
 
 // The following section contains overloads of the cross-platform APIs for
-// std::wstring and gurl_base::WStringPiece.
+// std::wstring and std::wstring_view.
 [[nodiscard]] BASE_EXPORT std::vector<std::wstring> SplitString(
-    WStringPiece input,
-    WStringPiece separators,
+    std::wstring_view input,
+    std::wstring_view separators,
     WhitespaceHandling whitespace,
     SplitResult result_type);
 
-[[nodiscard]] BASE_EXPORT std::vector<WStringPiece> SplitStringPiece(
-    WStringPiece input,
-    WStringPiece separators,
+[[nodiscard]] BASE_EXPORT std::vector<std::wstring_view> SplitStringPiece(
+    std::wstring_view input,
+    std::wstring_view separators,
     WhitespaceHandling whitespace,
     SplitResult result_type);
 
 [[nodiscard]] BASE_EXPORT std::vector<std::wstring> SplitStringUsingSubstr(
-    WStringPiece input,
-    WStringPiece delimiter,
+    std::wstring_view input,
+    std::wstring_view delimiter,
     WhitespaceHandling whitespace,
     SplitResult result_type);
 
-[[nodiscard]] BASE_EXPORT std::vector<WStringPiece> SplitStringPieceUsingSubstr(
-    WStringPiece input,
-    WStringPiece delimiter,
-    WhitespaceHandling whitespace,
-    SplitResult result_type);
+[[nodiscard]] BASE_EXPORT std::vector<std::wstring_view>
+SplitStringPieceUsingSubstr(std::wstring_view input,
+                            std::wstring_view delimiter,
+                            WhitespaceHandling whitespace,
+                            SplitResult result_type);
 
 }  // namespace base
 
diff --git a/base/strings/string_util.cc b/base/strings/string_util.cc
index 1633f7f..cda921f 100644
--- a/base/strings/string_util.cc
+++ b/base/strings/string_util.cc
@@ -15,6 +15,7 @@
 #include <wchar.h>
 
 #include <limits>
+#include <string_view>
 #include <type_traits>
 #include <vector>
 
@@ -233,8 +234,8 @@
   return internal::DoIsStringASCII(str.data(), str.length());
 }
 
-#if defined(WCHAR_T_IS_UTF32)
-bool IsStringASCII(WStringPiece str) {
+#if defined(WCHAR_T_IS_32_BIT)
+bool IsStringASCII(std::wstring_view str) {
   return internal::DoIsStringASCII(str.data(), str.length());
 }
 #endif
diff --git a/base/strings/string_util.h b/base/strings/string_util.h
index 29936fa..8f8bb72 100644
--- a/base/strings/string_util.h
+++ b/base/strings/string_util.h
@@ -7,12 +7,14 @@
 #ifndef BASE_STRINGS_STRING_UTIL_H_
 #define BASE_STRINGS_STRING_UTIL_H_
 
-#include <stdarg.h>   // va_list
+#include <stdarg.h>  // va_list
 #include <stddef.h>
 #include <stdint.h>
 
 #include <initializer_list>
+#include <memory>
 #include <string>
+#include <string_view>
 #include <type_traits>
 #include <vector>
 
@@ -20,7 +22,6 @@
 #include "polyfills/base/check_op.h"
 #include "base/compiler_specific.h"
 #include "base/containers/span.h"
-#include "base/cxx20_to_address.h"
 #include "base/strings/string_piece.h"  // For implicit conversions.
 #include "base/strings/string_util_internal.h"
 #include "build/build_config.h"
@@ -92,11 +93,11 @@
 template <typename CharT, typename Iter>
 constexpr BasicStringPiece<CharT> MakeBasicStringPiece(Iter begin, Iter end) {
   GURL_DCHECK_GE(end - begin, 0);
-  return {gurl_base::to_address(begin), static_cast<size_t>(end - begin)};
+  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_forward.h
+// aliases defined in base/strings/string_piece.h
 template <typename Iter>
 constexpr StringPiece MakeStringPiece(Iter begin, Iter end) {
   return MakeBasicStringPiece<char>(begin, end);
@@ -108,14 +109,14 @@
 }
 
 template <typename Iter>
-constexpr WStringPiece MakeWStringPiece(Iter begin, Iter end) {
+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<CharT>::value>>
+          typename = std::enable_if_t<std::is_integral_v<CharT>>>
 constexpr CharT ToLowerASCII(CharT c) {
   return internal::ToLowerASCII(c);
 }
@@ -123,7 +124,7 @@
 // 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<CharT>::value>>
+          typename = std::enable_if_t<std::is_integral_v<CharT>>>
 CharT ToUpperASCII(CharT c) {
   return (c >= 'a' && c <= 'z') ? static_cast<CharT>(c + 'A' - 'a') : c;
 }
@@ -344,8 +345,8 @@
 BASE_EXPORT bool IsStringASCII(StringPiece str);
 BASE_EXPORT bool IsStringASCII(StringPiece16 str);
 
-#if defined(WCHAR_T_IS_UTF32)
-BASE_EXPORT bool IsStringASCII(WStringPiece str);
+#if defined(WCHAR_T_IS_32_BIT)
+BASE_EXPORT bool IsStringASCII(std::wstring_view str);
 #endif
 
 // Performs a case-sensitive string compare of the given 16-bit string against
diff --git a/base/strings/string_util_internal.h b/base/strings/string_util_internal.h
index b05cb7a..c4802d1 100644
--- a/base/strings/string_util_internal.h
+++ b/base/strings/string_util_internal.h
@@ -15,7 +15,7 @@
 // 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<CharT>::value>>
+          typename = std::enable_if_t<std::is_integral_v<CharT>>>
 constexpr CharT ToLowerASCII(CharT c) {
   return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
 }
diff --git a/base/strings/string_util_perftest.cc b/base/strings/string_util_perftest.cc
index 879c74f..e2df9f3 100644
--- a/base/strings/string_util_perftest.cc
+++ b/base/strings/string_util_perftest.cc
@@ -35,7 +35,7 @@
       size_t non_ascii_pos = str_length * non_ascii_loc / 2 + 2;
       MeasureIsStringASCII<std::string>(str_length, non_ascii_pos);
       MeasureIsStringASCII<std::u16string>(str_length, non_ascii_pos);
-#if defined(WCHAR_T_IS_UTF32)
+#if defined(WCHAR_T_IS_32_BIT)
       MeasureIsStringASCII<std::basic_string<wchar_t>>(str_length,
                                                        non_ascii_pos);
 #endif
diff --git a/base/strings/string_util_unittest.cc b/base/strings/string_util_unittest.cc
index 988344d..12c4c8c 100644
--- a/base/strings/string_util_unittest.cc
+++ b/base/strings/string_util_unittest.cc
@@ -11,6 +11,7 @@
 
 #include <algorithm>
 #include <string>
+#include <string_view>
 #include <type_traits>
 
 #include "base/bits.h"
@@ -351,66 +352,60 @@
   EXPECT_EQ(output.compare(""), 0);
 }
 
-#if defined(WCHAR_T_IS_UTF16)
+#if defined(WCHAR_T_IS_16_BIT)
 TEST(StringUtilTest, as_wcstr) {
   char16_t rw_buffer[10] = {};
   static_assert(
-      std::is_same<wchar_t*, decltype(as_writable_wcstr(rw_buffer))>::value,
-      "");
+      std::is_same_v<wchar_t*, decltype(as_writable_wcstr(rw_buffer))>, "");
   EXPECT_EQ(static_cast<void*>(rw_buffer), as_writable_wcstr(rw_buffer));
 
   std::u16string rw_str(10, '\0');
-  static_assert(
-      std::is_same<wchar_t*, decltype(as_writable_wcstr(rw_str))>::value, "");
+  static_assert(std::is_same_v<wchar_t*, decltype(as_writable_wcstr(rw_str))>,
+                "");
   EXPECT_EQ(static_cast<const void*>(rw_str.data()), as_writable_wcstr(rw_str));
 
   const char16_t ro_buffer[10] = {};
-  static_assert(
-      std::is_same<const wchar_t*, decltype(as_wcstr(ro_buffer))>::value, "");
+  static_assert(std::is_same_v<const wchar_t*, decltype(as_wcstr(ro_buffer))>,
+                "");
   EXPECT_EQ(static_cast<const void*>(ro_buffer), as_wcstr(ro_buffer));
 
   const std::u16string ro_str(10, '\0');
-  static_assert(std::is_same<const wchar_t*, decltype(as_wcstr(ro_str))>::value,
-                "");
+  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;
-  static_assert(std::is_same<const wchar_t*, decltype(as_wcstr(piece))>::value,
-                "");
+  static_assert(std::is_same_v<const wchar_t*, decltype(as_wcstr(piece))>, "");
   EXPECT_EQ(static_cast<const void*>(piece.data()), as_wcstr(piece));
 }
 
 TEST(StringUtilTest, as_u16cstr) {
   wchar_t rw_buffer[10] = {};
   static_assert(
-      std::is_same<char16_t*, decltype(as_writable_u16cstr(rw_buffer))>::value,
-      "");
+      std::is_same_v<char16_t*, decltype(as_writable_u16cstr(rw_buffer))>, "");
   EXPECT_EQ(static_cast<void*>(rw_buffer), as_writable_u16cstr(rw_buffer));
 
   std::wstring rw_str(10, '\0');
   static_assert(
-      std::is_same<char16_t*, decltype(as_writable_u16cstr(rw_str))>::value,
-      "");
+      std::is_same_v<char16_t*, decltype(as_writable_u16cstr(rw_str))>, "");
   EXPECT_EQ(static_cast<const void*>(rw_str.data()),
             as_writable_u16cstr(rw_str));
 
   const wchar_t ro_buffer[10] = {};
   static_assert(
-      std::is_same<const char16_t*, decltype(as_u16cstr(ro_buffer))>::value,
-      "");
+      std::is_same_v<const char16_t*, decltype(as_u16cstr(ro_buffer))>, "");
   EXPECT_EQ(static_cast<const void*>(ro_buffer), as_u16cstr(ro_buffer));
 
   const std::wstring ro_str(10, '\0');
-  static_assert(
-      std::is_same<const char16_t*, decltype(as_u16cstr(ro_str))>::value, "");
+  static_assert(std::is_same_v<const char16_t*, decltype(as_u16cstr(ro_str))>,
+                "");
   EXPECT_EQ(static_cast<const void*>(ro_str.data()), as_u16cstr(ro_str));
 
-  WStringPiece piece = ro_buffer;
-  static_assert(
-      std::is_same<const char16_t*, decltype(as_u16cstr(piece))>::value, "");
+  std::wstring_view piece = ro_buffer;
+  static_assert(std::is_same_v<const char16_t*, decltype(as_u16cstr(piece))>,
+                "");
   EXPECT_EQ(static_cast<const void*>(piece.data()), as_u16cstr(piece));
 }
-#endif  // defined(WCHAR_T_IS_UTF16)
+#endif  // defined(WCHAR_T_IS_16_BIT)
 
 TEST(StringUtilTest, TrimWhitespace) {
   std::u16string output;  // Allow contents to carry over to next testcase
@@ -571,7 +566,7 @@
     }
   }
 
-#if defined(WCHAR_T_IS_UTF32)
+#if defined(WCHAR_T_IS_32_BIT)
   {
     const size_t string_length = wchar_ascii.length();
     for (size_t len = 0; len < string_length; ++len) {
@@ -589,7 +584,7 @@
       }
     }
   }
-#endif  // WCHAR_T_IS_UTF32
+#endif  // WCHAR_T_IS_32_BIT
 }
 
 TEST(StringUtilTest, ConvertASCII) {
@@ -1338,17 +1333,17 @@
   EXPECT_TRUE(MakeStringPiece16(bar.end(), bar.end()).empty());
 
   constexpr wchar_t kBaz[] = L"Baz";
-  static_assert(MakeWStringPiece(kBaz, kBaz + 3) == kBaz, "");
-  static_assert(MakeWStringPiece(kBaz, kBaz + 3).data() == kBaz, "");
-  static_assert(MakeWStringPiece(kBaz, kBaz + 3).size() == 3, "");
-  static_assert(MakeWStringPiece(kBaz + 3, kBaz + 3).empty(), "");
-  static_assert(MakeWStringPiece(kBaz + 4, kBaz + 4).empty(), "");
+  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(MakeWStringPiece(baz.begin(), baz.end()), baz);
-  EXPECT_EQ(MakeWStringPiece(baz.begin(), baz.end()).data(), baz.data());
-  EXPECT_EQ(MakeWStringPiece(baz.begin(), baz.end()).size(), baz.size());
-  EXPECT_TRUE(MakeWStringPiece(baz.end(), baz.end()).empty());
+  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) {
@@ -1528,7 +1523,7 @@
   EXPECT_TRUE(EqualsCaseInsensitiveASCII("aaa \xc3\xa4", "AAA \xc3\xa4"));
   EXPECT_FALSE(EqualsCaseInsensitiveASCII("aaa \xc3\x84", "AAA \xc3\xa4"));
 
-  // The `WStringPiece` overloads are only defined on Windows.
+  // The `std::wstring_view` overloads are only defined on Windows.
 #if BUILDFLAG(IS_WIN)
   EXPECT_TRUE(EqualsCaseInsensitiveASCII(L"", L""));
   EXPECT_TRUE(EqualsCaseInsensitiveASCII(L"Asdf", L"aSDF"));
diff --git a/base/strings/string_util_win.cc b/base/strings/string_util_win.cc
index ea84d0b..0caa8d3 100644
--- a/base/strings/string_util_win.cc
+++ b/base/strings/string_util_win.cc
@@ -4,95 +4,99 @@
 
 #include "base/strings/string_util_win.h"
 
+#include <string_view>
+
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_util_impl_helpers.h"
 #include "absl/types/optional.h"
 
 namespace gurl_base {
 
-bool IsStringASCII(WStringPiece str) {
+bool IsStringASCII(std::wstring_view str) {
   return internal::DoIsStringASCII(str.data(), str.length());
 }
 
-std::wstring ToLowerASCII(WStringPiece str) {
+std::wstring ToLowerASCII(std::wstring_view str) {
   return internal::ToLowerASCIIImpl(str);
 }
 
-std::wstring ToUpperASCII(WStringPiece str) {
+std::wstring ToUpperASCII(std::wstring_view str) {
   return internal::ToUpperASCIIImpl(str);
 }
 
-int CompareCaseInsensitiveASCII(WStringPiece a, WStringPiece b) {
+int CompareCaseInsensitiveASCII(std::wstring_view a, std::wstring_view b) {
   return internal::CompareCaseInsensitiveASCIIT(a, b);
 }
 
-bool RemoveChars(WStringPiece input,
-                 WStringPiece remove_chars,
+bool RemoveChars(std::wstring_view input,
+                 std::wstring_view remove_chars,
                  std::wstring* output) {
-  return internal::ReplaceCharsT(input, remove_chars, WStringPiece(), output);
+  return internal::ReplaceCharsT(input, remove_chars, std::wstring_view(),
+                                 output);
 }
 
-bool ReplaceChars(WStringPiece input,
-                  WStringPiece replace_chars,
-                  WStringPiece replace_with,
+bool ReplaceChars(std::wstring_view input,
+                  std::wstring_view replace_chars,
+                  std::wstring_view replace_with,
                   std::wstring* output) {
   return internal::ReplaceCharsT(input, replace_chars, replace_with, output);
 }
 
-bool TrimString(WStringPiece input,
-                WStringPiece trim_chars,
+bool TrimString(std::wstring_view input,
+                std::wstring_view trim_chars,
                 std::wstring* output) {
   return internal::TrimStringT(input, trim_chars, TRIM_ALL, output) !=
          TRIM_NONE;
 }
 
-WStringPiece TrimString(WStringPiece input,
-                        WStringPiece trim_chars,
-                        TrimPositions positions) {
+std::wstring_view TrimString(std::wstring_view input,
+                             std::wstring_view trim_chars,
+                             TrimPositions positions) {
   return internal::TrimStringPieceT(input, trim_chars, positions);
 }
 
-TrimPositions TrimWhitespace(WStringPiece input,
+TrimPositions TrimWhitespace(std::wstring_view input,
                              TrimPositions positions,
                              std::wstring* output) {
-  return internal::TrimStringT(input, WStringPiece(kWhitespaceWide), positions,
-                               output);
+  return internal::TrimStringT(input, std::wstring_view(kWhitespaceWide),
+                               positions, output);
 }
 
-WStringPiece TrimWhitespace(WStringPiece input, TrimPositions positions) {
-  return internal::TrimStringPieceT(input, WStringPiece(kWhitespaceWide),
+std::wstring_view TrimWhitespace(std::wstring_view input,
+                                 TrimPositions positions) {
+  return internal::TrimStringPieceT(input, std::wstring_view(kWhitespaceWide),
                                     positions);
 }
 
-std::wstring CollapseWhitespace(WStringPiece text,
+std::wstring CollapseWhitespace(std::wstring_view text,
                                 bool trim_sequences_with_line_breaks) {
   return internal::CollapseWhitespaceT(text, trim_sequences_with_line_breaks);
 }
 
-bool ContainsOnlyChars(WStringPiece input, WStringPiece characters) {
+bool ContainsOnlyChars(std::wstring_view input, std::wstring_view characters) {
   return input.find_first_not_of(characters) == StringPiece::npos;
 }
 
-bool EqualsASCII(WStringPiece str, StringPiece ascii) {
+bool EqualsASCII(std::wstring_view str, StringPiece ascii) {
   return ranges::equal(ascii, str);
 }
 
-bool StartsWith(WStringPiece str,
-                WStringPiece search_for,
+bool StartsWith(std::wstring_view str,
+                std::wstring_view search_for,
                 CompareCase case_sensitivity) {
   return internal::StartsWithT(str, search_for, case_sensitivity);
 }
 
-bool EndsWith(WStringPiece str,
-              WStringPiece search_for,
+bool EndsWith(std::wstring_view str,
+              std::wstring_view search_for,
               CompareCase case_sensitivity) {
   return internal::EndsWithT(str, search_for, case_sensitivity);
 }
 
 void ReplaceFirstSubstringAfterOffset(std::wstring* str,
                                       size_t start_offset,
-                                      WStringPiece find_this,
-                                      WStringPiece replace_with) {
+                                      std::wstring_view find_this,
+                                      std::wstring_view replace_with) {
   internal::DoReplaceMatchesAfterOffset(
       str, start_offset, internal::MakeSubstringMatcher(find_this),
       replace_with, internal::ReplaceType::REPLACE_FIRST);
@@ -100,8 +104,8 @@
 
 void ReplaceSubstringsAfterOffset(std::wstring* str,
                                   size_t start_offset,
-                                  WStringPiece find_this,
-                                  WStringPiece replace_with) {
+                                  std::wstring_view find_this,
+                                  std::wstring_view replace_with) {
   internal::DoReplaceMatchesAfterOffset(
       str, start_offset, internal::MakeSubstringMatcher(find_this),
       replace_with, internal::ReplaceType::REPLACE_ALL);
@@ -112,21 +116,21 @@
 }
 
 std::wstring JoinString(span<const std::wstring> parts,
-                        WStringPiece separator) {
+                        std::wstring_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
-std::wstring JoinString(span<const WStringPiece> parts,
-                        WStringPiece separator) {
+std::wstring JoinString(span<const std::wstring_view> parts,
+                        std::wstring_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
-std::wstring JoinString(std::initializer_list<WStringPiece> parts,
-                        WStringPiece separator) {
+std::wstring JoinString(std::initializer_list<std::wstring_view> parts,
+                        std::wstring_view separator) {
   return internal::JoinStringT(parts, separator);
 }
 
-std::wstring ReplaceStringPlaceholders(WStringPiece format_string,
+std::wstring ReplaceStringPlaceholders(std::wstring_view format_string,
                                        const std::vector<std::wstring>& subst,
                                        std::vector<size_t>* offsets) {
   absl::optional<std::wstring> replacement =
diff --git a/base/strings/string_util_win.h b/base/strings/string_util_win.h
index 14f08fe..ed3dbbc 100644
--- a/base/strings/string_util_win.h
+++ b/base/strings/string_util_win.h
@@ -12,6 +12,7 @@
 #include <wchar.h>
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "polyfills/base/check.h"
@@ -85,17 +86,17 @@
   return reinterpret_cast<const char16_t*>(str);
 }
 
-inline const char16_t* as_u16cstr(WStringPiece str) {
+inline const char16_t* as_u16cstr(std::wstring_view str) {
   return reinterpret_cast<const char16_t*>(str.data());
 }
 
-// Utility functions to convert between gurl_base::WStringPiece and
+// Utility functions to convert between std::wstring_view and
 // gurl_base::StringPiece16.
-inline WStringPiece AsWStringPiece(StringPiece16 str) {
-  return WStringPiece(as_wcstr(str.data()), str.size());
+inline std::wstring_view AsWStringView(StringPiece16 str) {
+  return std::wstring_view(as_wcstr(str.data()), str.size());
 }
 
-inline StringPiece16 AsStringPiece16(WStringPiece str) {
+inline StringPiece16 AsStringPiece16(std::wstring_view str) {
   return StringPiece16(as_u16cstr(str.data()), str.size());
 }
 
@@ -103,95 +104,100 @@
   return std::wstring(as_wcstr(str.data()), str.size());
 }
 
-inline std::u16string AsString16(WStringPiece str) {
+inline std::u16string AsString16(std::wstring_view str) {
   return std::u16string(as_u16cstr(str.data()), str.size());
 }
 
 // The following section contains overloads of the cross-platform APIs for
-// std::wstring and gurl_base::WStringPiece.
-BASE_EXPORT bool IsStringASCII(WStringPiece str);
+// std::wstring and std::wstring_view.
+BASE_EXPORT bool IsStringASCII(std::wstring_view str);
 
-BASE_EXPORT std::wstring ToLowerASCII(WStringPiece str);
+BASE_EXPORT std::wstring ToLowerASCII(std::wstring_view str);
 
-BASE_EXPORT std::wstring ToUpperASCII(WStringPiece str);
+BASE_EXPORT std::wstring ToUpperASCII(std::wstring_view str);
 
-BASE_EXPORT int CompareCaseInsensitiveASCII(WStringPiece a, WStringPiece b);
+BASE_EXPORT int CompareCaseInsensitiveASCII(std::wstring_view a,
+                                            std::wstring_view b);
 
-inline bool EqualsCaseInsensitiveASCII(WStringPiece a, WStringPiece b) {
+inline bool EqualsCaseInsensitiveASCII(std::wstring_view a,
+                                       std::wstring_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
-inline bool EqualsCaseInsensitiveASCII(WStringPiece a, StringPiece b) {
+inline bool EqualsCaseInsensitiveASCII(std::wstring_view a, StringPiece b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
-inline bool EqualsCaseInsensitiveASCII(StringPiece a, WStringPiece b) {
+inline bool EqualsCaseInsensitiveASCII(StringPiece a, std::wstring_view b) {
   return internal::EqualsCaseInsensitiveASCIIT(a, b);
 }
 
-BASE_EXPORT bool RemoveChars(WStringPiece input,
-                             WStringPiece remove_chars,
+BASE_EXPORT bool RemoveChars(std::wstring_view input,
+                             std::wstring_view remove_chars,
                              std::wstring* output);
 
-BASE_EXPORT bool ReplaceChars(WStringPiece input,
-                              WStringPiece replace_chars,
-                              WStringPiece replace_with,
+BASE_EXPORT bool ReplaceChars(std::wstring_view input,
+                              std::wstring_view replace_chars,
+                              std::wstring_view replace_with,
                               std::wstring* output);
 
-BASE_EXPORT bool TrimString(WStringPiece input,
-                            WStringPiece trim_chars,
+BASE_EXPORT bool TrimString(std::wstring_view input,
+                            std::wstring_view trim_chars,
                             std::wstring* output);
 
-BASE_EXPORT WStringPiece TrimString(WStringPiece input,
-                                    WStringPiece trim_chars,
-                                    TrimPositions positions);
+BASE_EXPORT std::wstring_view TrimString(std::wstring_view input,
+                                         std::wstring_view trim_chars,
+                                         TrimPositions positions);
 
-BASE_EXPORT TrimPositions TrimWhitespace(WStringPiece input,
+BASE_EXPORT TrimPositions TrimWhitespace(std::wstring_view input,
                                          TrimPositions positions,
                                          std::wstring* output);
 
-BASE_EXPORT WStringPiece TrimWhitespace(WStringPiece input,
-                                        TrimPositions positions);
+BASE_EXPORT std::wstring_view TrimWhitespace(std::wstring_view input,
+                                             TrimPositions positions);
 
 BASE_EXPORT std::wstring CollapseWhitespace(
-    WStringPiece text,
+    std::wstring_view text,
     bool trim_sequences_with_line_breaks);
 
-BASE_EXPORT bool ContainsOnlyChars(WStringPiece input, WStringPiece characters);
+BASE_EXPORT bool ContainsOnlyChars(std::wstring_view input,
+                                   std::wstring_view characters);
 
 BASE_EXPORT bool EqualsASCII(StringPiece16 str, StringPiece ascii);
 
 BASE_EXPORT bool StartsWith(
-    WStringPiece str,
-    WStringPiece search_for,
+    std::wstring_view str,
+    std::wstring_view search_for,
     CompareCase case_sensitivity = CompareCase::SENSITIVE);
 
 BASE_EXPORT bool EndsWith(
-    WStringPiece str,
-    WStringPiece search_for,
+    std::wstring_view str,
+    std::wstring_view search_for,
     CompareCase case_sensitivity = CompareCase::SENSITIVE);
 
-BASE_EXPORT void ReplaceFirstSubstringAfterOffset(std::wstring* str,
-                                                  size_t start_offset,
-                                                  WStringPiece find_this,
-                                                  WStringPiece replace_with);
+BASE_EXPORT void ReplaceFirstSubstringAfterOffset(
+    std::wstring* str,
+    size_t start_offset,
+    std::wstring_view find_this,
+    std::wstring_view replace_with);
 
 BASE_EXPORT void ReplaceSubstringsAfterOffset(std::wstring* str,
                                               size_t start_offset,
-                                              WStringPiece find_this,
-                                              WStringPiece replace_with);
+                                              std::wstring_view find_this,
+                                              std::wstring_view replace_with);
 
 BASE_EXPORT wchar_t* WriteInto(std::wstring* str, size_t length_with_null);
 
 BASE_EXPORT std::wstring JoinString(span<const std::wstring> parts,
-                                    WStringPiece separator);
+                                    std::wstring_view separator);
 
-BASE_EXPORT std::wstring JoinString(span<const WStringPiece> parts,
-                                    WStringPiece separator);
+BASE_EXPORT std::wstring JoinString(span<const std::wstring_view> parts,
+                                    std::wstring_view separator);
 
-BASE_EXPORT std::wstring JoinString(std::initializer_list<WStringPiece> parts,
-                                    WStringPiece separator);
+BASE_EXPORT std::wstring JoinString(
+    std::initializer_list<std::wstring_view> parts,
+    std::wstring_view separator);
 
 BASE_EXPORT std::wstring ReplaceStringPlaceholders(
-    WStringPiece format_string,
+    std::wstring_view format_string,
     const std::vector<std::wstring>& subst,
     std::vector<size_t>* offsets);
 
diff --git a/base/strings/stringprintf.h b/base/strings/stringprintf.h
index 99d04e8..879dac8 100644
--- a/base/strings/stringprintf.h
+++ b/base/strings/stringprintf.h
@@ -5,30 +5,72 @@
 #ifndef BASE_STRINGS_STRINGPRINTF_H_
 #define BASE_STRINGS_STRINGPRINTF_H_
 
-#include <stdarg.h>   // va_list
+#include <stdarg.h>  // va_list
 
 #include <string>
+#include <string_view>
 
 #include "polyfills/base/base_export.h"
 #include "base/compiler_specific.h"
 
 namespace gurl_base {
 
-// Return a C++ string given printf-like input.
+// 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);
 
-// Return a C++ string given vprintf-like input.
+// 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.
+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...);
+}
+
+// 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);
 
-// Append result to a supplied string.
+// 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);
 
-// Lower-level routine that takes a va_list and appends to a specified
-// string.  All other routines are just convenience wrappers around it.
+// 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);
 
diff --git a/base/strings/sys_string_conversions_unittest.cc b/base/strings/sys_string_conversions_unittest.cc
index 5d898aa..d4552ea 100644
--- a/base/strings/sys_string_conversions_unittest.cc
+++ b/base/strings/sys_string_conversions_unittest.cc
@@ -13,7 +13,7 @@
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#ifdef WCHAR_T_IS_UTF32
+#ifdef WCHAR_T_IS_32_BIT
 static const std::wstring kSysWideOldItalicLetterA = L"\x10300";
 #else
 static const std::wstring kSysWideOldItalicLetterA = L"\xd800\xdf00";
@@ -143,29 +143,31 @@
 }
 
 static const wchar_t* const kConvertRoundtripCases[] = {
-  L"Google Video",
-  // "网页 图片 资讯更多 »"
-  L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb",
-  //  "Παγκόσμιος Ιστός"
-  L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
-  L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2",
-  // "Поиск страниц на русском"
-  L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442"
-  L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430"
-  L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c",
-  // "전체서비스"
-  L"\xc804\xccb4\xc11c\xbe44\xc2a4",
+    L"Google Video",
+    // "网页 图片 资讯更多 »"
+    L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb",
+    //  "Παγκόσμιος Ιστός"
+    L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
+    L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2",
+    // "Поиск страниц на русском"
+    L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442"
+    L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430"
+    L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c",
+    // "전체서비스"
+    L"\xc804\xccb4\xc11c\xbe44\xc2a4",
 
-  // Test characters that take more than 16 bits. This will depend on whether
-  // wchar_t is 16 or 32 bits.
-#if defined(WCHAR_T_IS_UTF16)
-  L"\xd800\xdf00",
-  // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E)
-  L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44",
-#elif defined(WCHAR_T_IS_UTF32)
-  L"\x10300",
-  // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E)
-  L"\x11d40\x11d41\x11d42\x11d43\x11d44",
+// Test characters that take more than 16 bits. This will depend on whether
+// wchar_t is 16 or 32 bits.
+#if defined(WCHAR_T_IS_16_BIT)
+    L"\xd800\xdf00",
+    // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 :
+    // A,B,C,D,E)
+    L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44",
+#elif defined(WCHAR_T_IS_32_BIT)
+    L"\x10300",
+    // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 :
+    // A,B,C,D,E)
+    L"\x11d40\x11d41\x11d42\x11d43\x11d44",
 #endif
 };
 
diff --git a/base/strings/to_string.h b/base/strings/to_string.h
index 26ff960..b873cd4 100644
--- a/base/strings/to_string.h
+++ b/base/strings/to_string.h
@@ -116,7 +116,8 @@
 }  // namespace internal
 
 // Converts any type to a string, preferring defined operator<<() or ToString()
-// methods if they exist.
+// 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) {
   std::ostringstream ss;
diff --git a/base/strings/utf_ostream_operators.cc b/base/strings/utf_ostream_operators.cc
index 2f60140..a090195 100644
--- a/base/strings/utf_ostream_operators.cc
+++ b/base/strings/utf_ostream_operators.cc
@@ -5,6 +5,7 @@
 #include "base/strings/utf_ostream_operators.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "base/types/supports_ostream_operator.h"
 
 std::ostream& std::operator<<(std::ostream& out, const wchar_t* wstr) {
   return out << (wstr ? std::wstring_view(wstr) : std::wstring_view());
diff --git a/base/strings/utf_string_conversion_utils.cc b/base/strings/utf_string_conversion_utils.cc
index d7bbe62..261d730 100644
--- a/base/strings/utf_string_conversion_utils.cc
+++ b/base/strings/utf_string_conversion_utils.cc
@@ -66,7 +66,7 @@
   return IsValidCodepoint(*code_point);
 }
 
-#if defined(WCHAR_T_IS_UTF32)
+#if defined(WCHAR_T_IS_32_BIT)
 bool ReadUnicodeCharacter(const wchar_t* src,
                           size_t src_len,
                           size_t* char_index,
@@ -77,7 +77,7 @@
   // Validate the value.
   return IsValidCodepoint(*code_point);
 }
-#endif  // defined(WCHAR_T_IS_UTF32)
+#endif  // defined(WCHAR_T_IS_32_BIT)
 
 // WriteUnicodeCharacter -------------------------------------------------------
 
diff --git a/base/strings/utf_string_conversion_utils.h b/base/strings/utf_string_conversion_utils.h
index 183a7a6..3dca4b7 100644
--- a/base/strings/utf_string_conversion_utils.h
+++ b/base/strings/utf_string_conversion_utils.h
@@ -70,13 +70,13 @@
                                       size_t* char_index,
                                       base_icu::UChar32* code_point);
 
-#if defined(WCHAR_T_IS_UTF32)
+#if defined(WCHAR_T_IS_32_BIT)
 // Reads UTF-32 character. The usage is the same as the 8-bit version above.
 BASE_EXPORT bool ReadUnicodeCharacter(const wchar_t* src,
                                       size_t src_len,
                                       size_t* char_index,
                                       base_icu::UChar32* code_point);
-#endif  // defined(WCHAR_T_IS_UTF32)
+#endif  // defined(WCHAR_T_IS_32_BIT)
 
 // WriteUnicodeCharacter -------------------------------------------------------
 
@@ -90,7 +90,7 @@
 BASE_EXPORT size_t WriteUnicodeCharacter(base_icu::UChar32 code_point,
                                          std::u16string* output);
 
-#if defined(WCHAR_T_IS_UTF32)
+#if defined(WCHAR_T_IS_32_BIT)
 // Appends the given UTF-32 character to the given 32-bit string.  Returns the
 // number of 32-bit values written.
 inline size_t WriteUnicodeCharacter(base_icu::UChar32 code_point,
@@ -99,7 +99,7 @@
   output->push_back(static_cast<wchar_t>(code_point));
   return 1;
 }
-#endif  // defined(WCHAR_T_IS_UTF32)
+#endif  // defined(WCHAR_T_IS_32_BIT)
 
 // Generalized Unicode converter -----------------------------------------------
 
diff --git a/base/strings/utf_string_conversions.cc b/base/strings/utf_string_conversions.cc
index 383f1a3..31028fb 100644
--- a/base/strings/utf_string_conversions.cc
+++ b/base/strings/utf_string_conversions.cc
@@ -8,10 +8,12 @@
 #include <stdint.h>
 
 #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"
 #include "base/third_party/icu/icu_utf.h"
 #include "build/build_config.h"
@@ -41,7 +43,7 @@
   static constexpr int value = 3;
 };
 
-#if defined(WCHAR_T_IS_UTF32)
+#if defined(WCHAR_T_IS_32_BIT)
 template <>
 struct SizeCoefficient<wchar_t, char> {
   // UTF-8 uses at most 4 codeunits per character.
@@ -53,7 +55,7 @@
   // UTF-16 uses at most 2 codeunits per character.
   static constexpr int value = 2;
 };
-#endif  // defined(WCHAR_T_IS_UTF32)
+#endif  // defined(WCHAR_T_IS_32_BIT)
 
 template <typename SrcChar, typename DestChar>
 constexpr int size_coefficient_v =
@@ -66,9 +68,9 @@
 // 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<Char>::value &&
-                                             CHAR_BIT * sizeof(Char) == N,
-                                         bool>;
+using EnableIfBitsAre =
+    std::enable_if_t<std::is_integral_v<Char> && CHAR_BIT * sizeof(Char) == N,
+                     bool>;
 
 template <typename Char, EnableIfBitsAre<Char, 8> = true>
 void UnicodeAppendUnsafe(Char* out,
@@ -161,7 +163,7 @@
   return success;
 }
 
-#if defined(WCHAR_T_IS_UTF32)
+#if defined(WCHAR_T_IS_32_BIT)
 
 template <typename DestChar>
 bool DoUTFConversion(const wchar_t* src,
@@ -184,7 +186,7 @@
   return success;
 }
 
-#endif  // defined(WCHAR_T_IS_UTF32)
+#endif  // defined(WCHAR_T_IS_32_BIT)
 
 // UTFConversion --------------------------------------------------------------
 // Function template for generating all UTF conversions.
@@ -245,7 +247,7 @@
 
 // UTF-16 <-> Wide -------------------------------------------------------------
 
-#if defined(WCHAR_T_IS_UTF16)
+#if defined(WCHAR_T_IS_16_BIT)
 // When wide == UTF-16 the conversions are a NOP.
 
 bool WideToUTF16(const wchar_t* src, size_t src_len, std::u16string* output) {
@@ -253,7 +255,7 @@
   return true;
 }
 
-std::u16string WideToUTF16(WStringPiece wide) {
+std::u16string WideToUTF16(std::wstring_view wide) {
   return std::u16string(wide.begin(), wide.end());
 }
 
@@ -266,13 +268,13 @@
   return std::wstring(utf16.begin(), utf16.end());
 }
 
-#elif defined(WCHAR_T_IS_UTF32)
+#elif defined(WCHAR_T_IS_32_BIT)
 
 bool WideToUTF16(const wchar_t* src, size_t src_len, std::u16string* output) {
-  return UTFConversion(gurl_base::WStringPiece(src, src_len), output);
+  return UTFConversion(std::wstring_view(src, src_len), output);
 }
 
-std::u16string WideToUTF16(WStringPiece wide) {
+std::u16string WideToUTF16(std::wstring_view wide) {
   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.
@@ -292,7 +294,7 @@
   return ret;
 }
 
-#endif  // defined(WCHAR_T_IS_UTF32)
+#endif  // defined(WCHAR_T_IS_32_BIT)
 
 // UTF-8 <-> Wide --------------------------------------------------------------
 
@@ -310,24 +312,24 @@
   return ret;
 }
 
-#if defined(WCHAR_T_IS_UTF16)
+#if defined(WCHAR_T_IS_16_BIT)
 // Easy case since we can use the "utf" versions we already wrote above.
 
 bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) {
   return UTF16ToUTF8(as_u16cstr(src), src_len, output);
 }
 
-std::string WideToUTF8(WStringPiece wide) {
+std::string WideToUTF8(std::wstring_view wide) {
   return UTF16ToUTF8(StringPiece16(as_u16cstr(wide), wide.size()));
 }
 
-#elif defined(WCHAR_T_IS_UTF32)
+#elif defined(WCHAR_T_IS_32_BIT)
 
 bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) {
-  return UTFConversion(WStringPiece(src, src_len), output);
+  return UTFConversion(std::wstring_view(src, src_len), output);
 }
 
-std::string WideToUTF8(WStringPiece wide) {
+std::string WideToUTF8(std::wstring_view wide) {
   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.
@@ -335,7 +337,7 @@
   return ret;
 }
 
-#endif  // defined(WCHAR_T_IS_UTF32)
+#endif  // defined(WCHAR_T_IS_32_BIT)
 
 std::u16string ASCIIToUTF16(StringPiece ascii) {
   GURL_DCHECK(IsStringASCII(ascii)) << ascii;
@@ -347,16 +349,16 @@
   return std::string(utf16.begin(), utf16.end());
 }
 
-#if defined(WCHAR_T_IS_UTF16)
+#if defined(WCHAR_T_IS_16_BIT)
 std::wstring ASCIIToWide(StringPiece ascii) {
   GURL_DCHECK(IsStringASCII(ascii)) << ascii;
   return std::wstring(ascii.begin(), ascii.end());
 }
 
-std::string WideToASCII(WStringPiece wide) {
+std::string WideToASCII(std::wstring_view wide) {
   GURL_DCHECK(IsStringASCII(wide)) << wide;
   return std::string(wide.begin(), wide.end());
 }
-#endif  // defined(WCHAR_T_IS_UTF16)
+#endif  // defined(WCHAR_T_IS_16_BIT)
 
 }  // namespace base
diff --git a/base/strings/utf_string_conversions.h b/base/strings/utf_string_conversions.h
index d7f1ce7..385a933 100644
--- a/base/strings/utf_string_conversions.h
+++ b/base/strings/utf_string_conversions.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 
 #include <string>
+#include <string_view>
 
 #include "polyfills/base/base_export.h"
 #include "base/strings/string_piece.h"
@@ -24,7 +25,7 @@
 // possible.
 BASE_EXPORT bool WideToUTF8(const wchar_t* src, size_t src_len,
                             std::string* output);
-[[nodiscard]] BASE_EXPORT std::string WideToUTF8(WStringPiece wide);
+[[nodiscard]] BASE_EXPORT std::string WideToUTF8(std::wstring_view wide);
 BASE_EXPORT bool UTF8ToWide(const char* src, size_t src_len,
                             std::wstring* output);
 [[nodiscard]] BASE_EXPORT std::wstring UTF8ToWide(StringPiece utf8);
@@ -32,7 +33,7 @@
 BASE_EXPORT bool WideToUTF16(const wchar_t* src,
                              size_t src_len,
                              std::u16string* output);
-[[nodiscard]] BASE_EXPORT std::u16string WideToUTF16(WStringPiece wide);
+[[nodiscard]] BASE_EXPORT std::u16string WideToUTF16(std::wstring_view wide);
 BASE_EXPORT bool UTF16ToWide(const char16_t* src,
                              size_t src_len,
                              std::wstring* output);
@@ -55,15 +56,15 @@
 // beforehand.
 [[nodiscard]] BASE_EXPORT std::string UTF16ToASCII(StringPiece16 utf16);
 
-#if defined(WCHAR_T_IS_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);
 
 // Converts to 7-bit ASCII by truncating. The result must be known to be ASCII
 // beforehand.
-[[nodiscard]] BASE_EXPORT std::string WideToASCII(WStringPiece wide);
-#endif  // defined(WCHAR_T_IS_UTF16)
+[[nodiscard]] BASE_EXPORT std::string WideToASCII(std::wstring_view wide);
+#endif  // defined(WCHAR_T_IS_16_BIT)
 
 // The conversion functions in this file should not be used to convert string
 // literals. Instead, the corresponding prefixes (e.g. u"" for UTF16 or L"" for
diff --git a/base/strings/utf_string_conversions_unittest.cc b/base/strings/utf_string_conversions_unittest.cc
index cc35a12..55f6296 100644
--- a/base/strings/utf_string_conversions_unittest.cc
+++ b/base/strings/utf_string_conversions_unittest.cc
@@ -16,29 +16,31 @@
 namespace {
 
 const wchar_t* const kConvertRoundtripCases[] = {
-  L"Google Video",
-  // "网页 图片 资讯更多 »"
-  L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb",
-  //  "Παγκόσμιος Ιστός"
-  L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
-  L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2",
-  // "Поиск страниц на русском"
-  L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442"
-  L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430"
-  L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c",
-  // "전체서비스"
-  L"\xc804\xccb4\xc11c\xbe44\xc2a4",
+    L"Google Video",
+    // "网页 图片 资讯更多 »"
+    L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb",
+    //  "Παγκόσμιος Ιστός"
+    L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
+    L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2",
+    // "Поиск страниц на русском"
+    L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442"
+    L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430"
+    L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c",
+    // "전체서비스"
+    L"\xc804\xccb4\xc11c\xbe44\xc2a4",
 
-  // Test characters that take more than 16 bits. This will depend on whether
-  // wchar_t is 16 or 32 bits.
-#if defined(WCHAR_T_IS_UTF16)
-  L"\xd800\xdf00",
-  // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E)
-  L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44",
-#elif defined(WCHAR_T_IS_UTF32)
-  L"\x10300",
-  // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E)
-  L"\x11d40\x11d41\x11d42\x11d43\x11d44",
+// Test characters that take more than 16 bits. This will depend on whether
+// wchar_t is 16 or 32 bits.
+#if defined(WCHAR_T_IS_16_BIT)
+    L"\xd800\xdf00",
+    // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 :
+    // A,B,C,D,E)
+    L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44",
+#elif defined(WCHAR_T_IS_32_BIT)
+    L"\x10300",
+    // ?????  (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 :
+    // A,B,C,D,E)
+    L"\x11d40\x11d41\x11d42\x11d43\x11d44",
 #endif
 };
 
@@ -87,10 +89,10 @@
     {"\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_UTF16)
+#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},
-#elif defined(WCHAR_T_IS_UTF32)
+#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},
 #endif
@@ -117,7 +119,7 @@
   EXPECT_EQ('B', converted[0]);
 }
 
-#if defined(WCHAR_T_IS_UTF16)
+#if defined(WCHAR_T_IS_16_BIT)
 // This test is only valid when wchar_t == UTF-16.
 TEST(UTFStringConversionsTest, ConvertUTF16ToUTF8) {
   struct WideToUTF8Case {
@@ -147,7 +149,7 @@
   }
 }
 
-#elif defined(WCHAR_T_IS_UTF32)
+#elif defined(WCHAR_T_IS_32_BIT)
 // This test is only valid when wchar_t == UTF-32.
 TEST(UTFStringConversionsTest, ConvertUTF32ToUTF8) {
   struct WideToUTF8Case {
@@ -177,7 +179,7 @@
     EXPECT_EQ(expected, converted);
   }
 }
-#endif  // defined(WCHAR_T_IS_UTF32)
+#endif  // defined(WCHAR_T_IS_32_BIT)
 
 TEST(UTFStringConversionsTest, ConvertMultiString) {
   static char16_t multi16[] = {'f',  'o', 'o', '\0', 'b',  'a', 'r',
diff --git a/base/template_util.h b/base/template_util.h
index 480eaca..df1ff8b 100644
--- a/base/template_util.h
+++ b/base/template_util.h
@@ -45,11 +45,11 @@
 
 // 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<T>::value>
+template <typename T, bool = std::is_enum_v<T>>
 struct IsScopedEnumImpl : std::false_type {};
 
 template <typename T>
-struct IsScopedEnumImpl<T, /*std::is_enum<T>::value=*/true>
+struct IsScopedEnumImpl<T, /*std::is_enum_v<T>=*/true>
     : std::negation<std::is_convertible<T, std::underlying_type_t<T>>> {};
 
 }  // namespace internal
diff --git a/base/types/supports_ostream_operator.h b/base/types/supports_ostream_operator.h
new file mode 100644
index 0000000..66eb07b
--- /dev/null
+++ b/base/types/supports_ostream_operator.h
@@ -0,0 +1,24 @@
+// 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_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_
+#define BASE_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_
+
+#include <ostream>
+#include <type_traits>
+#include <utility>
+
+namespace gurl_base::internal {
+
+// Detects whether using operator<< would work.
+//
+// Note that the above #include of <ostream> is necessary to guarantee
+// consistent results here for basic types.
+template <typename T>
+concept SupportsOstreamOperator =
+    requires(const T& t, std::ostream& os) { os << t; };
+
+}  // namespace gurl_base::internal
+
+#endif  // BASE_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_
diff --git a/build/build_config.h b/build/build_config.h
index 2484703..65a2465 100644
--- a/build/build_config.h
+++ b/build/build_config.h
@@ -356,19 +356,19 @@
 
 // Type detection for wchar_t.
 #if defined(OS_WIN)
-#define WCHAR_T_IS_UTF16
+#define WCHAR_T_IS_16_BIT
 #elif defined(OS_FUCHSIA)
-#define WCHAR_T_IS_UTF32
+#define WCHAR_T_IS_32_BIT
 #elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \
     (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
-#define WCHAR_T_IS_UTF32
+#define WCHAR_T_IS_32_BIT
 #elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \
     (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff)
 // On Posix, we'll detect short wchar_t, but projects aren't guaranteed to
 // compile in this mode (in particular, Chrome doesn't). This is intended for
 // other projects using base who manage their own dependencies and make sure
 // short wchar works for them.
-#define WCHAR_T_IS_UTF16
+#define WCHAR_T_IS_16_BIT
 #else
 #error Please add support for your compiler in build/build_config.h
 #endif
diff --git a/build_config/build_config.bzl b/build_config/build_config.bzl
index 5960d2a..466db58 100644
--- a/build_config/build_config.bzl
+++ b/build_config/build_config.bzl
@@ -2,10 +2,10 @@
 
 _default_copts = select({
     "//build_config:windows_x86_64": [
-        "/std:c++17",
+        "/std:c++20",
     ],
     "//conditions:default": [
-        "-std=c++17",
+        "-std=c++20",
         "-fno-strict-aliasing",
     ],
 })
diff --git a/copy.bara.sky b/copy.bara.sky
index 45c9192..e19e158 100644
--- a/copy.bara.sky
+++ b/copy.bara.sky
@@ -20,7 +20,6 @@
         "base/containers/span.h",
         "base/containers/util.h",
         "base/cxx17_backports.h",
-        "base/cxx20_to_address.h",
         "base/cxx20_is_constant_evaluated.h",
         "base/debug/crash_logging.cc",
         "base/debug/crash_logging.h",
@@ -38,6 +37,7 @@
         "base/strings/*.h",
         "base/template_util.h",
         "base/types/always_false.h",
+        "base/types/supports_ostream_operator.h",
         "base/third_party/icu/**",
         "base/win/win_handle_types.h",
         "base/win/win_handle_types_list.inc",
@@ -83,7 +83,6 @@
     "base/logging.h",
     "base/memory/raw_ptr.h",
     "base/memory/raw_ptr_exclusion.h",
-    "base/metrics/histogram_functions.h",
     "base/notreached.h",
     "base/trace_event/memory_usage_estimator.h",
 ]
diff --git a/url/origin.cc b/url/origin.cc
index 45017ea..94d5197 100644
--- a/url/origin.cc
+++ b/url/origin.cc
@@ -78,19 +78,19 @@
 Origin::~Origin() = default;
 
 // static
-absl::optional<Origin> Origin::UnsafelyCreateTupleOriginWithoutNormalization(
+std::optional<Origin> Origin::UnsafelyCreateTupleOriginWithoutNormalization(
     std::string_view scheme,
     std::string_view host,
     uint16_t port) {
   SchemeHostPort tuple(std::string(scheme), std::string(host), port,
                        SchemeHostPort::CHECK_CANONICALIZATION);
   if (!tuple.IsValid())
-    return absl::nullopt;
+    return std::nullopt;
   return Origin(std::move(tuple));
 }
 
 // static
-absl::optional<Origin> Origin::UnsafelyCreateOpaqueOriginWithoutNormalization(
+std::optional<Origin> Origin::UnsafelyCreateOpaqueOriginWithoutNormalization(
     std::string_view precursor_scheme,
     std::string_view precursor_host,
     uint16_t precursor_port,
@@ -104,7 +104,7 @@
   if (!precursor.IsValid() &&
       !(precursor_scheme.empty() && precursor_host.empty() &&
         precursor_port == 0)) {
-    return absl::nullopt;
+    return std::nullopt;
   }
   return Origin(std::move(nonce), std::move(precursor));
 }
@@ -304,11 +304,11 @@
   GURL_DCHECK_EQ(0U, port());
 }
 
-absl::optional<std::string> Origin::SerializeWithNonce() const {
+std::optional<std::string> Origin::SerializeWithNonce() const {
   return SerializeWithNonceImpl();
 }
 
-absl::optional<std::string> Origin::SerializeWithNonceAndInitIfNeeded() {
+std::optional<std::string> Origin::SerializeWithNonceAndInitIfNeeded() {
   GetNonceForSerialization();
   return SerializeWithNonceImpl();
 }
@@ -317,9 +317,9 @@
 // string - tuple_.GetURL().spec().
 // uint64_t (if opaque) - high bits of nonce if opaque. 0 if not initialized.
 // uint64_t (if opaque) - low bits of nonce if opaque. 0 if not initialized.
-absl::optional<std::string> Origin::SerializeWithNonceImpl() const {
+std::optional<std::string> Origin::SerializeWithNonceImpl() const {
   if (!opaque() && !tuple_.IsValid())
-    return absl::nullopt;
+    return std::nullopt;
 
   gurl_base::Pickle pickle;
   pickle.WriteString(tuple_.Serialize());
@@ -339,16 +339,16 @@
 }
 
 // static
-absl::optional<Origin> Origin::Deserialize(const std::string& value) {
+std::optional<Origin> Origin::Deserialize(const std::string& value) {
   std::string data;
   if (!gurl_base::Base64Decode(value, &data))
-    return absl::nullopt;
+    return std::nullopt;
   gurl_base::Pickle pickle(reinterpret_cast<char*>(&data[0]), data.size());
   gurl_base::PickleIterator reader(pickle);
 
   std::string pickled_url;
   if (!reader.ReadString(&pickled_url))
-    return absl::nullopt;
+    return std::nullopt;
   GURL url(pickled_url);
 
   // If only a tuple was serialized, then this origin is not opaque. For opaque
@@ -357,26 +357,26 @@
 
   // Opaque origins without a tuple are ok.
   if (!is_opaque && !url.is_valid())
-    return absl::nullopt;
+    return std::nullopt;
   SchemeHostPort tuple(url);
 
   // Possible successful early return if the pickled Origin was not opaque.
   if (!is_opaque) {
     Origin origin(tuple);
     if (origin.opaque())
-      return absl::nullopt;  // Something went horribly wrong.
+      return std::nullopt;  // Something went horribly wrong.
     return origin;
   }
 
   uint64_t nonce_high = 0;
   if (!reader.ReadUInt64(&nonce_high))
-    return absl::nullopt;
+    return std::nullopt;
 
   uint64_t nonce_low = 0;
   if (!reader.ReadUInt64(&nonce_low))
-    return absl::nullopt;
+    return std::nullopt;
 
-  absl::optional<gurl_base::UnguessableToken> nonce_token =
+  std::optional<gurl_base::UnguessableToken> nonce_token =
       gurl_base::UnguessableToken::Deserialize(nonce_high, nonce_low);
 
   Origin::Nonce nonce;
diff --git a/url/origin.h b/url/origin.h
index d364454..6528946 100644
--- a/url/origin.h
+++ b/url/origin.h
@@ -11,6 +11,7 @@
 #include <string>
 #include <string_view>
 
+#include <optional>
 #include "polyfills/base/component_export.h"
 #include "polyfills/base/debug/alias.h"
 #include "base/debug/crash_logging.h"
@@ -21,7 +22,6 @@
 #include "build/build_config.h"
 #include "build/buildflag.h"
 #include "build/robolectric_buildflags.h"
-#include "absl/types/optional.h"
 #include "url/scheme_host_port.h"
 
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ROBOLECTRIC)
@@ -190,7 +190,7 @@
   // forth over IPC (as transitioning through GURL would risk potentially
   // dangerous recanonicalization); other potential callers should prefer the
   // 'GURL'-based constructor.
-  static absl::optional<Origin> UnsafelyCreateTupleOriginWithoutNormalization(
+  static std::optional<Origin> UnsafelyCreateTupleOriginWithoutNormalization(
       std::string_view scheme,
       std::string_view host,
       uint16_t port);
@@ -417,7 +417,7 @@
   // This factory method should be used in order to pass opaque Origin objects
   // back and forth over IPC (as transitioning through GURL would risk
   // potentially dangerous recanonicalization).
-  static absl::optional<Origin> UnsafelyCreateOpaqueOriginWithoutNormalization(
+  static std::optional<Origin> UnsafelyCreateOpaqueOriginWithoutNormalization(
       std::string_view precursor_scheme,
       std::string_view precursor_host,
       uint16_t precursor_port,
@@ -439,17 +439,17 @@
   // origin's |tuple_| is invalid nullopt is returned. If the nonce is not
   // initialized, a nonce of 0 is used. Use of this method should be limited as
   // an opaque origin will never be matchable in future browser sessions.
-  absl::optional<std::string> SerializeWithNonce() const;
+  std::optional<std::string> SerializeWithNonce() const;
 
   // Like SerializeWithNonce(), but forces |nonce_| to be initialized prior to
   // serializing.
-  absl::optional<std::string> SerializeWithNonceAndInitIfNeeded();
+  std::optional<std::string> SerializeWithNonceAndInitIfNeeded();
 
-  absl::optional<std::string> SerializeWithNonceImpl() const;
+  std::optional<std::string> SerializeWithNonceImpl() const;
 
   // Deserializes an origin from |ToValueWithNonce|. Returns nullopt if the
   // value was invalid in any way.
-  static absl::optional<Origin> Deserialize(const std::string& value);
+  static std::optional<Origin> Deserialize(const std::string& 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
@@ -460,7 +460,7 @@
   // The nonce is used for maintaining identity of an opaque origin. This
   // nonce is preserved when an opaque origin is copied or moved. An Origin
   // is considered opaque if and only if |nonce_| holds a value.
-  absl::optional<Nonce> nonce_;
+  std::optional<Nonce> nonce_;
 };
 
 // Pretty-printers for logging. These expose the internal state of the nonce.
diff --git a/url/origin_unittest.cc b/url/origin_unittest.cc
index ead042a..842522f 100644
--- a/url/origin_unittest.cc
+++ b/url/origin_unittest.cc
@@ -75,7 +75,7 @@
 
   // Wrappers around url::Origin methods to expose it to tests.
 
-  absl::optional<Origin> UnsafelyCreateOpaqueOriginWithoutNormalization(
+  std::optional<Origin> UnsafelyCreateOpaqueOriginWithoutNormalization(
       std::string_view precursor_scheme,
       std::string_view precursor_host,
       uint16_t precursor_port,
@@ -84,16 +84,15 @@
         precursor_scheme, precursor_host, precursor_port, nonce);
   }
 
-  absl::optional<std::string> SerializeWithNonce(const Origin& origin) {
+  std::optional<std::string> SerializeWithNonce(const Origin& origin) {
     return origin.SerializeWithNonce();
   }
 
-  absl::optional<std::string> SerializeWithNonceAndInitIfNeeded(
-      Origin& origin) {
+  std::optional<std::string> SerializeWithNonceAndInitIfNeeded(Origin& origin) {
     return origin.SerializeWithNonceAndInitIfNeeded();
   }
 
-  absl::optional<Origin> Deserialize(const std::string& value) {
+  std::optional<Origin> Deserialize(const std::string& value) {
     return Origin::Deserialize(value);
   }
 
@@ -287,7 +286,7 @@
   for (const auto& test : cases) {
     SCOPED_TRACE(testing::Message()
                  << test.scheme << "://" << test.host << ":" << test.port);
-    absl::optional<url::Origin> origin =
+    std::optional<url::Origin> origin =
         url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
             test.scheme, test.host, test.port);
     ASSERT_TRUE(origin);
@@ -300,7 +299,7 @@
     ExpectParsedUrlsEqual(GURL(origin->Serialize()), origin->GetURL());
 
     gurl_base::UnguessableToken nonce = gurl_base::UnguessableToken::Create();
-    absl::optional<url::Origin> opaque_origin =
+    std::optional<url::Origin> opaque_origin =
         UnsafelyCreateOpaqueOriginWithoutNormalization(
             test.scheme, test.host, test.port, CreateNonce(nonce));
     ASSERT_TRUE(opaque_origin);
@@ -356,7 +355,7 @@
 
   // Opaque origins with unknown precursors are allowed.
   gurl_base::UnguessableToken token = gurl_base::UnguessableToken::Create();
-  absl::optional<url::Origin> anonymous_opaque =
+  std::optional<url::Origin> anonymous_opaque =
       UnsafelyCreateOpaqueOriginWithoutNormalization("", "", 0,
                                                      CreateNonce(token));
   ASSERT_TRUE(anonymous_opaque)
@@ -668,10 +667,10 @@
   for (const GURL& url : valid_urls) {
     SCOPED_TRACE(url.spec());
     Origin origin = Origin::Create(url);
-    absl::optional<std::string> serialized = SerializeWithNonce(origin);
+    std::optional<std::string> serialized = SerializeWithNonce(origin);
     ASSERT_TRUE(serialized);
 
-    absl::optional<Origin> deserialized = Deserialize(std::move(*serialized));
+    std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
     ASSERT_TRUE(deserialized.has_value());
 
     EXPECT_TRUE(DoEqualityComparisons(origin, deserialized.value(), true));
@@ -680,11 +679,11 @@
 }
 
 TEST_F(OriginTest, DeserializeInvalid) {
-  EXPECT_EQ(absl::nullopt, Deserialize(std::string()));
-  EXPECT_EQ(absl::nullopt, Deserialize("deadbeef"));
-  EXPECT_EQ(absl::nullopt, Deserialize("0123456789"));
-  EXPECT_EQ(absl::nullopt, Deserialize("https://a.com"));
-  EXPECT_EQ(absl::nullopt, Deserialize("https://192.168.1.1"));
+  EXPECT_EQ(std::nullopt, Deserialize(std::string()));
+  EXPECT_EQ(std::nullopt, Deserialize("deadbeef"));
+  EXPECT_EQ(std::nullopt, Deserialize("0123456789"));
+  EXPECT_EQ(std::nullopt, Deserialize("https://a.com"));
+  EXPECT_EQ(std::nullopt, Deserialize("https://192.168.1.1"));
 }
 
 TEST_F(OriginTest, SerializeTBDNonce) {
@@ -696,8 +695,8 @@
   for (const GURL& url : invalid_urls) {
     SCOPED_TRACE(url.spec());
     Origin origin = Origin::Create(url);
-    absl::optional<std::string> serialized = SerializeWithNonce(origin);
-    absl::optional<Origin> deserialized = Deserialize(std::move(*serialized));
+    std::optional<std::string> serialized = SerializeWithNonce(origin);
+    std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
     ASSERT_TRUE(deserialized.has_value());
 
     // Can't use DoEqualityComparisons here since empty nonces are never ==
@@ -708,10 +707,10 @@
   {
     // Same basic test as above, but without a GURL to create tuple_.
     Origin opaque;
-    absl::optional<std::string> serialized = SerializeWithNonce(opaque);
+    std::optional<std::string> serialized = SerializeWithNonce(opaque);
     ASSERT_TRUE(serialized);
 
-    absl::optional<Origin> deserialized = Deserialize(std::move(*serialized));
+    std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
     ASSERT_TRUE(deserialized.has_value());
 
     // Can't use DoEqualityComparisons here since empty nonces are never ==
@@ -723,9 +722,9 @@
   for (const GURL& url : invalid_urls) {
     SCOPED_TRACE(url.spec());
     Origin origin = Origin::Create(url);
-    absl::optional<std::string> serialized =
+    std::optional<std::string> serialized =
         SerializeWithNonceAndInitIfNeeded(origin);
-    absl::optional<Origin> deserialized = Deserialize(std::move(*serialized));
+    std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
     ASSERT_TRUE(deserialized.has_value());
 
     // The nonce should have been initialized prior to Serialization().
@@ -737,10 +736,10 @@
   Origin opaque;
   GetNonce(opaque);
 
-  absl::optional<std::string> serialized = SerializeWithNonce(opaque);
+  std::optional<std::string> serialized = SerializeWithNonce(opaque);
   ASSERT_TRUE(serialized);
 
-  absl::optional<Origin> deserialized = Deserialize(std::move(*serialized));
+  std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
   ASSERT_TRUE(deserialized.has_value());
 
   EXPECT_TRUE(DoEqualityComparisons(opaque, deserialized.value(), true));
diff --git a/url/url_canon_internal.cc b/url/url_canon_internal.cc
index da0fb68..76a45d0 100644
--- a/url/url_canon_internal.cc
+++ b/url/url_canon_internal.cc
@@ -281,11 +281,6 @@
 };
 // clang-format on
 
-const char kHexCharLookup[0x10] = {
-    '0', '1', '2', '3', '4', '5', '6', '7',
-    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
-};
-
 const char kCharToHexLookup[8] = {
     0,         // 0x00 - 0x1f
     '0',       // 0x20 - 0x3f: digits 0 - 9 are 0x30 - 0x39
diff --git a/url/url_canon_internal.h b/url/url_canon_internal.h
index 199a8b7..1452e82 100644
--- a/url/url_canon_internal.h
+++ b/url/url_canon_internal.h
@@ -13,8 +13,11 @@
 #include <stddef.h>
 #include <stdlib.h>
 
+#include <string>
+
 #include "polyfills/base/component_export.h"
 #include "polyfills/base/notreached.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/third_party/icu/icu_utf.h"
 #include "url/url_canon.h"
 
@@ -86,10 +89,6 @@
                         SharedCharTypes type,
                         CanonOutput* output);
 
-// Maps the hex numerical values 0x0 to 0xf to the corresponding ASCII digit
-// that will be used to represent it.
-COMPONENT_EXPORT(URL) extern const char kHexCharLookup[0x10];
-
 // This lookup table allows fast conversion between ASCII hex letters and their
 // corresponding numerical value. The 8-bit range is divided up into 8
 // regions of 0x20 characters each. Each of the three character types (numbers,
@@ -135,8 +134,10 @@
 template <typename UINCHAR, typename OUTCHAR>
 inline void AppendEscapedChar(UINCHAR ch, CanonOutputT<OUTCHAR>* output) {
   output->push_back('%');
-  output->push_back(static_cast<OUTCHAR>(kHexCharLookup[(ch >> 4) & 0xf]));
-  output->push_back(static_cast<OUTCHAR>(kHexCharLookup[ch & 0xf]));
+  std::string hex;
+  gurl_base::AppendHexEncodedByte(static_cast<uint8_t>(ch), hex);
+  output->push_back(static_cast<OUTCHAR>(hex[0]));
+  output->push_back(static_cast<OUTCHAR>(hex[1]));
 }
 
 // The character we'll substitute for undecodable or invalid characters.
diff --git a/url/url_canon_ip.cc b/url/url_canon_ip.cc
index 88d9bf5..18f2c15 100644
--- a/url/url_canon_ip.cc
+++ b/url/url_canon_ip.cc
@@ -504,9 +504,7 @@
                                 &num_ipv4_components)) {
       return false;
     }
-    if ((num_ipv4_components != 4 || trailing_dot) &&
-        gurl_base::FeatureList::IsEnabled(
-            url::kStrictIPv4EmbeddedIPv6AddressParsing)) {
+    if ((num_ipv4_components != 4 || trailing_dot)) {
       return false;
     }
   }
diff --git a/url/url_canon_path.cc b/url/url_canon_path.cc
index 440da70..966ce27 100644
--- a/url/url_canon_path.cc
+++ b/url/url_canon_path.cc
@@ -4,11 +4,9 @@
 
 #include <limits.h>
 
+#include <optional>
 #include "polyfills/base/check.h"
 #include "polyfills/base/check_op.h"
-#include "polyfills/base/feature_list.h"
-#include "polyfills/base/metrics/histogram_functions.h"
-#include "absl/types/optional.h"
 #include "url/url_canon.h"
 #include "url/url_canon_internal.h"
 #include "url/url_features.h"
@@ -19,9 +17,9 @@
 namespace {
 
 enum CharacterFlags {
-  // Pass through unchanged, whether escaped or unescaped. This doesn't
+  // Pass through unchanged, whether escaped or not. This doesn't
   // actually set anything so you can't OR it to check, it's just to make the
-  // table below more clear when neither ESCAPE or UNESCAPE is set.
+  // table below more clear when any other flag is not set.
   PASS = 0,
 
   // This character requires special handling in DoPartialPathInternal. Doing
@@ -35,12 +33,6 @@
   // for this is triggered. Not valid with PASS or ESCAPE
   ESCAPE_BIT = 2,
   ESCAPE = ESCAPE_BIT | SPECIAL,
-
-  // This character must be unescaped in canonical output. Not valid with
-  // ESCAPE or PASS. We DON'T set the SPECIAL flag since if we encounter these
-  // characters unescaped, they should just be copied.
-  // TODO(https://crbug.com/1252531): This is guarded by a feature flag.
-  UNESCAPE = 4,
 };
 
 // This table contains one of the above flag values. Note some flags are more
@@ -53,8 +45,9 @@
 // to comply with the URL Standard.
 //
 // Dot is even more special, and the escaped version is handled specially by
-// IsDot. Therefore, we don't need the "escape" flag, and even the "unescape"
-// bit is never handled (we just need the "special") bit.
+// IsDot. Therefore, we don't need the "escape" flag. We just need the "special"
+// bit.
+//
 // clang-format off
 const unsigned char kPathCharLookup[0x100] = {
 //   NULL     control chars...
@@ -62,17 +55,17 @@
 //   control chars...
      ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,
 //   ' '      !        "        #        $        %        &        '        (        )        *        +        ,        -        .        /
-     ESCAPE,  PASS,    ESCAPE,  ESCAPE,  PASS,    ESCAPE,  PASS,    PASS,    PASS,    PASS,    PASS,    PASS,    PASS,    UNESCAPE,SPECIAL, PASS,
+     ESCAPE,  PASS,    ESCAPE,  ESCAPE,  PASS,    ESCAPE,  PASS,    PASS,    PASS,    PASS,    PASS,    PASS,    PASS,    PASS    ,SPECIAL, PASS,
 //   0        1        2        3        4        5        6        7        8        9        :        ;        <        =        >        ?
-     UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,PASS,    PASS,    ESCAPE,  PASS,    ESCAPE,  ESCAPE,
+     PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS,    PASS,    ESCAPE,  PASS,    ESCAPE,  ESCAPE,
 //   @        A        B        C        D        E        F        G        H        I        J        K        L        M        N        O
-     PASS,    UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,
+     PASS,    PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,
 //   P        Q        R        S        T        U        V        W        X        Y        Z        [        \        ]        ^        _
-     UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,PASS,    ESCAPE,  PASS,    ESCAPE,  UNESCAPE,
+     PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS,    ESCAPE,  PASS,    ESCAPE,  PASS    ,
 //   `        a        b        c        d        e        f        g        h        i        j        k        l        m        n        o
-     ESCAPE,  UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,
+     ESCAPE,  PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,
 //   p        q        r        s        t        u        v        w        x        y        z        {        |        }        ~        <NBSP>
-     UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,UNESCAPE,ESCAPE,  ESCAPE,  ESCAPE,  UNESCAPE,ESCAPE,
+     PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,PASS    ,ESCAPE,  ESCAPE,  ESCAPE,  PASS    ,ESCAPE,
 //   ...all the high-bit characters are escaped
      ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,
      ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,  ESCAPE,
@@ -173,76 +166,6 @@
   output->set_length(i + 1);
 }
 
-// Looks for problematic nested escape sequences and escapes the output as
-// needed to ensure they can't be misinterpreted.
-//
-// Our concern is that in input escape sequence that's invalid because it
-// contains nested escape sequences might look valid once those are unescaped.
-// For example, "%%300" is not a valid escape sequence, but after unescaping the
-// inner "%30" this becomes "%00" which is valid.  Leaving this in the output
-// string can result in callers re-canonicalizing the string and unescaping this
-// sequence, thus resulting in something fundamentally different than the
-// original input here.  This can cause a variety of problems.
-//
-// This function is called after we've just unescaped a sequence that's within
-// two output characters of a previous '%' that we know didn't begin a valid
-// escape sequence in the input string.  We look for whether the output is going
-// to turn into a valid escape sequence, and if so, convert the initial '%' into
-// an escaped "%25" so the output can't be misinterpreted.
-//
-// |spec| is the input string we're canonicalizing.
-// |next_input_index| is the index of the next unprocessed character in |spec|.
-// |input_len| is the length of |spec|.
-// |last_invalid_percent_index| is the index in |output| of a previously-seen
-// '%' character.  The caller knows this '%' character isn't followed by a valid
-// escape sequence in the input string.
-// |output| is the canonicalized output thus far.  The caller guarantees this
-// ends with a '%' followed by one or two characters, and the '%' is the one
-// pointed to by |last_invalid_percent_index|.  The last character in the string
-// was just unescaped.
-template <typename CHAR>
-void CheckForNestedEscapes(const CHAR* spec,
-                           size_t next_input_index,
-                           size_t input_len,
-                           size_t last_invalid_percent_index,
-                           CanonOutput* output) {
-  const size_t length = output->length();
-  const char last_unescaped_char = output->at(length - 1);
-
-  // If |output| currently looks like "%c", we need to try appending the next
-  // input character to see if this will result in a problematic escape
-  // sequence.  Note that this won't trigger on the first nested escape of a
-  // two-escape sequence like "%%30%30" -- we'll allow the conversion to
-  // "%0%30" -- but the second nested escape will be caught by this function
-  // when it's called again in that case.
-  const bool append_next_char = last_invalid_percent_index == length - 2;
-  if (append_next_char) {
-    // If the input doesn't contain a 7-bit character next, this case won't be a
-    // problem.
-    if ((next_input_index == input_len) || (spec[next_input_index] >= 0x80))
-      return;
-    output->push_back(static_cast<char>(spec[next_input_index]));
-  }
-
-  // Now output ends like "%cc".  Try to unescape this.
-  size_t begin = last_invalid_percent_index;
-  unsigned char temp;
-  if (DecodeEscaped(output->data(), &begin, output->length(), &temp)) {
-    // New escape sequence found.  Overwrite the characters following the '%'
-    // with "25", and push_back() the one or two characters that were following
-    // the '%' when we were called.
-    if (!append_next_char)
-      output->push_back(output->at(last_invalid_percent_index + 1));
-    output->set(last_invalid_percent_index + 1, '2');
-    output->set(last_invalid_percent_index + 2, '5');
-    output->push_back(last_unescaped_char);
-  } else if (append_next_char) {
-    // Not a valid escape sequence, but we still need to undo appending the next
-    // source character so the caller can process it normally.
-    output->set_length(length);
-  }
-}
-
 // Canonicalizes and appends the given path to the output. It assumes that if
 // the input path starts with a slash, it should be copied to the output.
 //
@@ -264,13 +187,7 @@
 
   size_t end = static_cast<size_t>(path.end());
 
-  // We use this variable to minimize the amount of work done when unescaping --
-  // we'll only call CheckForNestedEscapes() when this points at one of the last
-  // couple of characters in |output|.
-  absl::optional<size_t> last_invalid_percent_index;
-
   bool success = true;
-  bool unescape_escaped_char = false;
   for (size_t i = static_cast<size_t>(path.begin); i < end; i++) {
     UCHAR uch = static_cast<UCHAR>(spec[i]);
     if (sizeof(CHAR) > 1 && uch >= 0x80) {
@@ -310,9 +227,6 @@
                 break;
               case DIRECTORY_UP:
                 BackUpToPreviousSlash(path_begin_in_output, output);
-                if (last_invalid_percent_index >= output->length()) {
-                  last_invalid_percent_index = absl::nullopt;
-                }
                 i += dotlen + consumed_len - 1;
                 break;
             }
@@ -329,47 +243,18 @@
 
         } else if (out_ch == '%') {
           // Handle escape sequences.
-          unsigned char unescaped_value;
-          if (DecodeEscaped(spec, &i, end, &unescaped_value)) {
-            // Valid escape sequence, see if we keep, reject, or unescape it.
-            // Note that at this point DecodeEscape() will have advanced |i| to
-            // the last character of the escape sequence.
-            char unescaped_flags = kPathCharLookup[unescaped_value];
-
-            if (!url::IsUsingDontDecodeAsciiPercentEncodedURLPath() &&
-                (unescaped_flags & UNESCAPE)) {
-              // This escaped value shouldn't be escaped.  Try to copy it.
-              unescape_escaped_char = true;
-
-              output->push_back(unescaped_value);
-              // If we just unescaped a value within 2 output characters of the
-              // '%' from a previously-detected invalid escape sequence, we
-              // might have an input string with problematic nested escape
-              // sequences; detect and fix them.
-              if (last_invalid_percent_index.has_value() &&
-                  ((last_invalid_percent_index.value() + 3) >=
-                   output->length())) {
-                CheckForNestedEscapes(spec, i + 1, end,
-                                      last_invalid_percent_index.value(),
-                                      output);
-              }
-            } else {
-              // Either this is an invalid escaped character, or it's a valid
-              // escaped character we should keep escaped.  In the first case we
-              // should just copy it exactly and remember the error.  In the
-              // second we also copy exactly in case the server is sensitive to
-              // changing the case of any hex letters.
-              output->push_back('%');
-              output->push_back(static_cast<char>(spec[i - 1]));
-              output->push_back(static_cast<char>(spec[i]));
-            }
+          unsigned char unused_unescaped_value;
+          if (DecodeEscaped(spec, &i, end, &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]));
           } else {
             // Invalid escape sequence. IE7+ rejects any URLs with such
             // sequences, while other browsers pass them through unchanged. We
             // use the permissive behavior.
             // TODO(brettw): Consider testing IE's strict behavior, which would
             // allow removing the code to handle nested escapes above.
-            last_invalid_percent_index = output->length();
             output->push_back('%');
           }
         } else if (flags & ESCAPE_BIT) {
@@ -382,8 +267,6 @@
       }
     }
   }
-  gurl_base::UmaHistogramBoolean("URL.Path.UnescapeEscapedChar",
-                            unescape_escaped_char);
   return success;
 }
 
diff --git a/url/url_canon_unittest.cc b/url/url_canon_unittest.cc
index da4b7b3..e0ac0ee 100644
--- a/url/url_canon_unittest.cc
+++ b/url/url_canon_unittest.cc
@@ -8,9 +8,9 @@
 #include <stddef.h>
 #include <string_view>
 
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/gtest_util.h"
-#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/third_party/mozilla/url_parse.h"
@@ -55,17 +55,6 @@
   const char* expected_address_hex;  // Two hex chars per IP address byte.
 };
 
-std::string BytesToHexString(unsigned char bytes[16], int length) {
-  EXPECT_TRUE(length == 0 || length == 4 || length == 16)
-      << "Bad IP address length: " << length;
-  std::string result;
-  for (int i = 0; i < length; ++i) {
-    result.push_back(kHexCharLookup[(bytes[i] >> 4) & 0xf]);
-    result.push_back(kHexCharLookup[bytes[i] & 0xf]);
-  }
-  return result;
-}
-
 struct ReplaceCase {
   const char* base;
   const char* scheme;
@@ -184,7 +173,7 @@
       }
       output.Complete();
       EXPECT_EQ(utf_case.expected_success, success);
-      EXPECT_EQ(std::string(utf_case.output), out_str);
+      EXPECT_EQ(utf_case.output, out_str);
     }
     if (utf_case.input16) {
       out_str.clear();
@@ -200,7 +189,7 @@
       }
       output.Complete();
       EXPECT_EQ(utf_case.expected_success, success);
-      EXPECT_EQ(std::string(utf_case.output), out_str);
+      EXPECT_EQ(utf_case.output, out_str);
     }
 
     if (utf_case.input8 && utf_case.input16 && utf_case.expected_success) {
@@ -253,7 +242,7 @@
     output1.Complete();
 
     EXPECT_EQ(scheme_case.expected_success, success);
-    EXPECT_EQ(std::string(scheme_case.expected), out_str);
+    EXPECT_EQ(scheme_case.expected, out_str);
     EXPECT_EQ(scheme_case.expected_component.begin, out_comp.begin);
     EXPECT_EQ(scheme_case.expected_component.len, out_comp.len);
 
@@ -268,7 +257,7 @@
     output2.Complete();
 
     EXPECT_EQ(scheme_case.expected_success, success);
-    EXPECT_EQ(std::string(scheme_case.expected), out_str);
+    EXPECT_EQ(scheme_case.expected, out_str);
     EXPECT_EQ(scheme_case.expected_component.begin, out_comp.begin);
     EXPECT_EQ(scheme_case.expected_component.len, out_comp.len);
   }
@@ -282,7 +271,7 @@
   EXPECT_FALSE(CanonicalizeScheme("", Component(0, -1), &output, &out_comp));
   output.Complete();
 
-  EXPECT_EQ(std::string(":"), out_str);
+  EXPECT_EQ(":", out_str);
   EXPECT_EQ(0, out_comp.begin);
   EXPECT_EQ(0, out_comp.len);
 }
@@ -622,7 +611,7 @@
 
       EXPECT_EQ(host_case.expected_family != CanonHostInfo::BROKEN, success)
           << "for input: " << host_case.input8;
-      EXPECT_EQ(std::string(host_case.expected), out_str)
+      EXPECT_EQ(host_case.expected, out_str)
           << "for input: " << host_case.input8;
       EXPECT_EQ(host_case.expected_component.begin, out_comp.begin)
           << "for input: " << host_case.input8;
@@ -646,7 +635,7 @@
       output.Complete();
 
       EXPECT_EQ(host_case.expected_family != CanonHostInfo::BROKEN, success);
-      EXPECT_EQ(std::string(host_case.expected), out_str);
+      EXPECT_EQ(host_case.expected, out_str);
       EXPECT_EQ(host_case.expected_component.begin, out_comp.begin);
       EXPECT_EQ(host_case.expected_component.len, out_comp.len);
     }
@@ -667,11 +656,13 @@
       output.Complete();
 
       EXPECT_EQ(host_case.expected_family, host_info.family);
-      EXPECT_EQ(std::string(host_case.expected), out_str);
+      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(std::string(host_case.expected_address_hex),
-                BytesToHexString(host_info.address, host_info.AddressLength()));
+      EXPECT_EQ(
+          host_case.expected_address_hex,
+          gurl_base::HexEncode(host_info.address,
+                          static_cast<size_t>(host_info.AddressLength())));
       if (host_case.expected_family == CanonHostInfo::IPV4) {
         EXPECT_EQ(host_case.expected_num_ipv4_components,
                   host_info.num_ipv4_components);
@@ -693,11 +684,13 @@
       output.Complete();
 
       EXPECT_EQ(host_case.expected_family, host_info.family);
-      EXPECT_EQ(std::string(host_case.expected), out_str);
+      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(std::string(host_case.expected_address_hex),
-                BytesToHexString(host_info.address, host_info.AddressLength()));
+      EXPECT_EQ(
+          host_case.expected_address_hex,
+          gurl_base::HexEncode(host_info.address,
+                          static_cast<size_t>(host_info.AddressLength())));
       if (host_case.expected_family == CanonHostInfo::IPV4) {
         EXPECT_EQ(host_case.expected_num_ipv4_components,
                   host_info.num_ipv4_components);
@@ -875,8 +868,9 @@
     output1.Complete();
 
     EXPECT_EQ(test_case.expected_family, host_info.family);
-    EXPECT_EQ(std::string(test_case.expected_address_hex),
-              BytesToHexString(host_info.address, host_info.AddressLength()));
+    EXPECT_EQ(test_case.expected_address_hex,
+              gurl_base::HexEncode(host_info.address,
+                              static_cast<size_t>(host_info.AddressLength())));
     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);
@@ -896,8 +890,9 @@
     output2.Complete();
 
     EXPECT_EQ(test_case.expected_family, host_info.family);
-    EXPECT_EQ(std::string(test_case.expected_address_hex),
-              BytesToHexString(host_info.address, host_info.AddressLength()));
+    EXPECT_EQ(test_case.expected_address_hex,
+              gurl_base::HexEncode(host_info.address,
+                              static_cast<size_t>(host_info.AddressLength())));
     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);
@@ -908,153 +903,162 @@
   }
 }
 
-class URLCanonIPv6Test
-    : public ::testing::Test,
-      public ::testing::WithParamInterface<bool> {
- public:
-  URLCanonIPv6Test() {
-    if (GetParam()) {
-      scoped_feature_list_.InitAndEnableFeature(kStrictIPv4EmbeddedIPv6AddressParsing);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(kStrictIPv4EmbeddedIPv6AddressParsing);
-    }
-  }
-
- private:
-  gurl_base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-INSTANTIATE_TEST_SUITE_P(All,
-                         URLCanonIPv6Test,
-                         ::testing::Bool());
-
-TEST_P(URLCanonIPv6Test, IPv6) {
-  bool strict_ipv4_embedded_ipv6_parsing =
-      gurl_base::FeatureList::IsEnabled(url::kStrictIPv4EmbeddedIPv6AddressParsing);
-
+TEST(URLCanonTest, IPv6) {
   IPAddressCase cases[] = {
       // Empty is not an IP address.
-    {"", L"", "", Component(), CanonHostInfo::NEUTRAL, -1, ""},
+      {"", L"", "", Component(), CanonHostInfo::NEUTRAL, -1, ""},
       // Non-IPs with [:] characters are marked BROKEN.
-    {":", L":", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[", L"[", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[:", L"[:", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"]", L"]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {":]", L":]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[]", L"[]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[:]", L"[:]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {":", L":", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[", L"[", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[:", L"[:", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"]", L"]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {":]", L":]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[]", L"[]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[:]", L"[:]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
       // Regular IP address is invalid without bounding '[' and ']'.
-    {"2001:db8::1", L"2001:db8::1", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[2001:db8::1", L"[2001:db8::1", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"2001:db8::1]", L"2001:db8::1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"2001:db8::1", L"2001:db8::1", "", Component(), CanonHostInfo::BROKEN,
+       -1, ""},
+      {"[2001:db8::1", L"[2001:db8::1", "", Component(), CanonHostInfo::BROKEN,
+       -1, ""},
+      {"2001:db8::1]", L"2001:db8::1]", "", Component(), CanonHostInfo::BROKEN,
+       -1, ""},
       // Regular IP addresses.
-    {"[::]", L"[::]", "[::]", Component(0,4), CanonHostInfo::IPV6, -1, "00000000000000000000000000000000"},
-    {"[::1]", L"[::1]", "[::1]", Component(0,5), CanonHostInfo::IPV6, -1, "00000000000000000000000000000001"},
-    {"[1::]", L"[1::]", "[1::]", Component(0,5), CanonHostInfo::IPV6, -1, "00010000000000000000000000000000"},
+      {"[::]", L"[::]", "[::]", Component(0, 4), CanonHostInfo::IPV6, -1,
+       "00000000000000000000000000000000"},
+      {"[::1]", L"[::1]", "[::1]", Component(0, 5), CanonHostInfo::IPV6, -1,
+       "00000000000000000000000000000001"},
+      {"[1::]", L"[1::]", "[1::]", Component(0, 5), CanonHostInfo::IPV6, -1,
+       "00010000000000000000000000000000"},
 
-    // Leading zeros should be stripped.
-    {"[000:01:02:003:004:5:6:007]", L"[000:01:02:003:004:5:6:007]", "[0:1:2:3:4:5:6:7]", Component(0,17), CanonHostInfo::IPV6, -1, "00000001000200030004000500060007"},
+      // Leading zeros should be stripped.
+      {"[000:01:02:003:004:5:6:007]", L"[000:01:02:003:004:5:6:007]",
+       "[0:1:2:3:4:5:6:7]", Component(0, 17), CanonHostInfo::IPV6, -1,
+       "00000001000200030004000500060007"},
 
-    // Upper case letters should be lowercased.
-    {"[A:b:c:DE:fF:0:1:aC]", L"[A:b:c:DE:fF:0:1:aC]", "[a:b:c:de:ff:0:1:ac]", Component(0,20), CanonHostInfo::IPV6, -1, "000A000B000C00DE00FF0000000100AC"},
+      // Upper case letters should be lowercased.
+      {"[A:b:c:DE:fF:0:1:aC]", L"[A:b:c:DE:fF:0:1:aC]", "[a:b:c:de:ff:0:1:ac]",
+       Component(0, 20), CanonHostInfo::IPV6, -1,
+       "000A000B000C00DE00FF0000000100AC"},
 
-    // The same address can be written with different contractions, but should
-    // get canonicalized to the same thing.
-    {"[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"},
-    {"[1::2:0:0:3:0]", L"[1::2:0:0:3:0]", "[1::2:0:0:3:0]", Component(0,14), CanonHostInfo::IPV6, -1, "00010000000000020000000000030000"},
+      // The same address can be written with different contractions, but should
+      // get canonicalized to the same thing.
+      {"[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"},
+      {"[1::2:0:0:3:0]", L"[1::2:0:0:3:0]", "[1::2:0:0:3:0]", Component(0, 14),
+       CanonHostInfo::IPV6, -1, "00010000000000020000000000030000"},
 
-    // Addresses with embedded IPv4.
-    {"[::192.168.0.1]", L"[::192.168.0.1]", "[::c0a8:1]", Component(0,10), CanonHostInfo::IPV6, -1, "000000000000000000000000C0A80001"},
-    {"[::ffff:192.168.0.1]", L"[::ffff:192.168.0.1]", "[::ffff:c0a8:1]", Component(0,15), CanonHostInfo::IPV6, -1, "00000000000000000000FFFFC0A80001"},
-    {"[::eeee:192.168.0.1]", L"[::eeee:192.168.0.1]", "[::eeee:c0a8:1]", Component(0, 15), CanonHostInfo::IPV6, -1, "00000000000000000000EEEEC0A80001"},
-    {"[2001::192.168.0.1]", L"[2001::192.168.0.1]", "[2001::c0a8:1]", Component(0, 14), CanonHostInfo::IPV6, -1, "200100000000000000000000C0A80001"},
-    {"[1:2:192.168.0.1:5:6]", L"[1:2:192.168.0.1:5:6]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // Addresses with embedded IPv4.
+      {"[::192.168.0.1]", L"[::192.168.0.1]", "[::c0a8:1]", Component(0, 10),
+       CanonHostInfo::IPV6, -1, "000000000000000000000000C0A80001"},
+      {"[::ffff:192.168.0.1]", L"[::ffff:192.168.0.1]", "[::ffff:c0a8:1]",
+       Component(0, 15), CanonHostInfo::IPV6, -1,
+       "00000000000000000000FFFFC0A80001"},
+      {"[::eeee:192.168.0.1]", L"[::eeee:192.168.0.1]", "[::eeee:c0a8:1]",
+       Component(0, 15), CanonHostInfo::IPV6, -1,
+       "00000000000000000000EEEEC0A80001"},
+      {"[2001::192.168.0.1]", L"[2001::192.168.0.1]", "[2001::c0a8:1]",
+       Component(0, 14), CanonHostInfo::IPV6, -1,
+       "200100000000000000000000C0A80001"},
+      {"[1:2:192.168.0.1:5:6]", L"[1:2:192.168.0.1:5:6]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
 
-    // IPv4 embedded IPv6 addresses
-    {"[::ffff:192.1.2]",
-     L"[::ffff:192.1.2]",
-     "[::ffff:c001:2]",
-     strict_ipv4_embedded_ipv6_parsing ? Component() : Component(0,15),
-     strict_ipv4_embedded_ipv6_parsing ? CanonHostInfo::BROKEN : CanonHostInfo::IPV6,
-     -1,
-     (strict_ipv4_embedded_ipv6_parsing ? "" : "00000000000000000000FFFFC0010002")},
-    {"[::ffff:192.1]",
-     L"[::ffff:192.1]",
-     "[::ffff:c000:1]",
-     strict_ipv4_embedded_ipv6_parsing ? Component() : Component(0,15),
-     strict_ipv4_embedded_ipv6_parsing ? CanonHostInfo::BROKEN : CanonHostInfo::IPV6,
-     -1,
-     (strict_ipv4_embedded_ipv6_parsing ? "" : "00000000000000000000FFFFC0000001")},
-    {"[::ffff:192.1.2.3.4]",
-     L"[::ffff:192.1.2.3.4]",
-     "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // IPv4 embedded IPv6 addresses
+      {"[::ffff:192.1.2]", L"[::ffff:192.1.2]", "[::ffff:c001:2]", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[::ffff:192.1]", L"[::ffff:192.1]", "[::ffff:c000:1]", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[::ffff:192.1.2.3.4]", L"[::ffff:192.1.2.3.4]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
 
-    // IPv4 using hex.
-    // TODO(eroman): Should this format be disallowed?
-    {"[::ffff:0xC0.0Xa8.0x0.0x1]", L"[::ffff:0xC0.0Xa8.0x0.0x1]", "[::ffff:c0a8:1]", Component(0,15), CanonHostInfo::IPV6, -1, "00000000000000000000FFFFC0A80001"},
+      // IPv4 using hex.
+      // TODO(eroman): Should this format be disallowed?
+      {"[::ffff:0xC0.0Xa8.0x0.0x1]", L"[::ffff:0xC0.0Xa8.0x0.0x1]",
+       "[::ffff:c0a8:1]", Component(0, 15), CanonHostInfo::IPV6, -1,
+       "00000000000000000000FFFFC0A80001"},
 
-    // There may be zeros surrounding the "::" contraction.
-    {"[0:0::0:0:8]", L"[0:0::0:0:8]", "[::8]", Component(0,5), CanonHostInfo::IPV6, -1, "00000000000000000000000000000008"},
+      // There may be zeros surrounding the "::" contraction.
+      {"[0:0::0:0:8]", L"[0:0::0:0:8]", "[::8]", Component(0, 5),
+       CanonHostInfo::IPV6, -1, "00000000000000000000000000000008"},
 
-    {"[2001:db8::1]", L"[2001:db8::1]", "[2001:db8::1]", Component(0,13), CanonHostInfo::IPV6, -1, "20010DB8000000000000000000000001"},
+      {"[2001:db8::1]", L"[2001:db8::1]", "[2001:db8::1]", Component(0, 13),
+       CanonHostInfo::IPV6, -1, "20010DB8000000000000000000000001"},
 
-    // Can only have one "::" contraction in an IPv6 string literal.
-    {"[2001::db8::1]", L"[2001::db8::1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    // No more than 2 consecutive ':'s.
-    {"[2001:db8:::1]", L"[2001:db8:::1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[:::]", L"[:::]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    // Non-IP addresses due to invalid characters.
-    {"[2001::.com]", L"[2001::.com]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    // If there are not enough components, the last one should fill them out.
-    // ... omitted at this time ...
-    // Too many components means not an IP address. Similarly, with too few
-    // if using IPv4 compat or mapped addresses.
-    {"[::192.168.0.0.1]", L"[::192.168.0.0.1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[::ffff:192.168.0.0.1]", L"[::ffff:192.168.0.0.1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[1:2:3:4:5:6:7:8:9]", L"[1:2:3:4:5:6:7:8:9]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    // Too many bits (even though 8 comonents, the last one holds 32 bits).
-    {"[0:0:0:0:0:0:0:192.168.0.1]", L"[0:0:0:0:0:0:0:192.168.0.1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // Can only have one "::" contraction in an IPv6 string literal.
+      {"[2001::db8::1]", L"[2001::db8::1]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      // No more than 2 consecutive ':'s.
+      {"[2001:db8:::1]", L"[2001:db8:::1]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[:::]", L"[:::]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // Non-IP addresses due to invalid characters.
+      {"[2001::.com]", L"[2001::.com]", "", Component(), CanonHostInfo::BROKEN,
+       -1, ""},
+      // If there are not enough components, the last one should fill them out.
+      // ... omitted at this time ...
+      // Too many components means not an IP address. Similarly, with too few
+      // if using IPv4 compat or mapped addresses.
+      {"[::192.168.0.0.1]", L"[::192.168.0.0.1]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[::ffff:192.168.0.0.1]", L"[::ffff:192.168.0.0.1]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[1:2:3:4:5:6:7:8:9]", L"[1:2:3:4:5:6:7:8:9]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      // Too many bits (even though 8 components, the last one holds 32 bits).
+      {"[0:0:0:0:0:0:0:192.168.0.1]", L"[0:0:0:0:0:0:0:192.168.0.1]", "",
+       Component(), CanonHostInfo::BROKEN, -1, ""},
 
-    // Too many bits specified -- the contraction would have to be zero-length
-    // to not exceed 128 bits.
-    {"[1:2:3:4:5:6::192.168.0.1]", L"[1:2:3:4:5:6::192.168.0.1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // Too many bits specified -- the contraction would have to be zero-length
+      // to not exceed 128 bits.
+      {"[1:2:3:4:5:6::192.168.0.1]", L"[1:2:3:4:5:6::192.168.0.1]", "",
+       Component(), CanonHostInfo::BROKEN, -1, ""},
 
-    // The contraction is for 16 bits of zero.
-    {"[1:2:3:4:5:6::8]", L"[1:2:3:4:5:6::8]", "[1:2:3:4:5:6:0:8]", Component(0,17), CanonHostInfo::IPV6, -1, "00010002000300040005000600000008"},
+      // The contraction is for 16 bits of zero.
+      {"[1:2:3:4:5:6::8]", L"[1:2:3:4:5:6::8]", "[1:2:3:4:5:6:0:8]",
+       Component(0, 17), CanonHostInfo::IPV6, -1,
+       "00010002000300040005000600000008"},
 
-    // Cannot have a trailing colon.
-    {"[1:2:3:4:5:6:7:8:]", L"[1:2:3:4:5:6:7:8:]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[1:2:3:4:5:6:192.168.0.1:]", L"[1:2:3:4:5:6:192.168.0.1:]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // Cannot have a trailing colon.
+      {"[1:2:3:4:5:6:7:8:]", L"[1:2:3:4:5:6:7:8:]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[1:2:3:4:5:6:192.168.0.1:]", L"[1:2:3:4:5:6:192.168.0.1:]", "",
+       Component(), CanonHostInfo::BROKEN, -1, ""},
 
-    // Cannot have negative numbers.
-    {"[-1:2:3:4:5:6:7:8]", L"[-1:2:3:4:5:6:7:8]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // Cannot have negative numbers.
+      {"[-1:2:3:4:5:6:7:8]", L"[-1:2:3:4:5:6:7:8]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
 
-    // Scope ID -- the URL may contain an optional ["%" <scope_id>] section.
-    // The scope_id should be included in the canonicalized URL, and is an
-    // unsigned decimal number.
+      // Scope ID -- the URL may contain an optional ["%" <scope_id>] section.
+      // The scope_id should be included in the canonicalized URL, and is an
+      // unsigned decimal number.
 
-    // Invalid because no ID was given after the percent.
+      // Invalid because no ID was given after the percent.
 
-    // Don't allow scope-id
-    {"[1::%1]", L"[1::%1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[1::%eth0]", L"[1::%eth0]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[1::%]", L"[1::%]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[%]", L"[%]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[::%:]", L"[::%:]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // Don't allow scope-id
+      {"[1::%1]", L"[1::%1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[1::%eth0]", L"[1::%eth0]", "", Component(), CanonHostInfo::BROKEN, -1,
+       ""},
+      {"[1::%]", L"[1::%]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[%]", L"[%]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[::%:]", L"[::%:]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
 
-    // Don't allow leading or trailing colons.
-    {"[:0:0::0:0:8]", L"[:0:0::0:0:8]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[0:0::0:0:8:]", L"[0:0::0:0:8:]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
-    {"[:0:0::0:0:8:]", L"[:0:0::0:0:8:]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      // Don't allow leading or trailing colons.
+      {"[:0:0::0:0:8]", L"[:0:0::0:0:8]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[0:0::0:0:8:]", L"[0:0::0:0:8:]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[:0:0::0:0:8:]", L"[:0:0::0:0:8:]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
 
       // We allow a single trailing dot.
-    // ... omitted at this time ...
+      // ... omitted at this time ...
       // Two dots in a row means not an IP address.
-    {"[::192.168..1]", L"[::192.168..1]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[::192.168..1]", L"[::192.168..1]", "", Component(),
+       CanonHostInfo::BROKEN, -1, ""},
       // Any non-first components get truncated to one byte.
-    // ... omitted at this time ...
+      // ... omitted at this time ...
       // Spaces should be rejected.
-    {"[::1 hello]", L"[::1 hello]", "", Component(), CanonHostInfo::BROKEN, -1, ""},
+      {"[::1 hello]", L"[::1 hello]", "", Component(), CanonHostInfo::BROKEN,
+       -1, ""},
   };
 
   for (size_t i = 0; i < std::size(cases); i++) {
@@ -1068,8 +1072,10 @@
     output1.Complete();
 
     EXPECT_EQ(cases[i].expected_family, host_info.family);
-    EXPECT_EQ(std::string(cases[i].expected_address_hex),
-              BytesToHexString(host_info.address, host_info.AddressLength())) << "iter " << i << " host " << cases[i].input8;
+    EXPECT_EQ(cases[i].expected_address_hex,
+              gurl_base::HexEncode(host_info.address,
+                              static_cast<size_t>(host_info.AddressLength())))
+        << "iter " << i << " host " << cases[i].input8;
     if (host_info.family == CanonHostInfo::IPV6) {
       EXPECT_STREQ(cases[i].expected, out_str1.c_str());
       EXPECT_EQ(cases[i].expected_component.begin,
@@ -1088,8 +1094,9 @@
     output2.Complete();
 
     EXPECT_EQ(cases[i].expected_family, host_info.family);
-    EXPECT_EQ(std::string(cases[i].expected_address_hex),
-              BytesToHexString(host_info.address, host_info.AddressLength()));
+    EXPECT_EQ(cases[i].expected_address_hex,
+              gurl_base::HexEncode(host_info.address,
+                              static_cast<size_t>(host_info.AddressLength())));
     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);
@@ -1197,7 +1204,7 @@
     output1.Complete();
 
     EXPECT_EQ(user_info_case.expected_success, success);
-    EXPECT_EQ(std::string(user_info_case.expected), out_str);
+    EXPECT_EQ(user_info_case.expected, out_str);
     EXPECT_EQ(user_info_case.expected_username.begin, out_user.begin);
     EXPECT_EQ(user_info_case.expected_username.len, out_user.len);
     EXPECT_EQ(user_info_case.expected_password.begin, out_pass.begin);
@@ -1217,7 +1224,7 @@
     output2.Complete();
 
     EXPECT_EQ(user_info_case.expected_success, success);
-    EXPECT_EQ(std::string(user_info_case.expected), out_str);
+    EXPECT_EQ(user_info_case.expected, out_str);
     EXPECT_EQ(user_info_case.expected_username.begin, out_user.begin);
     EXPECT_EQ(user_info_case.expected_username.len, out_user.len);
     EXPECT_EQ(user_info_case.expected_password.begin, out_pass.begin);
@@ -1259,7 +1266,7 @@
     output1.Complete();
 
     EXPECT_EQ(port_case.expected_success, success);
-    EXPECT_EQ(std::string(port_case.expected), out_str);
+    EXPECT_EQ(port_case.expected, out_str);
     EXPECT_EQ(port_case.expected_component.begin, out_comp.begin);
     EXPECT_EQ(port_case.expected_component.len, out_comp.len);
 
@@ -1272,7 +1279,7 @@
     output2.Complete();
 
     EXPECT_EQ(port_case.expected_success, success);
-    EXPECT_EQ(std::string(port_case.expected), out_str);
+    EXPECT_EQ(port_case.expected, out_str);
     EXPECT_EQ(port_case.expected_component.begin, out_comp.begin);
     EXPECT_EQ(port_case.expected_component.len, out_comp.len);
   }
@@ -2847,54 +2854,4 @@
   output.set_length(0);
 }
 
-class URLCanonAsciiPercentEncodePathTest
-    : public ::testing::Test,
-      public ::testing::WithParamInterface<bool> {
- public:
-  URLCanonAsciiPercentEncodePathTest() {
-    if (GetParam()) {
-      scoped_feature_list_.InitAndEnableFeature(
-          url::kDontDecodeAsciiPercentEncodedURLPath);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(
-          url::kDontDecodeAsciiPercentEncodedURLPath);
-    }
-  }
-
- private:
-  gurl_base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-INSTANTIATE_TEST_SUITE_P(All,
-                         URLCanonAsciiPercentEncodePathTest,
-                         ::testing::Bool());
-
-TEST_P(URLCanonAsciiPercentEncodePathTest, UnescapePathCharHistogram) {
-  struct TestCase {
-    std::string_view path;
-    gurl_base::HistogramBase::Count cnt;
-  } cases[] = {
-      {"/a", 0},
-      {"/%61", 1},
-      {"/%61%61", 1},
-  };
-
-  for (const auto& c : cases) {
-    gurl_base::HistogramTester histogram_tester;
-    Component in_comp(0, c.path.size());
-    Component out_comp;
-    std::string out_str;
-    StdStringCanonOutput output(&out_str);
-    bool success = CanonicalizePath(c.path.data(), in_comp, &output, &out_comp);
-    ASSERT_TRUE(success);
-    if (gurl_base::FeatureList::IsEnabled(
-            url::kDontDecodeAsciiPercentEncodedURLPath)) {
-      histogram_tester.ExpectBucketCount("URL.Path.UnescapeEscapedChar", 1, 0);
-    } else {
-      histogram_tester.ExpectBucketCount("URL.Path.UnescapeEscapedChar", 1,
-                                         c.cnt);
-    }
-  }
-}
-
 }  // namespace url
diff --git a/url/url_features.cc b/url/url_features.cc
index 9c9827b..b649c86 100644
--- a/url/url_features.cc
+++ b/url/url_features.cc
@@ -16,25 +16,21 @@
              "RecordIDNA2008Metrics",
              gurl_base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kStrictIPv4EmbeddedIPv6AddressParsing,
-             "StrictIPv4EmbeddedIPv6AddressParsing",
-             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 crbug.com/1252531.
-BASE_FEATURE(kDontDecodeAsciiPercentEncodedURLPath,
-             "DontDecodeAsciiPercentEncodedURLPath",
-             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",
+             gurl_base::FEATURE_DISABLED_BY_DEFAULT);
+
 bool IsUsingIDNA2008NonTransitional() {
   // 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.
@@ -46,17 +42,6 @@
   return gurl_base::FeatureList::IsEnabled(kUseIDNA2008NonTransitional);
 }
 
-bool IsUsingDontDecodeAsciiPercentEncodedURLPath() {
-  // If the FeatureList isn't available yet, fall back to the feature's default
-  // state. This may happen during early startup, see https://crbug.com/1478960.
-  if (!gurl_base::FeatureList::GetInstance()) {
-    return kDontDecodeAsciiPercentEncodedURLPath.default_state ==
-           gurl_base::FEATURE_ENABLED_BY_DEFAULT;
-  }
-
-  return gurl_base::FeatureList::IsEnabled(kDontDecodeAsciiPercentEncodedURLPath);
-}
-
 bool IsUsingStandardCompliantHostCharacters() {
   // 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.
@@ -68,6 +53,17 @@
   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);
 }
diff --git a/url/url_features.h b/url/url_features.h
index 55b3234..ca52b80 100644
--- a/url/url_features.h
+++ b/url/url_features.h
@@ -18,35 +18,30 @@
 // Returns true if Chrome is recording IDNA 2008 related metrics.
 COMPONENT_EXPORT(URL) bool IsRecordingIDNA2008Metrics();
 
-// Returns true if kDontDecodeAsciiPercentEncodedURLPath feature is enabled.
-// See url::kDontDecodeAsciiPercentEncodedURLPath for details.
-COMPONENT_EXPORT(URL) bool IsUsingDontDecodeAsciiPercentEncodedURLPath();
-
-// Returns true if kDontDecodeAsciiPercentEncodedURLPath feature is enabled.
+// Returns true if IsUsingStandardCompliantHostCharacters feature is enabled.
 // See url::kStandardCompliantHostCharacters for details.
 COMPONENT_EXPORT(URL) bool IsUsingStandardCompliantHostCharacters();
 
-// Returns true if Chrome is enforcing the 4 part check for IPv4 embedded IPv6
-// addresses.
-COMPONENT_EXPORT(URL)
-BASE_DECLARE_FEATURE(kStrictIPv4EmbeddedIPv6AddressParsing);
+// 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'.)
 COMPONENT_EXPORT(URL)
 BASE_DECLARE_FEATURE(kResolveBareFragmentWithColonOnNonHierarchical);
 
-// When enabled, percent-encoded ASCII characters in URL path are not decoded
-// automatically. See https://crbug.com/125231.
-COMPONENT_EXPORT(URL)
-BASE_DECLARE_FEATURE(kDontDecodeAsciiPercentEncodedURLPath);
-
 // 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);
 
+// 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);
+
 }  // namespace url
 
 #endif  // URL_URL_FEATURES_H_
diff --git a/url/url_parse_unittest.cc b/url/url_parse_unittest.cc
index 88b6f05..d67becf 100644
--- a/url/url_parse_unittest.cc
+++ b/url/url_parse_unittest.cc
@@ -134,11 +134,11 @@
     "http://user@",
     "http:",
   };
-  for (size_t i = 0; i < std::size(length_cases); i++) {
-    int true_length = static_cast<int>(strlen(length_cases[i]));
+  for (const char* length_case : length_cases) {
+    int true_length = static_cast<int>(strlen(length_case));
 
     Parsed parsed;
-    ParseStandardURL(length_cases[i], true_length, &parsed);
+    ParseStandardURL(length_case, true_length, &parsed);
 
     EXPECT_EQ(true_length, parsed.Length());
   }
@@ -193,154 +193,159 @@
     {"file:///c:/foo", Parsed::HOST, true, 7},
     {"file:///c:/foo", Parsed::PATH, true, 7},
   };
-  for (size_t i = 0; i < std::size(count_cases); i++) {
-    int length = static_cast<int>(strlen(count_cases[i].url));
+  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_cases[i].url[0] == 'f')
-      ParseFileURL(count_cases[i].url, length, &parsed);
-    else
-      ParseStandardURL(count_cases[i].url, length, &parsed);
+    if (length > 0 && count_case.url[0] == 'f') {
+      ParseFileURL(count_case.url, length, &parsed);
+    } else {
+      ParseStandardURL(count_case.url, length, &parsed);
+    }
 
     int chars_before = parsed.CountCharactersBefore(
-        count_cases[i].component, count_cases[i].include_delimiter);
-    EXPECT_EQ(count_cases[i].expected_count, chars_before);
+        count_case.component, count_case.include_delimiter);
+    EXPECT_EQ(count_case.expected_count, chars_before);
   }
 }
 
 // Standard --------------------------------------------------------------------
 
-// Input                               Scheme  Usrname Passwd     Host         Port Path       Query        Ref
-// ------------------------------------ ------- ------- ---------- ------------ --- ---------- ------------ -----
+// clang-format off
+// Input                               Scheme  Usrname  Passwd     Host         Port Path       Query        Ref
+// ------------------------------------ ------- -------- ---------- ------------ --- ---------- ------------ -----
 static URLParseCase cases[] = {
   // Regular URL with all the parts
-{"http://user:pass@foo:21/bar;par?b#c", "http", "user", "pass",    "foo",       21, "/bar;par","b",          "c"},
+{"http://user:pass@foo:21/bar;par?b#c", "http", "user",  "pass",    "foo",       21, "/bar;par","b",          "c"},
 
   // Known schemes should lean towards authority identification
-{"http:foo.com",                        "http", NULL,  NULL,      "foo.com",    -1, NULL,      NULL,        NULL},
+{"http:foo.com",                        "http", nullptr, nullptr,   "foo.com",    -1, nullptr,   nullptr,     nullptr},
 
   // Spaces!
-{"\t   :foo.com   \n",                  "",     NULL,  NULL,      "foo.com",    -1, NULL,      NULL,        NULL},
-{" foo.com  ",                          NULL,   NULL,  NULL,      "foo.com",    -1, NULL,      NULL,        NULL},
-{"a:\t foo.com",                        "a",    NULL,  NULL,      "\t foo.com", -1, NULL,      NULL,        NULL},
-{"http://f:21/ b ? d # e ",             "http", NULL,  NULL,      "f",          21, "/ b ",    " d ",       " e"},
+{"\t   :foo.com   \n",                  "",     nullptr, nullptr,   "foo.com",    -1, nullptr,   nullptr,     nullptr},
+{" foo.com  ",                          nullptr,nullptr, nullptr,   "foo.com",    -1, nullptr,   nullptr,     nullptr},
+{"a:\t foo.com",                        "a",    nullptr, nullptr,   "\t foo.com", -1, nullptr,   nullptr,     nullptr},
+{"http://f:21/ b ? d # e ",             "http", nullptr, nullptr,   "f",          21, "/ b ",    " d ",       " e"},
 
   // Invalid port numbers should be identified and turned into -2, empty port
   // numbers should be -1. Spaces aren't allowed in port numbers
-{"http://f:/c",                         "http", NULL,  NULL,      "f",          -1, "/c",      NULL,        NULL},
-{"http://f:0/c",                        "http", NULL,  NULL,      "f",           0, "/c",      NULL,        NULL},
-{"http://f:00000000000000/c",           "http", NULL,  NULL,      "f",           0, "/c",      NULL,        NULL},
-{"http://f:00000000000000000000080/c",  "http", NULL,  NULL,      "f",          80, "/c",      NULL,        NULL},
-{"http://f:b/c",                        "http", NULL,  NULL,      "f",          -2, "/c",      NULL,        NULL},
-{"http://f: /c",                        "http", NULL,  NULL,      "f",          -2, "/c",      NULL,        NULL},
-{"http://f:\n/c",                       "http", NULL,  NULL,      "f",          -2, "/c",      NULL,        NULL},
-{"http://f:fifty-two/c",                "http", NULL,  NULL,      "f",          -2, "/c",      NULL,        NULL},
-{"http://f:999999/c",                   "http", NULL,  NULL,      "f",          -2, "/c",      NULL,        NULL},
-{"http://f: 21 / b ? d # e ",           "http", NULL,  NULL,      "f",          -2, "/ b ",    " d ",       " e"},
+{"http://f:/c",                         "http", nullptr, nullptr,   "f",          -1, "/c",      nullptr,     nullptr},
+{"http://f:0/c",                        "http", nullptr, nullptr,   "f",           0, "/c",      nullptr,     nullptr},
+{"http://f:00000000000000/c",           "http", nullptr, nullptr,   "f",           0, "/c",      nullptr,     nullptr},
+{"http://f:00000000000000000000080/c",  "http", nullptr, nullptr,   "f",          80, "/c",      nullptr,     nullptr},
+{"http://f:b/c",                        "http", nullptr, nullptr,   "f",          -2, "/c",      nullptr,     nullptr},
+{"http://f: /c",                        "http", nullptr, nullptr,   "f",          -2, "/c",      nullptr,     nullptr},
+{"http://f:\n/c",                       "http", nullptr, nullptr,   "f",          -2, "/c",      nullptr,     nullptr},
+{"http://f:fifty-two/c",                "http", nullptr, nullptr,   "f",          -2, "/c",      nullptr,     nullptr},
+{"http://f:999999/c",                   "http", nullptr, nullptr,   "f",          -2, "/c",      nullptr,     nullptr},
+{"http://f: 21 / b ? d # e ",           "http", nullptr, nullptr,   "f",          -2, "/ b ",    " d ",       " e"},
 
   // Creative URLs missing key elements
-{"",                                    NULL,   NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{"  \t",                                NULL,   NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{":foo.com/",                           "",     NULL,  NULL,      "foo.com",    -1, "/",       NULL,        NULL},
-{":foo.com\\",                          "",     NULL,  NULL,      "foo.com",    -1, "\\",      NULL,        NULL},
-{":",                                   "",     NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{":a",                                  "",     NULL,  NULL,      "a",          -1, NULL,      NULL,        NULL},
-{":/",                                  "",     NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{":\\",                                 "",     NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{":#",                                  "",     NULL,  NULL,      NULL,         -1, NULL,      NULL,        ""},
-{"#",                                   NULL,   NULL,  NULL,      NULL,         -1, NULL,      NULL,        ""},
-{"#/",                                  NULL,   NULL,  NULL,      NULL,         -1, NULL,      NULL,        "/"},
-{"#\\",                                 NULL,   NULL,  NULL,      NULL,         -1, NULL,      NULL,        "\\"},
-{"#;?",                                 NULL,   NULL,  NULL,      NULL,         -1, NULL,      NULL,        ";?"},
-{"?",                                   NULL,   NULL,  NULL,      NULL,         -1, NULL,      "",          NULL},
-{"/",                                   NULL,   NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{":23",                                 "",     NULL,  NULL,      "23",         -1, NULL,      NULL,        NULL},
-{"/:23",                                "/",    NULL,  NULL,      "23",         -1, NULL,      NULL,        NULL},
-{"//",                                  NULL,   NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{"::",                                  "",     NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{"::23",                                "",     NULL,  NULL,      NULL,         23, NULL,      NULL,        NULL},
-{"foo://",                              "foo",  NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
+{"",                                    nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{"  \t",                                nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{":foo.com/",                           "",     nullptr, nullptr,   "foo.com",    -1, "/",       nullptr,     nullptr},
+{":foo.com\\",                          "",     nullptr, nullptr,   "foo.com",    -1, "\\",      nullptr,     nullptr},
+{":",                                   "",     nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{":a",                                  "",     nullptr, nullptr,   "a",          -1, nullptr,   nullptr,     nullptr},
+{":/",                                  "",     nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{":\\",                                 "",     nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{":#",                                  "",     nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     ""},
+{"#",                                   nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     ""},
+{"#/",                                  nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     "/"},
+{"#\\",                                 nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     "\\"},
+{"#;?",                                 nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     ";?"},
+{"?",                                   nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   "",          nullptr},
+{"/",                                   nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{":23",                                 "",     nullptr, nullptr,   "23",         -1, nullptr,   nullptr,     nullptr},
+{"/:23",                                "/",    nullptr, nullptr,   "23",         -1, nullptr,   nullptr,     nullptr},
+{"//",                                  nullptr,nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{"::",                                  "",     nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{"::23",                                "",     nullptr, nullptr,   nullptr,      23, nullptr,   nullptr,     nullptr},
+{"foo://",                              "foo",  nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
 
   // Username/passwords and things that look like them
-{"http://a:b@c:29/d",                   "http", "a",   "b",       "c",          29, "/d",      NULL,        NULL},
-{"http::@c:29",                         "http", "",    "",        "c",          29, NULL,      NULL,        NULL},
+{"http://a:b@c:29/d",                   "http", "a",    "b",       "c",           29, "/d",      nullptr,     nullptr},
+{"http::@c:29",                         "http", "",     "",        "c",           29, nullptr,   nullptr,     nullptr},
   // ... "]" in the password field isn't allowed, but we tolerate it here...
-{"http://&a:foo(b]c@d:2/",              "http", "&a",  "foo(b]c", "d",           2, "/",       NULL,        NULL},
-{"http://::@c@d:2",                     "http", "",    ":@c",     "d",           2, NULL,      NULL,        NULL},
-{"http://foo.com:b@d/",                 "http", "foo.com", "b",   "d",          -1, "/",       NULL,        NULL},
+{"http://&a:foo(b]c@d:2/",              "http", "&a",   "foo(b]c", "d",            2, "/",       nullptr,     nullptr},
+{"http://::@c@d:2",                     "http", "",     ":@c",     "d",            2, nullptr,   nullptr,     nullptr},
+{"http://foo.com:b@d/",                 "http", "foo.com","b",     "d",           -1, "/",       nullptr,     nullptr},
 
-{"http://foo.com/\\@",                  "http", NULL,  NULL,      "foo.com",    -1, "/\\@",    NULL,        NULL},
-{"http:\\\\foo.com\\",                  "http", NULL,  NULL,      "foo.com",    -1, "\\",      NULL,        NULL},
-{"http:\\\\a\\b:c\\d@foo.com\\",        "http", NULL,  NULL,      "a",          -1, "\\b:c\\d@foo.com\\", NULL,   NULL},
+{"http://foo.com/\\@",                  "http", nullptr, nullptr,   "foo.com",    -1, "/\\@",    nullptr,     nullptr},
+{"http:\\\\foo.com\\",                  "http", nullptr, nullptr,   "foo.com",    -1, "\\",      nullptr,     nullptr},
+{"http:\\\\a\\b:c\\d@foo.com\\",        "http", nullptr, nullptr,   "a",          -1, "\\b:c\\d@foo.com\\", nullptr,nullptr},
 
   // Tolerate different numbers of slashes.
-{"foo:/",                               "foo",  NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{"foo:/bar.com/",                       "foo",  NULL,  NULL,      "bar.com",    -1, "/",       NULL,        NULL},
-{"foo://///////",                       "foo",  NULL,  NULL,      NULL,         -1, NULL,      NULL,        NULL},
-{"foo://///////bar.com/",               "foo",  NULL,  NULL,      "bar.com",    -1, "/",       NULL,        NULL},
-{"foo:////://///",                      "foo",  NULL,  NULL,      NULL,         -1, "/////",   NULL,        NULL},
+{"foo:/",                               "foo",  nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{"foo:/bar.com/",                       "foo",  nullptr, nullptr,   "bar.com",    -1, "/",       nullptr,     nullptr},
+{"foo://///////",                       "foo",  nullptr, nullptr,   nullptr,      -1, nullptr,   nullptr,     nullptr},
+{"foo://///////bar.com/",               "foo",  nullptr, nullptr,   "bar.com",    -1, "/",       nullptr,     nullptr},
+{"foo:////://///",                      "foo",  nullptr, nullptr,   nullptr,      -1, "/////",   nullptr,     nullptr},
 
   // Raw file paths on Windows aren't handled by the parser.
-{"c:/foo",                              "c",    NULL,  NULL,      "foo",        -1, NULL,      NULL,        NULL},
-{"//foo/bar",                           NULL,   NULL,  NULL,      "foo",        -1, "/bar",    NULL,        NULL},
+{"c:/foo",                              "c",    nullptr, nullptr,   "foo",        -1, nullptr,   nullptr,     nullptr},
+{"//foo/bar",                           nullptr,nullptr, nullptr,   "foo",        -1, "/bar",    nullptr,     nullptr},
 
   // Use the first question mark for the query and the ref.
-{"http://foo/path;a??e#f#g",            "http", NULL,  NULL,      "foo",        -1, "/path;a", "?e",      "f#g"},
-{"http://foo/abcd?efgh?ijkl",           "http", NULL,  NULL,      "foo",        -1, "/abcd",   "efgh?ijkl", NULL},
-{"http://foo/abcd#foo?bar",             "http", NULL,  NULL,      "foo",        -1, "/abcd",   NULL,        "foo?bar"},
+{"http://foo/path;a??e#f#g",            "http", nullptr, nullptr,   "foo",        -1, "/path;a", "?e",        "f#g"},
+{"http://foo/abcd?efgh?ijkl",           "http", nullptr, nullptr,   "foo",        -1, "/abcd",   "efgh?ijkl", nullptr},
+{"http://foo/abcd#foo?bar",             "http", nullptr, nullptr,   "foo",        -1, "/abcd",   nullptr,     "foo?bar"},
 
   // IPv6, check also interesting uses of colons.
-{"[61:24:74]:98",                       "[61",  NULL,  NULL,      "24:74]",     98, NULL,      NULL,        NULL},
-{"http://[61:27]:98",                   "http", NULL,  NULL,      "[61:27]",    98, NULL,      NULL,        NULL},
-{"http:[61:27]/:foo",                   "http", NULL,  NULL,      "[61:27]",    -1, "/:foo",   NULL,        NULL},
-{"http://[1::2]:3:4",                   "http", NULL,  NULL,      "[1::2]:3",    4, NULL,      NULL,        NULL},
+{"[61:24:74]:98",                       "[61",  nullptr, nullptr,   "24:74]",     98, nullptr,   nullptr,     nullptr},
+{"http://[61:27]:98",                   "http", nullptr, nullptr,   "[61:27]",    98, nullptr,   nullptr,     nullptr},
+{"http:[61:27]/:foo",                   "http", nullptr, nullptr,   "[61:27]",    -1, "/:foo",   nullptr,     nullptr},
+{"http://[1::2]:3:4",                   "http", nullptr, nullptr,   "[1::2]:3",    4, nullptr,   nullptr,     nullptr},
 
   // Partially-complete IPv6 literals, and related cases.
-{"http://2001::1",                      "http", NULL,  NULL,      "2001:",       1, NULL,      NULL,        NULL},
-{"http://[2001::1",                     "http", NULL,  NULL,      "[2001::1",   -1, NULL,      NULL,        NULL},
-{"http://2001::1]",                     "http", NULL,  NULL,      "2001::1]",   -1, NULL,      NULL,        NULL},
-{"http://2001::1]:80",                  "http", NULL,  NULL,      "2001::1]",   80, NULL,      NULL,        NULL},
-{"http://[2001::1]",                    "http", NULL,  NULL,      "[2001::1]",  -1, NULL,      NULL,        NULL},
-{"http://[2001::1]:80",                 "http", NULL,  NULL,      "[2001::1]",  80, NULL,      NULL,        NULL},
-{"http://[[::]]",                       "http", NULL,  NULL,      "[[::]]",     -1, NULL,      NULL,        NULL},
+{"http://2001::1",                      "http", nullptr, nullptr,   "2001:",       1, nullptr,   nullptr,     nullptr},
+{"http://[2001::1",                     "http", nullptr, nullptr,   "[2001::1",   -1, nullptr,   nullptr,     nullptr},
+{"http://2001::1]",                     "http", nullptr, nullptr,   "2001::1]",   -1, nullptr,   nullptr,     nullptr},
+{"http://2001::1]:80",                  "http", nullptr, nullptr,   "2001::1]",   80, nullptr,   nullptr,     nullptr},
+{"http://[2001::1]",                    "http", nullptr, nullptr,   "[2001::1]",  -1, nullptr,   nullptr,     nullptr},
+{"http://[2001::1]:80",                 "http", nullptr, nullptr,   "[2001::1]",  80, nullptr,   nullptr,     nullptr},
+{"http://[[::]]",                       "http", nullptr, nullptr,   "[[::]]",     -1, nullptr,   nullptr,     nullptr},
 
 };
+// clang-format on
 
 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 (size_t i = 0; i < std::size(cases); i++) {
-    const char* url = cases[i].input;
+  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, cases[i].scheme, parsed.scheme));
-    EXPECT_TRUE(ComponentMatches(url, cases[i].username, parsed.username));
-    EXPECT_TRUE(ComponentMatches(url, cases[i].password, parsed.password));
-    EXPECT_TRUE(ComponentMatches(url, cases[i].host, parsed.host));
-    EXPECT_EQ(cases[i].port, port);
-    EXPECT_TRUE(ComponentMatches(url, cases[i].path, parsed.path));
-    EXPECT_TRUE(ComponentMatches(url, cases[i].query, parsed.query));
-    EXPECT_TRUE(ComponentMatches(url, cases[i].ref, parsed.ref));
+    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));
   }
 }
 
 // PathURL --------------------------------------------------------------------
 
 // Various incarnations of path URLs.
+// clang-format off
 static PathURLParseCase path_cases[] = {
-{"",                                        NULL,          NULL},
-{":",                                       "",            NULL},
+{"",                                        nullptr,       nullptr},
+{":",                                       "",            nullptr},
 {":/",                                      "",            "/"},
-{"/",                                       NULL,          "/"},
-{" This is \\interesting// \t",             NULL,          "This is \\interesting// \t"},
-{"about:",                                  "about",       NULL},
+{"/",                                       nullptr,       "/"},
+{" This is \\interesting// \t",             nullptr,       "This is \\interesting// \t"},
+{"about:",                                  "about",       nullptr},
 {"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) {
   // Declared outside for loop to try to catch cases in init() where we forget
@@ -364,82 +369,84 @@
 }
 
 // Various incarnations of file URLs.
+// clang-format off
 static URLParseCase file_cases[] = {
 #ifdef WIN32
-{"file:server",              "file", NULL, NULL, "server", -1, NULL,          NULL, NULL},
-{"  file: server  \t",       "file", NULL, NULL, " server",-1, NULL,          NULL, NULL},
-{"FiLe:c|",                  "FiLe", NULL, NULL, NULL,     -1, "c|",          NULL, NULL},
-{"FILE:/\\\\/server/file",   "FILE", NULL, NULL, "server", -1, "/file",       NULL, NULL},
-{"file://server/",           "file", NULL, NULL, "server", -1, "/",           NULL, NULL},
-{"file://localhost/c:/",     "file", NULL, NULL, "localhost", -1, "/c:/",     NULL, NULL},
-{"file://127.0.0.1/c|\\",    "file", NULL, NULL, "127.0.0.1", -1, "/c|\\",    NULL, NULL},
-{"file:/",                   "file", NULL, NULL, NULL,     -1, NULL,          NULL, NULL},
-{"file:",                    "file", NULL, NULL, NULL,     -1, NULL,          NULL, NULL},
+{"file:server",              "file", nullptr, nullptr, "server", -1, nullptr,       nullptr, nullptr},
+{"  file: server  \t",       "file", nullptr, nullptr, " server",-1, nullptr,       nullptr, nullptr},
+{"FiLe:c|",                  "FiLe", nullptr, nullptr, nullptr,  -1, "c|",          nullptr, nullptr},
+{"FILE:/\\\\/server/file",   "FILE", nullptr, nullptr, "server", -1, "/file",       nullptr, nullptr},
+{"file://server/",           "file", nullptr, nullptr, "server", -1, "/",           nullptr, nullptr},
+{"file://localhost/c:/",     "file", nullptr, nullptr, "localhost", -1, "/c:/",     nullptr, nullptr},
+{"file://127.0.0.1/c|\\",    "file", nullptr, nullptr, "127.0.0.1", -1, "/c|\\",    nullptr, nullptr},
+{"file:/",                   "file", nullptr, nullptr, nullptr,  -1, nullptr,       nullptr, nullptr},
+{"file:",                    "file", nullptr, nullptr, nullptr,  -1, nullptr,       nullptr, nullptr},
   // If there is a Windows drive letter, treat any number of slashes as the
   // path part.
-{"file:c:\\fo\\b",           "file", NULL, NULL, NULL,     -1, "c:\\fo\\b",   NULL, NULL},
-{"file:/c:\\foo/bar",        "file", NULL, NULL, NULL,     -1, "/c:\\foo/bar",NULL, NULL},
-{"file://c:/f\\b",           "file", NULL, NULL, NULL,     -1, "/c:/f\\b",    NULL, NULL},
-{"file:///C:/foo",           "file", NULL, NULL, NULL,     -1, "/C:/foo",     NULL, NULL},
-{"file://///\\/\\/c:\\f\\b", "file", NULL, NULL, NULL,     -1, "/c:\\f\\b",   NULL, NULL},
+{"file:c:\\fo\\b",           "file", nullptr, nullptr, nullptr,  -1, "c:\\fo\\b",   nullptr, nullptr},
+{"file:/c:\\foo/bar",        "file", nullptr, nullptr, nullptr,  -1, "/c:\\foo/bar",nullptr, nullptr},
+{"file://c:/f\\b",           "file", nullptr, nullptr, nullptr,  -1, "/c:/f\\b",    nullptr, nullptr},
+{"file:///C:/foo",           "file", nullptr, nullptr, nullptr,  -1, "/C:/foo",     nullptr, nullptr},
+{"file://///\\/\\/c:\\f\\b", "file", nullptr, nullptr, nullptr,  -1, "/c:\\f\\b",   nullptr, nullptr},
   // If there is not a drive letter, we should treat is as UNC EXCEPT for
   // three slashes, which we treat as a Unix style path.
-{"file:server/file",         "file", NULL, NULL, "server", -1, "/file",       NULL, NULL},
-{"file:/server/file",        "file", NULL, NULL, "server", -1, "/file",       NULL, NULL},
-{"file://server/file",       "file", NULL, NULL, "server", -1, "/file",       NULL, NULL},
-{"file:///server/file",      "file", NULL, NULL, NULL,     -1, "/server/file",NULL, NULL},
-{"file://\\server/file",     "file", NULL, NULL, NULL,     -1, "\\server/file",NULL, NULL},
-{"file:////server/file",     "file", NULL, NULL, "server", -1, "/file",       NULL, NULL},
+{"file:server/file",         "file", nullptr, nullptr, "server", -1, "/file",       nullptr, nullptr},
+{"file:/server/file",        "file", nullptr, nullptr, "server", -1, "/file",       nullptr, nullptr},
+{"file://server/file",       "file", nullptr, nullptr, "server", -1, "/file",       nullptr, nullptr},
+{"file:///server/file",      "file", nullptr, nullptr, nullptr,  -1, "/server/file",nullptr, nullptr},
+{"file://\\server/file",     "file", nullptr, nullptr, nullptr,  -1, "\\server/file",nullptr, nullptr},
+{"file:////server/file",     "file", nullptr, nullptr, "server", -1, "/file",       nullptr, nullptr},
   // Queries and refs are valid for file URLs as well.
-{"file:///C:/foo.html?#",   "file", NULL, NULL,  NULL,     -1, "/C:/foo.html",  "",   ""},
-{"file:///C:/foo.html?query=yes#ref", "file", NULL, NULL, NULL, -1, "/C:/foo.html", "query=yes", "ref"},
+{"file:///C:/foo.html?#",   "file", nullptr, nullptr,  nullptr,  -1, "/C:/foo.html",  "",   ""},
+{"file:///C:/foo.html?query=yes#ref", "file", nullptr, nullptr, nullptr, -1, "/C:/foo.html", "query=yes", "ref"},
 #else  // WIN32
   // No slashes.
-  {"file:",                    "file", NULL, NULL, NULL,      -1, NULL,             NULL, NULL},
-  {"file:path",                "file", NULL, NULL, NULL,      -1, "path",           NULL, NULL},
-  {"file:path/",               "file", NULL, NULL, NULL,      -1, "path/",          NULL, NULL},
-  {"file:path/f.txt",          "file", NULL, NULL, NULL,      -1, "path/f.txt",     NULL, NULL},
+  {"file:",                    "file", nullptr, nullptr, nullptr,   -1, nullptr,          nullptr, nullptr},
+  {"file:path",                "file", nullptr, nullptr, nullptr,   -1, "path",           nullptr, nullptr},
+  {"file:path/",               "file", nullptr, nullptr, nullptr,   -1, "path/",          nullptr, nullptr},
+  {"file:path/f.txt",          "file", nullptr, nullptr, nullptr,   -1, "path/f.txt",     nullptr, nullptr},
   // One slash.
-  {"file:/",                   "file", NULL, NULL, NULL,      -1, "/",              NULL, NULL},
-  {"file:/path",               "file", NULL, NULL, NULL,      -1, "/path",          NULL, NULL},
-  {"file:/path/",              "file", NULL, NULL, NULL,      -1, "/path/",         NULL, NULL},
-  {"file:/path/f.txt",         "file", NULL, NULL, NULL,      -1, "/path/f.txt",    NULL, NULL},
+  {"file:/",                   "file", nullptr, nullptr, nullptr,   -1, "/",              nullptr, nullptr},
+  {"file:/path",               "file", nullptr, nullptr, nullptr,   -1, "/path",          nullptr, nullptr},
+  {"file:/path/",              "file", nullptr, nullptr, nullptr,   -1, "/path/",         nullptr, nullptr},
+  {"file:/path/f.txt",         "file", nullptr, nullptr, nullptr,   -1, "/path/f.txt",    nullptr, nullptr},
   // Two slashes.
-  {"file://",                  "file", NULL, NULL, NULL,      -1, NULL,             NULL, NULL},
-  {"file://server",            "file", NULL, NULL, "server",  -1, NULL,             NULL, NULL},
-  {"file://server/",           "file", NULL, NULL, "server",  -1, "/",              NULL, NULL},
-  {"file://server/f.txt",      "file", NULL, NULL, "server",  -1, "/f.txt",         NULL, NULL},
+  {"file://",                  "file", nullptr, nullptr, nullptr,   -1, nullptr,          nullptr, nullptr},
+  {"file://server",            "file", nullptr, nullptr, "server",  -1, nullptr,          nullptr, nullptr},
+  {"file://server/",           "file", nullptr, nullptr, "server",  -1, "/",              nullptr, nullptr},
+  {"file://server/f.txt",      "file", nullptr, nullptr, "server",  -1, "/f.txt",         nullptr, nullptr},
   // Three slashes.
-  {"file:///",                 "file", NULL, NULL, NULL,      -1, "/",              NULL, NULL},
-  {"file:///path",             "file", NULL, NULL, NULL,      -1, "/path",          NULL, NULL},
-  {"file:///path/",            "file", NULL, NULL, NULL,      -1, "/path/",         NULL, NULL},
-  {"file:///path/f.txt",       "file", NULL, NULL, NULL,      -1, "/path/f.txt",    NULL, NULL},
+  {"file:///",                 "file", nullptr, nullptr, nullptr,   -1, "/",              nullptr, nullptr},
+  {"file:///path",             "file", nullptr, nullptr, nullptr,   -1, "/path",          nullptr, nullptr},
+  {"file:///path/",            "file", nullptr, nullptr, nullptr,   -1, "/path/",         nullptr, nullptr},
+  {"file:///path/f.txt",       "file", nullptr, nullptr, nullptr,   -1, "/path/f.txt",    nullptr, nullptr},
   // More than three slashes.
-  {"file:////",                "file", NULL, NULL, NULL,      -1, "/",              NULL, NULL},
-  {"file:////path",            "file", NULL, NULL, NULL,      -1, "/path",          NULL, NULL},
-  {"file:////path/",           "file", NULL, NULL, NULL,      -1, "/path/",         NULL, NULL},
-  {"file:////path/f.txt",      "file", NULL, NULL, NULL,      -1, "/path/f.txt",    NULL, NULL},
+  {"file:////",                "file", nullptr, nullptr, nullptr,   -1, "/",              nullptr, nullptr},
+  {"file:////path",            "file", nullptr, nullptr, nullptr,   -1, "/path",          nullptr, nullptr},
+  {"file:////path/",           "file", nullptr, nullptr, nullptr,   -1, "/path/",         nullptr, nullptr},
+  {"file:////path/f.txt",      "file", nullptr, nullptr, nullptr,   -1, "/path/f.txt",    nullptr, nullptr},
   // Schemeless URLs
-  {"path/f.txt",               NULL,   NULL, NULL, NULL,       -1, "path/f.txt",    NULL, NULL},
-  {"path:80/f.txt",            "path", NULL, NULL, NULL,       -1, "80/f.txt",      NULL, NULL},
-  {"path/f.txt:80",            "path/f.txt",NULL, NULL, NULL,  -1, "80",            NULL, NULL}, // Wrong.
-  {"/path/f.txt",              NULL,   NULL, NULL, NULL,       -1, "/path/f.txt",   NULL, NULL},
-  {"/path:80/f.txt",           NULL,   NULL, NULL, NULL,       -1, "/path:80/f.txt",NULL, NULL},
-  {"/path/f.txt:80",           NULL,   NULL, NULL, NULL,       -1, "/path/f.txt:80",NULL, NULL},
-  {"//server/f.txt",           NULL,   NULL, NULL, "server",   -1, "/f.txt",        NULL, NULL},
-  {"//server:80/f.txt",        NULL,   NULL, NULL, "server:80",-1, "/f.txt",        NULL, NULL},
-  {"//server/f.txt:80",        NULL,   NULL, NULL, "server",   -1, "/f.txt:80",     NULL, NULL},
-  {"///path/f.txt",            NULL,   NULL, NULL, NULL,       -1, "/path/f.txt",   NULL, NULL},
-  {"///path:80/f.txt",         NULL,   NULL, NULL, NULL,       -1, "/path:80/f.txt",NULL, NULL},
-  {"///path/f.txt:80",         NULL,   NULL, NULL, NULL,       -1, "/path/f.txt:80",NULL, NULL},
-  {"////path/f.txt",           NULL,   NULL, NULL, NULL,       -1, "/path/f.txt",   NULL, NULL},
-  {"////path:80/f.txt",        NULL,   NULL, NULL, NULL,       -1, "/path:80/f.txt",NULL, NULL},
-  {"////path/f.txt:80",        NULL,   NULL, NULL, NULL,       -1, "/path/f.txt:80",NULL, NULL},
+  {"path/f.txt",               nullptr,nullptr, nullptr, nullptr,    -1, "path/f.txt",    nullptr, nullptr},
+  {"path:80/f.txt",            "path", nullptr, nullptr, nullptr,    -1, "80/f.txt",      nullptr, nullptr},
+  {"path/f.txt:80",            "path/f.txt",nullptr, nullptr, nullptr,-1,"80",            nullptr, nullptr}, // Wrong.
+  {"/path/f.txt",              nullptr,nullptr, nullptr, nullptr,    -1, "/path/f.txt",   nullptr, nullptr},
+  {"/path:80/f.txt",           nullptr,nullptr, nullptr, nullptr,    -1, "/path:80/f.txt",nullptr, nullptr},
+  {"/path/f.txt:80",           nullptr,nullptr, nullptr, nullptr,    -1, "/path/f.txt:80",nullptr, nullptr},
+  {"//server/f.txt",           nullptr,nullptr, nullptr, "server",   -1, "/f.txt",        nullptr, nullptr},
+  {"//server:80/f.txt",        nullptr,nullptr, nullptr, "server:80",-1, "/f.txt",        nullptr, nullptr},
+  {"//server/f.txt:80",        nullptr,nullptr, nullptr, "server",   -1, "/f.txt:80",     nullptr, nullptr},
+  {"///path/f.txt",            nullptr,nullptr, nullptr, nullptr,    -1, "/path/f.txt",   nullptr, nullptr},
+  {"///path:80/f.txt",         nullptr,nullptr, nullptr, nullptr,    -1, "/path:80/f.txt",nullptr, nullptr},
+  {"///path/f.txt:80",         nullptr,nullptr, nullptr, nullptr,    -1, "/path/f.txt:80",nullptr, nullptr},
+  {"////path/f.txt",           nullptr,nullptr, nullptr, nullptr,    -1, "/path/f.txt",   nullptr, nullptr},
+  {"////path:80/f.txt",        nullptr,nullptr, nullptr, nullptr,    -1, "/path:80/f.txt",nullptr, nullptr},
+  {"////path/f.txt:80",        nullptr,nullptr, nullptr, nullptr,    -1, "/path/f.txt:80",nullptr, nullptr},
   // Queries and refs are valid for file URLs as well.
-  {"file:///foo.html?#",       "file", NULL, NULL, NULL,       -1, "/foo.html",     "",   ""},
-  {"file:///foo.html?q=y#ref", "file", NULL, NULL, NULL,       -1, "/foo.html",    "q=y", "ref"},
+  {"file:///foo.html?#",       "file", nullptr, nullptr, nullptr,    -1, "/foo.html",     "",   ""},
+  {"file:///foo.html?q=y#ref", "file", nullptr, nullptr, nullptr,    -1, "/foo.html",    "q=y", "ref"},
 #endif  // WIN32
 };
+// clang-format on
 
 TEST(URLParser, ParseFileURL) {
   // Declared outside for loop to try to catch cases in init() where we forget
@@ -506,8 +513,8 @@
       {"http://www.google.com/foo;bar;html", "foo"},
   };
 
-  for (size_t i = 0; i < std::size(extract_cases); i++) {
-    const char* url = extract_cases[i].input;
+  for (const auto& extract_case : extract_cases) {
+    const char* url = extract_case.input;
     int len = static_cast<int>(strlen(url));
 
     Parsed parsed;
@@ -516,7 +523,7 @@
     Component file_name;
     ExtractFileName(url, parsed.path, &file_name);
 
-    EXPECT_TRUE(ComponentMatches(url, extract_cases[i].expected, file_name));
+    EXPECT_TRUE(ComponentMatches(url, extract_case.expected, file_name));
   }
 }
 
@@ -551,39 +558,39 @@
       return true;
     }
   }
-  return expected_key == NULL;  // We didn't find that many parameters.
+  return expected_key == nullptr;  // We didn't find that many parameters.
 }
 
 TEST(URLParser, ExtractQueryKeyValue) {
-  EXPECT_TRUE(NthParameterIs("http://www.google.com", 1, NULL, NULL));
+  EXPECT_TRUE(NthParameterIs("http://www.google.com", 1, nullptr, nullptr));
 
   // Basic case.
   char a[] = "http://www.google.com?arg1=1&arg2=2&bar";
   EXPECT_TRUE(NthParameterIs(a, 1, "arg1", "1"));
   EXPECT_TRUE(NthParameterIs(a, 2, "arg2", "2"));
   EXPECT_TRUE(NthParameterIs(a, 3, "bar", ""));
-  EXPECT_TRUE(NthParameterIs(a, 4, NULL, NULL));
+  EXPECT_TRUE(NthParameterIs(a, 4, nullptr, nullptr));
 
   // Empty param at the end.
   char b[] = "http://www.google.com?foo=bar&";
   EXPECT_TRUE(NthParameterIs(b, 1, "foo", "bar"));
-  EXPECT_TRUE(NthParameterIs(b, 2, NULL, NULL));
+  EXPECT_TRUE(NthParameterIs(b, 2, nullptr, nullptr));
 
   // Empty param at the beginning.
   char c[] = "http://www.google.com?&foo=bar";
   EXPECT_TRUE(NthParameterIs(c, 1, "", ""));
   EXPECT_TRUE(NthParameterIs(c, 2, "foo", "bar"));
-  EXPECT_TRUE(NthParameterIs(c, 3, NULL, NULL));
+  EXPECT_TRUE(NthParameterIs(c, 3, nullptr, nullptr));
 
   // Empty key with value.
   char d[] = "http://www.google.com?=foo";
   EXPECT_TRUE(NthParameterIs(d, 1, "", "foo"));
-  EXPECT_TRUE(NthParameterIs(d, 2, NULL, NULL));
+  EXPECT_TRUE(NthParameterIs(d, 2, nullptr, nullptr));
 
   // Empty value with key.
   char e[] = "http://www.google.com?foo=";
   EXPECT_TRUE(NthParameterIs(e, 1, "foo", ""));
-  EXPECT_TRUE(NthParameterIs(e, 2, NULL, NULL));
+  EXPECT_TRUE(NthParameterIs(e, 2, nullptr, nullptr));
 
   // Empty key and values.
   char f[] = "http://www.google.com?&&==&=";
@@ -591,37 +598,39 @@
   EXPECT_TRUE(NthParameterIs(f, 2, "", ""));
   EXPECT_TRUE(NthParameterIs(f, 3, "", "="));
   EXPECT_TRUE(NthParameterIs(f, 4, "", ""));
-  EXPECT_TRUE(NthParameterIs(f, 5, NULL, NULL));
+  EXPECT_TRUE(NthParameterIs(f, 5, nullptr, nullptr));
 }
 
 // MailtoURL --------------------------------------------------------------------
 
+// clang-format off
 static MailtoURLParseCase mailto_cases[] = {
 //|input                       |scheme   |path               |query
-{"mailto:foo@gmail.com",        "mailto", "foo@gmail.com",    NULL},
-{"  mailto: to  \t",            "mailto", " to",              NULL},
-{"mailto:addr1%2C%20addr2 ",    "mailto", "addr1%2C%20addr2", NULL},
-{"Mailto:addr1, addr2 ",        "Mailto", "addr1, addr2",     NULL},
-{"mailto:addr1:addr2 ",         "mailto", "addr1:addr2",      NULL},
-{"mailto:?to=addr1,addr2",      "mailto", NULL,               "to=addr1,addr2"},
-{"mailto:?to=addr1%2C%20addr2", "mailto", NULL,               "to=addr1%2C%20addr2"},
+{"mailto:foo@gmail.com",        "mailto", "foo@gmail.com",    nullptr},
+{"  mailto: to  \t",            "mailto", " to",              nullptr},
+{"mailto:addr1%2C%20addr2 ",    "mailto", "addr1%2C%20addr2", nullptr},
+{"Mailto:addr1, addr2 ",        "Mailto", "addr1, addr2",     nullptr},
+{"mailto:addr1:addr2 ",         "mailto", "addr1:addr2",      nullptr},
+{"mailto:?to=addr1,addr2",      "mailto", nullptr,            "to=addr1,addr2"},
+{"mailto:?to=addr1%2C%20addr2", "mailto", nullptr,            "to=addr1%2C%20addr2"},
 {"mailto:addr1?to=addr2",       "mailto", "addr1",            "to=addr2"},
-{"mailto:?body=#foobar#",       "mailto", NULL,               "body=#foobar#",},
+{"mailto:?body=#foobar#",       "mailto", nullptr,            "body=#foobar#",},
 {"mailto:#?body=#foobar#",      "mailto", "#",                "body=#foobar#"},
 };
+// clang-format on
 
 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 (size_t i = 0; i < std::size(mailto_cases); ++i) {
-    const char* url = mailto_cases[i].input;
+  for (const auto& mailto_case : mailto_cases) {
+    const char* url = mailto_case.input;
     ParseMailtoURL(url, static_cast<int>(strlen(url)), &parsed);
     int port = ParsePort(url, parsed.port);
 
-    EXPECT_TRUE(ComponentMatches(url, mailto_cases[i].scheme, parsed.scheme));
-    EXPECT_TRUE(ComponentMatches(url, mailto_cases[i].path, parsed.path));
-    EXPECT_TRUE(ComponentMatches(url, mailto_cases[i].query, parsed.query));
+    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);
 
     // The remaining components are never used for mailto URLs.
@@ -634,46 +643,50 @@
 
 // Various incarnations of filesystem URLs.
 static FileSystemURLParseCase filesystem_cases[] = {
-  // Regular URL with all the parts
-{"filesystem:http://user:pass@foo:21/temporary/bar;par?b#c", "http",  "user", "pass", "foo", 21, "/temporary",  "/bar;par",  "b",  "c"},
-{"filesystem:https://foo/persistent/bar;par/",               "https", NULL,   NULL,   "foo", -1, "/persistent", "/bar;par/", NULL, NULL},
-{"filesystem:file:///persistent/bar;par/",                   "file", NULL,    NULL,   NULL,  -1, "/persistent", "/bar;par/", NULL, NULL},
-{"filesystem:file:///persistent/bar;par/?query#ref",                   "file", NULL,    NULL,   NULL,  -1, "/persistent", "/bar;par/", "query", "ref"},
-{"filesystem:file:///persistent",                            "file", NULL,    NULL,   NULL,  -1, "/persistent", "",        NULL, NULL},
+    // Regular URL with all the parts
+    {"filesystem:http://user:pass@foo:21/temporary/bar;par?b#c", "http", "user",
+     "pass", "foo", 21, "/temporary", "/bar;par", "b", "c"},
+    {"filesystem:https://foo/persistent/bar;par/", "https", nullptr, nullptr,
+     "foo", -1, "/persistent", "/bar;par/", nullptr, nullptr},
+    {"filesystem:file:///persistent/bar;par/", "file", nullptr, nullptr,
+     nullptr, -1, "/persistent", "/bar;par/", nullptr, nullptr},
+    {"filesystem:file:///persistent/bar;par/?query#ref", "file", nullptr,
+     nullptr, nullptr, -1, "/persistent", "/bar;par/", "query", "ref"},
+    {"filesystem:file:///persistent", "file", nullptr, nullptr, nullptr, -1,
+     "/persistent", "", nullptr, nullptr},
 };
 
 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 (size_t i = 0; i < std::size(filesystem_cases); i++) {
-    const FileSystemURLParseCase* parsecase = &filesystem_cases[i];
-    const char* url = parsecase->input;
+  for (const auto& filesystem_case : filesystem_cases) {
+    const char* url = filesystem_case.input;
     ParseFileSystemURL(url, static_cast<int>(strlen(url)), &parsed);
 
     EXPECT_TRUE(ComponentMatches(url, "filesystem", parsed.scheme));
-    EXPECT_EQ(!parsecase->inner_scheme, !parsed.inner_parsed());
+    EXPECT_EQ(!filesystem_case.inner_scheme, !parsed.inner_parsed());
     // Only check the inner_parsed if there is one.
     if (parsed.inner_parsed()) {
-      EXPECT_TRUE(ComponentMatches(url, parsecase->inner_scheme,
+      EXPECT_TRUE(ComponentMatches(url, filesystem_case.inner_scheme,
           parsed.inner_parsed()->scheme));
-      EXPECT_TRUE(ComponentMatches(url, parsecase->inner_username,
+      EXPECT_TRUE(ComponentMatches(url, filesystem_case.inner_username,
           parsed.inner_parsed()->username));
-      EXPECT_TRUE(ComponentMatches(url, parsecase->inner_password,
+      EXPECT_TRUE(ComponentMatches(url, filesystem_case.inner_password,
           parsed.inner_parsed()->password));
-      EXPECT_TRUE(ComponentMatches(url, parsecase->inner_host,
+      EXPECT_TRUE(ComponentMatches(url, filesystem_case.inner_host,
           parsed.inner_parsed()->host));
       int port = ParsePort(url, parsed.inner_parsed()->port);
-      EXPECT_EQ(parsecase->inner_port, port);
+      EXPECT_EQ(filesystem_case.inner_port, port);
 
       // The remaining components are never used for filesystem URLs.
       ExpectInvalidComponent(parsed.inner_parsed()->query);
       ExpectInvalidComponent(parsed.inner_parsed()->ref);
     }
 
-    EXPECT_TRUE(ComponentMatches(url, parsecase->path, parsed.path));
-    EXPECT_TRUE(ComponentMatches(url, parsecase->query, parsed.query));
-    EXPECT_TRUE(ComponentMatches(url, parsecase->ref, parsed.ref));
+    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));
 
     // The remaining components are never used for filesystem URLs.
     ExpectInvalidComponent(parsed.username);
diff --git a/url/url_util_unittest.cc b/url/url_util_unittest.cc
index 1435d3b..edf3563 100644
--- a/url/url_util_unittest.cc
+++ b/url/url_util_unittest.cc
@@ -8,10 +8,10 @@
 
 #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"
-#include "absl/types/optional.h"
 #include "url/third_party/mozilla/url_parse.h"
 #include "url/url_canon.h"
 #include "url/url_canon_stdstring.h"
@@ -37,8 +37,8 @@
 
   // 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", NULL));
+  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(found_scheme == Component(0, 4));
@@ -160,18 +160,22 @@
   // Check that the following calls do not cause crash
   Replacements<char> replacements;
   replacements.SetRef("test", Component(0, 4));
-  ReplaceComponents(NULL, 0, parsed, replacements, NULL, &output, &new_parsed);
-  ReplaceComponents("", 0, parsed, replacements, NULL, &output, &new_parsed);
+  ReplaceComponents(nullptr, 0, parsed, replacements, nullptr, &output,
+                    &new_parsed);
+  ReplaceComponents("", 0, parsed, replacements, nullptr, &output, &new_parsed);
   replacements.ClearRef();
   replacements.SetHost("test", Component(0, 4));
-  ReplaceComponents(NULL, 0, parsed, replacements, NULL, &output, &new_parsed);
-  ReplaceComponents("", 0, parsed, replacements, NULL, &output, &new_parsed);
+  ReplaceComponents(nullptr, 0, parsed, replacements, nullptr, &output,
+                    &new_parsed);
+  ReplaceComponents("", 0, parsed, replacements, nullptr, &output, &new_parsed);
 
   replacements.ClearHost();
-  ReplaceComponents(NULL, 0, parsed, replacements, NULL, &output, &new_parsed);
-  ReplaceComponents("", 0, parsed, replacements, NULL, &output, &new_parsed);
-  ReplaceComponents(NULL, 0, parsed, replacements, NULL, &output, &new_parsed);
-  ReplaceComponents("", 0, parsed, replacements, NULL, &output, &new_parsed);
+  ReplaceComponents(nullptr, 0, parsed, replacements, nullptr, &output,
+                    &new_parsed);
+  ReplaceComponents("", 0, parsed, replacements, nullptr, &output, &new_parsed);
+  ReplaceComponents(nullptr, 0, parsed, replacements, nullptr, &output,
+                    &new_parsed);
+  ReplaceComponents("", 0, parsed, replacements, nullptr, &output, &new_parsed);
 }
 
 static std::string CheckReplaceScheme(const char* base_url,
@@ -179,7 +183,7 @@
   // Make sure the input is canonicalized.
   RawCanonOutput<32> original;
   Parsed original_parsed;
-  Canonicalize(base_url, strlen(base_url), true, NULL, &original,
+  Canonicalize(base_url, strlen(base_url), true, nullptr, &original,
                &original_parsed);
 
   Replacements<char> replacements;
@@ -189,7 +193,7 @@
   StdStringCanonOutput output(&output_string);
   Parsed output_parsed;
   ReplaceComponents(original.data(), original.length(), original_parsed,
-                    replacements, NULL, &output, &output_parsed);
+                    replacements, nullptr, &output, &output_parsed);
 
   output.Complete();
   return output_string;
@@ -257,16 +261,17 @@
       {"%ef%bf%bd", "\xef\xbf\xbd"},
   };
 
-  for (size_t i = 0; i < std::size(decode_cases); i++) {
-    const char* input = decode_cases[i].input;
+  for (const auto& decode_case : decode_cases) {
     RawCanonOutputT<char16_t> output;
-    DecodeURLEscapeSequences(input, DecodeURLMode::kUTF8OrIsomorphic, &output);
-    EXPECT_EQ(decode_cases[i].output, gurl_base::UTF16ToUTF8(std::u16string(
-                                          output.data(), output.length())));
+    DecodeURLEscapeSequences(decode_case.input,
+                             DecodeURLMode::kUTF8OrIsomorphic, &output);
+    EXPECT_EQ(decode_case.output, gurl_base::UTF16ToUTF8(std::u16string(
+                                      output.data(), output.length())));
 
     RawCanonOutputT<char16_t> output_utf8;
-    DecodeURLEscapeSequences(input, DecodeURLMode::kUTF8, &output_utf8);
-    EXPECT_EQ(decode_cases[i].output,
+    DecodeURLEscapeSequences(decode_case.input, DecodeURLMode::kUTF8,
+                             &output_utf8);
+    EXPECT_EQ(decode_case.output,
               gurl_base::UTF16ToUTF8(
                   std::u16string(output_utf8.data(), output_utf8.length())));
   }
@@ -296,17 +301,17 @@
        {0xfffd, 0xfffd, 0}},
   };
 
-  for (const auto& test : utf8_decode_cases) {
-    const char* input = test.input;
+  for (const auto& utf8_decode_case : utf8_decode_cases) {
     RawCanonOutputT<char16_t> output_iso;
-    DecodeURLEscapeSequences(input, DecodeURLMode::kUTF8OrIsomorphic,
-                             &output_iso);
-    EXPECT_EQ(std::u16string(test.expected_iso.data()),
+    DecodeURLEscapeSequences(utf8_decode_case.input,
+                             DecodeURLMode::kUTF8OrIsomorphic, &output_iso);
+    EXPECT_EQ(std::u16string(utf8_decode_case.expected_iso.data()),
               std::u16string(output_iso.data(), output_iso.length()));
 
     RawCanonOutputT<char16_t> output_utf8;
-    DecodeURLEscapeSequences(input, DecodeURLMode::kUTF8, &output_utf8);
-    EXPECT_EQ(std::u16string(test.expected_utf8.data()),
+    DecodeURLEscapeSequences(utf8_decode_case.input, DecodeURLMode::kUTF8,
+                             &output_utf8);
+    EXPECT_EQ(std::u16string(utf8_decode_case.expected_utf8.data()),
               std::u16string(output_utf8.data(), output_utf8.length()));
   }
 }
@@ -335,12 +340,11 @@
      "pqrstuvwxyz%7B%7C%7D~%7F"},
   };
 
-  for (size_t i = 0; i < std::size(encode_cases); i++) {
-    const char* input = encode_cases[i].input;
+  for (const auto& encode_case : encode_cases) {
     RawCanonOutputT<char> buffer;
-    EncodeURIComponent(input, &buffer);
+    EncodeURIComponent(encode_case.input, &buffer);
     std::string output(buffer.data(), buffer.length());
-    EXPECT_EQ(encode_cases[i].output, output);
+    EXPECT_EQ(encode_case.output, output);
   }
 }
 
@@ -413,23 +417,25 @@
       // adding the requested dot doesn't seem wrong either.
       {"aaa://a\\", "aaa:.", true, "aaa://a\\."}};
 
-  for (size_t i = 0; i < std::size(resolve_non_standard_cases); i++) {
-    const ResolveRelativeCase& test_data = resolve_non_standard_cases[i];
+  for (const auto& test : resolve_non_standard_cases) {
+    SCOPED_TRACE(testing::Message()
+                 << "base: " << test.base << ", rel: " << test.rel);
+
     Parsed base_parsed;
-    ParsePathURL(test_data.base, strlen(test_data.base), false, &base_parsed);
+    ParsePathURL(test.base, strlen(test.base), false, &base_parsed);
 
     std::string resolved;
     StdStringCanonOutput output(&resolved);
     Parsed resolved_parsed;
-    bool valid = ResolveRelative(test_data.base, strlen(test_data.base),
-                                 base_parsed, test_data.rel,
-                                 strlen(test_data.rel), NULL, &output,
-                                 &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_data.is_valid, valid) << i;
-    if (test_data.is_valid && valid)
-      EXPECT_EQ(test_data.out, resolved) << i;
+    EXPECT_EQ(test.is_valid, valid);
+    if (test.is_valid && valid) {
+      EXPECT_EQ(test.out, resolved);
+    }
   }
 }
 
@@ -446,10 +452,8 @@
   StdStringCanonOutput output(&resolved);
   Parsed resolved_parsed;
 
-  bool valid = ResolveRelative(base, strlen(base),
-                               base_parsed, rel,
-                               strlen(rel), NULL, &output,
-                               &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());
 }
@@ -492,7 +496,7 @@
     Parsed resolved_parsed;
     bool valid =
         ResolveRelative(test.base, strlen(test.base), base_parsed, test.rel,
-                        strlen(test.rel), NULL, &output, &resolved_parsed);
+                        strlen(test.rel), nullptr, &output, &resolved_parsed);
     ASSERT_TRUE(valid);
     output.Complete();
 
@@ -585,8 +589,8 @@
 }
 
 namespace {
-absl::optional<std::string> CanonicalizeSpec(std::string_view spec,
-                                             bool trim_path_end) {
+std::optional<std::string> CanonicalizeSpec(std::string_view spec,
+                                            bool trim_path_end) {
   std::string canonicalized;
   StdStringCanonOutput output(&canonicalized);
   Parsed parsed;
@@ -604,10 +608,10 @@
 TEST_F(URLUtilTest, TestCanonicalizeWindowsPathWithLeadingNUL) {
   auto PrefixWithNUL = [](std::string&& s) -> std::string { return '\0' + s; };
   EXPECT_EQ(CanonicalizeSpec(PrefixWithNUL("w:"), /*trim_path_end=*/false),
-            absl::make_optional("file:///W:"));
+            std::make_optional("file:///W:"));
   EXPECT_EQ(CanonicalizeSpec(PrefixWithNUL("\\\\server\\share"),
                              /*trim_path_end=*/false),
-            absl::make_optional("file://server/share"));
+            std::make_optional("file://server/share"));
 }
 #endif
 
@@ -624,7 +628,7 @@
     for (bool trim_path_end : {false, true}) {
       SCOPED_TRACE(testing::Message() << "trim_path_end: " << trim_path_end);
 
-      absl::optional<std::string> canonicalized =
+      std::optional<std::string> canonicalized =
           CanonicalizeSpec(spec, trim_path_end);
       ASSERT_TRUE(canonicalized);
       EXPECT_EQ(canonicalized, CanonicalizeSpec(*canonicalized, trim_path_end));