Garbage collect CXFA_LocaleMgr.

Although not strictly required, this replaces a raw pointer to
a node with a traceable member.

In turn, pass LocaleMgrIface to the string formatter only on calls
that are affected by it. This prevents a lower layer that knows
nothing about garbage collection holding an unowned pointer to
a GC'd object.

Bug: pdfium:1563
Change-Id: Id6a372136f171a307b764a95d478c108cd3be5e4
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/73990
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
diff --git a/testing/fuzzers/BUILD.gn b/testing/fuzzers/BUILD.gn
index 81f5100..421fa52 100644
--- a/testing/fuzzers/BUILD.gn
+++ b/testing/fuzzers/BUILD.gn
@@ -256,10 +256,13 @@
     pdfium_fuzzer("pdf_cfgas_stringformatter_fuzzer") {
       sources = [ "pdf_cfgas_stringformatter_fuzzer.cc" ]
       deps = [
+        ":fuzzer_utils",
         "../../core/fxcrt",
+        "../../fxjs:gc",
         "../../xfa/fgas",
         "../../xfa/fxfa/parser",
       ]
+      public_fuzzer = true
     }
 
     pdfium_fuzzer("pdf_cfx_barcode_fuzzer") {
diff --git a/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc b/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc
index 663958b..71b4793 100644
--- a/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc
+++ b/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc
@@ -6,10 +6,15 @@
 
 #include <stdint.h>
 
-#include <memory>
+#include <vector>
 
 #include "core/fxcrt/fx_string.h"
+#include "public/fpdfview.h"
+#include "testing/fuzzers/pdfium_fuzzer_util.h"
+#include "testing/fuzzers/xfa_process_state.h"
 #include "third_party/base/stl_util.h"
+#include "v8/include/cppgc/heap.h"
+#include "v8/include/cppgc/persistent.h"
 #include "xfa/fxfa/parser/cxfa_localemgr.h"
 
 namespace {
@@ -27,11 +32,15 @@
   if (size < 5 || size > 128)  // Big strings are unlikely to help.
     return 0;
 
+  auto* state = static_cast<XFAProcessState*>(FPDF_GetFuzzerPerProcessState());
+  cppgc::Heap* heap = state->GetHeap();
+
   // Static for speed.
-  static std::vector<std::unique_ptr<CXFA_LocaleMgr>> mgrs;
+  static std::vector<cppgc::Persistent<CXFA_LocaleMgr>> mgrs;
   if (mgrs.empty()) {
     for (const auto* locale : kLocales)
-      mgrs.push_back(std::make_unique<CXFA_LocaleMgr>(nullptr, locale));
+      mgrs.push_back(cppgc::MakeGarbageCollected<CXFA_LocaleMgr>(
+          heap->GetAllocationHandle(), heap, nullptr, locale));
   }
 
   uint8_t test_selector = data[0] % 10;
@@ -47,8 +56,7 @@
   WideString value =
       WideString::FromLatin1(ByteStringView(data + pattern_len, value_len));
 
-  auto fmt = std::make_unique<CFGAS_StringFormatter>(
-      mgrs[locale_selector].get(), pattern);
+  auto fmt = std::make_unique<CFGAS_StringFormatter>(pattern);
 
   WideString result;
   CFX_DateTime dt;
@@ -57,10 +65,11 @@
       fmt->FormatText(value, &result);
       break;
     case 1:
-      fmt->FormatNum(value, &result);
+      fmt->FormatNum(mgrs[locale_selector], value, &result);
       break;
     case 2:
-      fmt->FormatDateTime(value, kTypes[type_selector], &result);
+      fmt->FormatDateTime(mgrs[locale_selector], value, kTypes[type_selector],
+                          &result);
       break;
     case 3:
       fmt->FormatNull(&result);
@@ -72,10 +81,11 @@
       fmt->ParseText(value, &result);
       break;
     case 6:
-      fmt->ParseNum(value, &result);
+      fmt->ParseNum(mgrs[locale_selector], value, &result);
       break;
     case 7:
-      fmt->ParseDateTime(value, kTypes[type_selector], &dt);
+      fmt->ParseDateTime(mgrs[locale_selector], value, kTypes[type_selector],
+                         &dt);
       break;
     case 8:
       fmt->ParseNull(value);
@@ -84,5 +94,7 @@
       fmt->ParseZero(value);
       break;
   }
+
+  state->MaybeForceGCAndPump();
   return 0;
 }
diff --git a/xfa/fgas/BUILD.gn b/xfa/fgas/BUILD.gn
index fe2057d..f5aee6f 100644
--- a/xfa/fgas/BUILD.gn
+++ b/xfa/fgas/BUILD.gn
@@ -50,6 +50,7 @@
   deps = [
     ":fgas",
     "../../core/fpdfapi/page",
+    "../../fxjs:gc",
     "../fxfa/parser",
   ]
   pdfium_root_dir = "../../"
diff --git a/xfa/fgas/crt/cfgas_stringformatter.cpp b/xfa/fgas/crt/cfgas_stringformatter.cpp
index 69fb1a8..2cac0de 100644
--- a/xfa/fgas/crt/cfgas_stringformatter.cpp
+++ b/xfa/fgas/crt/cfgas_stringformatter.cpp
@@ -15,6 +15,7 @@
 #include "core/fxcrt/fx_safe_types.h"
 #include "third_party/base/stl_util.h"
 #include "xfa/fgas/crt/cfgas_decimal.h"
+#include "xfa/fgas/crt/locale_mgr_iface.h"
 
 // NOTE: Code uses the convention for backwards-looping with unsigned types
 // that exploits the well-defined behaviour for unsigned underflow (and hence
@@ -862,11 +863,8 @@
   return true;
 }
 
-CFGAS_StringFormatter::CFGAS_StringFormatter(LocaleMgrIface* pLocaleMgr,
-                                             const WideString& wsPattern)
-    : m_pLocaleMgr(pLocaleMgr),
-      m_wsPattern(wsPattern),
-      m_spPattern(m_wsPattern.span()) {}
+CFGAS_StringFormatter::CFGAS_StringFormatter(const WideString& wsPattern)
+    : m_wsPattern(wsPattern), m_spPattern(m_wsPattern.span()) {}
 
 CFGAS_StringFormatter::~CFGAS_StringFormatter() = default;
 
@@ -988,6 +986,7 @@
 }
 
 LocaleIface* CFGAS_StringFormatter::GetNumericFormat(
+    LocaleMgrIface* pLocaleMgr,
     size_t* iDotIndex,
     uint32_t* dwStyle,
     WideString* wsPurgePattern) const {
@@ -1027,7 +1026,7 @@
           while (ccf < m_spPattern.size() && m_spPattern[ccf] != ')')
             wsLCID += m_spPattern[ccf++];
 
-          pLocale = m_pLocaleMgr->GetLocaleByName(wsLCID);
+          pLocale = pLocaleMgr->GetLocaleByName(wsLCID);
         } else if (m_spPattern[ccf] == '.') {
           WideString wsSubCategory;
           ccf++;
@@ -1046,7 +1045,7 @@
             }
           }
           if (!pLocale)
-            pLocale = m_pLocaleMgr->GetDefLocale();
+            pLocale = pLocaleMgr->GetDefLocale();
 
           ASSERT(pLocale);
 
@@ -1086,7 +1085,7 @@
   if (!bFindDot)
     *iDotIndex = wsPurgePattern->GetLength();
   if (!pLocale)
-    pLocale = m_pLocaleMgr->GetDefLocale();
+    pLocale = pLocaleMgr->GetDefLocale();
   return pLocale;
 }
 
@@ -1161,7 +1160,8 @@
   return iPattern == spTextFormat.size() && iText == spSrcText.size();
 }
 
-bool CFGAS_StringFormatter::ParseNum(const WideString& wsSrcNum,
+bool CFGAS_StringFormatter::ParseNum(LocaleMgrIface* pLocaleMgr,
+                                     const WideString& wsSrcNum,
                                      WideString* wsValue) const {
   wsValue->clear();
   if (wsSrcNum.IsEmpty() || m_spPattern.empty())
@@ -1171,7 +1171,7 @@
   uint32_t dwFormatStyle = 0;
   WideString wsNumFormat;
   LocaleIface* pLocale =
-      GetNumericFormat(&dot_index_f, &dwFormatStyle, &wsNumFormat);
+      GetNumericFormat(pLocaleMgr, &dot_index_f, &dwFormatStyle, &wsNumFormat);
   if (!pLocale || wsNumFormat.IsEmpty())
     return false;
 
@@ -1582,6 +1582,7 @@
 }
 
 CFGAS_StringFormatter::DateTimeType CFGAS_StringFormatter::GetDateTimeFormat(
+    LocaleMgrIface* pLocaleMgr,
     LocaleIface** pLocale,
     WideString* wsDatePattern,
     WideString* wsTimePattern) const {
@@ -1608,8 +1609,7 @@
           *wsTimePattern = m_wsPattern.Last(m_wsPattern.GetLength() - ccf);
           wsTimePattern->SetAt(0, ' ');
           if (!*pLocale)
-            *pLocale = m_pLocaleMgr->GetDefLocale();
-
+            *pLocale = pLocaleMgr->GetDefLocale();
           return DateTimeType::kDateTime;
         }
         wsCategory += m_spPattern[ccf];
@@ -1638,7 +1638,7 @@
           while (ccf < m_spPattern.size() && m_spPattern[ccf] != ')')
             wsLCID += m_spPattern[ccf++];
 
-          *pLocale = m_pLocaleMgr->GetLocaleByName(wsLCID);
+          *pLocale = pLocaleMgr->GetLocaleByName(wsLCID);
         } else if (m_spPattern[ccf] == '.') {
           WideString wsSubCategory;
           ccf++;
@@ -1657,7 +1657,7 @@
             }
           }
           if (!*pLocale)
-            *pLocale = m_pLocaleMgr->GetDefLocale();
+            *pLocale = pLocaleMgr->GetDefLocale();
           ASSERT(*pLocale);
 
           switch (eCategory) {
@@ -1705,7 +1705,7 @@
       *wsTimePattern += wsTempPattern;
   }
   if (!*pLocale)
-    *pLocale = m_pLocaleMgr->GetDefLocale();
+    *pLocale = pLocaleMgr->GetDefLocale();
   if (eDateTimeType == DateTimeType::kUnknown) {
     wsTimePattern->clear();
     *wsDatePattern = m_wsPattern;
@@ -1713,7 +1713,8 @@
   return eDateTimeType;
 }
 
-bool CFGAS_StringFormatter::ParseDateTime(const WideString& wsSrcDateTime,
+bool CFGAS_StringFormatter::ParseDateTime(LocaleMgrIface* pLocaleMgr,
+                                          const WideString& wsSrcDateTime,
                                           DateTimeType eDateTimeType,
                                           CFX_DateTime* dtValue) const {
   dtValue->Reset();
@@ -1724,7 +1725,7 @@
   WideString wsDatePattern;
   WideString wsTimePattern;
   DateTimeType eCategory =
-      GetDateTimeFormat(&pLocale, &wsDatePattern, &wsTimePattern);
+      GetDateTimeFormat(pLocaleMgr, &pLocale, &wsDatePattern, &wsTimePattern);
   if (!pLocale)
     return false;
 
@@ -1869,7 +1870,8 @@
   return iText == spSrcText.size();
 }
 
-bool CFGAS_StringFormatter::FormatNum(const WideString& wsInputNum,
+bool CFGAS_StringFormatter::FormatNum(LocaleMgrIface* pLocaleMgr,
+                                      const WideString& wsInputNum,
                                       WideString* wsOutput) const {
   if (wsInputNum.IsEmpty() || m_spPattern.empty())
     return false;
@@ -1878,7 +1880,7 @@
   uint32_t dwNumStyle = 0;
   WideString wsNumFormat;
   LocaleIface* pLocale =
-      GetNumericFormat(&dot_index_f, &dwNumStyle, &wsNumFormat);
+      GetNumericFormat(pLocaleMgr, &dot_index_f, &dwNumStyle, &wsNumFormat);
   if (!pLocale || wsNumFormat.IsEmpty())
     return false;
 
@@ -2214,7 +2216,8 @@
   return true;
 }
 
-bool CFGAS_StringFormatter::FormatDateTime(const WideString& wsSrcDateTime,
+bool CFGAS_StringFormatter::FormatDateTime(LocaleMgrIface* pLocaleMgr,
+                                           const WideString& wsSrcDateTime,
                                            DateTimeType eDateTimeType,
                                            WideString* wsOutput) const {
   if (wsSrcDateTime.IsEmpty() || m_spPattern.empty())
@@ -2224,7 +2227,7 @@
   WideString wsTimePattern;
   LocaleIface* pLocale = nullptr;
   DateTimeType eCategory =
-      GetDateTimeFormat(&pLocale, &wsDatePattern, &wsTimePattern);
+      GetDateTimeFormat(pLocaleMgr, &pLocale, &wsDatePattern, &wsTimePattern);
   if (!pLocale)
     return false;
 
diff --git a/xfa/fgas/crt/cfgas_stringformatter.h b/xfa/fgas/crt/cfgas_stringformatter.h
index 98dfc6f..4e67e52 100644
--- a/xfa/fgas/crt/cfgas_stringformatter.h
+++ b/xfa/fgas/crt/cfgas_stringformatter.h
@@ -9,10 +9,11 @@
 
 #include <vector>
 
-#include "core/fxcrt/unowned_ptr.h"
+#include "core/fxcrt/fx_string.h"
 #include "third_party/base/span.h"
 #include "xfa/fgas/crt/locale_iface.h"
-#include "xfa/fgas/crt/locale_mgr_iface.h"
+
+class LocaleMgrIface;
 
 bool FX_DateFromCanonical(pdfium::span<const wchar_t> wsTime,
                           CFX_DateTime* datetime);
@@ -41,8 +42,7 @@
     kTimeDate,
   };
 
-  CFGAS_StringFormatter(LocaleMgrIface* pLocaleMgr,
-                        const WideString& wsPattern);
+  explicit CFGAS_StringFormatter(const WideString& wsPattern);
   ~CFGAS_StringFormatter();
 
   static std::vector<WideString> SplitOnBars(const WideString& wsFormatString);
@@ -50,16 +50,22 @@
   Category GetCategory() const;
 
   bool ParseText(const WideString& wsSrcText, WideString* wsValue) const;
-  bool ParseNum(const WideString& wsSrcNum, WideString* wsValue) const;
-  bool ParseDateTime(const WideString& wsSrcDateTime,
+  bool ParseNum(LocaleMgrIface* pLocaleMgr,
+                const WideString& wsSrcNum,
+                WideString* wsValue) const;
+  bool ParseDateTime(LocaleMgrIface* pLocaleMgr,
+                     const WideString& wsSrcDateTime,
                      DateTimeType eDateTimeType,
                      CFX_DateTime* dtValue) const;
   bool ParseZero(const WideString& wsSrcText) const;
   bool ParseNull(const WideString& wsSrcText) const;
 
   bool FormatText(const WideString& wsSrcText, WideString* wsOutput) const;
-  bool FormatNum(const WideString& wsSrcNum, WideString* wsOutput) const;
-  bool FormatDateTime(const WideString& wsSrcDateTime,
+  bool FormatNum(LocaleMgrIface* pLocaleMgr,
+                 const WideString& wsSrcNum,
+                 WideString* wsOutput) const;
+  bool FormatDateTime(LocaleMgrIface* pLocaleMgr,
+                      const WideString& wsSrcDateTime,
                       DateTimeType eDateTimeType,
                       WideString* wsOutput) const;
   bool FormatZero(WideString* wsOutput) const;
@@ -67,14 +73,15 @@
 
  private:
   WideString GetTextFormat(WideStringView wsCategory) const;
-  LocaleIface* GetNumericFormat(size_t* iDotIndex,
+  LocaleIface* GetNumericFormat(LocaleMgrIface* pLocaleMgr,
+                                size_t* iDotIndex,
                                 uint32_t* dwStyle,
                                 WideString* wsPurgePattern) const;
-  DateTimeType GetDateTimeFormat(LocaleIface** pLocale,
+  DateTimeType GetDateTimeFormat(LocaleMgrIface* pLocaleMgr,
+                                 LocaleIface** pLocale,
                                  WideString* wsDatePattern,
                                  WideString* wsTimePattern) const;
 
-  UnownedPtr<LocaleMgrIface> const m_pLocaleMgr;
   const WideString m_wsPattern;                   // keep pattern string alive.
   const pdfium::span<const wchar_t> m_spPattern;  // span into |m_wsPattern|.
 };
diff --git a/xfa/fgas/crt/cfgas_stringformatter_unittest.cpp b/xfa/fgas/crt/cfgas_stringformatter_unittest.cpp
index d582bb3..4b45d2a 100644
--- a/xfa/fgas/crt/cfgas_stringformatter_unittest.cpp
+++ b/xfa/fgas/crt/cfgas_stringformatter_unittest.cpp
@@ -13,25 +13,15 @@
 #include "build/build_config.h"
 #include "core/fpdfapi/page/cpdf_pagemodule.h"
 #include "testing/fx_string_testhelpers.h"
+#include "testing/fxgc_unittest.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/base/stl_util.h"
+#include "v8/include/cppgc/persistent.h"
 #include "xfa/fxfa/parser/cxfa_localemgr.h"
 
-class CFGAS_StringFormatterTest : public testing::Test {
- public:
-  CFGAS_StringFormatterTest() {
-    SetTZ("UTC");
-    CPDF_PageModule::Create();
-  }
+namespace {
 
-  ~CFGAS_StringFormatterTest() override { CPDF_PageModule::Destroy(); }
-
-  void TearDown() override {
-    fmt_.reset();
-    mgr_.reset();
-  }
-
-  void SetTZ(const char* tz) {
+void SetTZ(const char* tz) {
 #if defined(OS_WIN)
     _putenv_s("TZ", tz);
     _tzset();
@@ -39,21 +29,26 @@
     setenv("TZ", tz, 1);
     tzset();
 #endif
+}
+
+}  // namespace
+
+class CFGAS_StringFormatterTest : public FXGCUnitTest {
+ public:
+  CFGAS_StringFormatterTest() {
+    SetTZ("UTC");
+    CPDF_PageModule::Create();
   }
 
-  // Note, this re-creates the fmt on each call. If you need to multiple
-  // times store it locally.
-  CFGAS_StringFormatter* fmt(const WideString& locale,
-                             const WideString& pattern) {
-    fmt_.reset();  // Can't outlive |mgr_|.
-    mgr_ = std::make_unique<CXFA_LocaleMgr>(nullptr, locale);
-    fmt_ = std::make_unique<CFGAS_StringFormatter>(mgr_.get(), pattern);
-    return fmt_.get();
+  ~CFGAS_StringFormatterTest() override {
+    CPDF_PageModule::Destroy();
+    SetTZ("UTC");
   }
 
- protected:
-  std::unique_ptr<CXFA_LocaleMgr> mgr_;
-  std::unique_ptr<CFGAS_StringFormatter> fmt_;
+  CXFA_LocaleMgr* Mgr(const WideString& locale) {
+    return cppgc::MakeGarbageCollected<CXFA_LocaleMgr>(
+        heap()->GetAllocationHandle(), heap(), nullptr, locale);
+  }
 };
 
 // TODO(dsinclair): Looks like the formatter/parser does not handle the various
@@ -115,10 +110,10 @@
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     WideString result;
-    EXPECT_TRUE(fmt(tests[i].locale, tests[i].pattern)
-                    ->FormatDateTime(tests[i].input,
-                                     CFGAS_StringFormatter::DateTimeType::kDate,
-                                     &result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.FormatDateTime(Mgr(tests[i].locale), tests[i].input,
+                                   CFGAS_StringFormatter::DateTimeType::kDate,
+                                   &result));
     EXPECT_STREQ(tests[i].output, result.c_str()) << " TEST: " << i;
   }
 }
@@ -165,10 +160,10 @@
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     WideString result;
-    EXPECT_TRUE(fmt(tests[i].locale, tests[i].pattern)
-                    ->FormatDateTime(tests[i].input,
-                                     CFGAS_StringFormatter::DateTimeType::kTime,
-                                     &result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.FormatDateTime(Mgr(tests[i].locale), tests[i].input,
+                                   CFGAS_StringFormatter::DateTimeType::kTime,
+                                   &result));
     EXPECT_STREQ(tests[i].output, result.c_str()) << " TEST: " << i;
   }
 
@@ -198,11 +193,10 @@
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     WideString result;
-    EXPECT_TRUE(
-        fmt(tests[i].locale, tests[i].pattern)
-            ->FormatDateTime(tests[i].input,
-                             CFGAS_StringFormatter::DateTimeType::kDateTime,
-                             &result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.FormatDateTime(
+        Mgr(tests[i].locale), tests[i].input,
+        CFGAS_StringFormatter::DateTimeType::kDateTime, &result));
     EXPECT_STREQ(tests[i].output, result.c_str()) << " TEST: " << i;
   }
 }
@@ -228,11 +222,10 @@
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     WideString result;
-    EXPECT_TRUE(
-        fmt(tests[i].locale, tests[i].pattern)
-            ->FormatDateTime(tests[i].input,
-                             CFGAS_StringFormatter::DateTimeType::kTimeDate,
-                             &result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.FormatDateTime(
+        Mgr(tests[i].locale), tests[i].input,
+        CFGAS_StringFormatter::DateTimeType::kTimeDate, &result));
     EXPECT_STREQ(tests[i].output, result.c_str()) << " TEST: " << i;
   }
 }
@@ -291,10 +284,10 @@
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     CFX_DateTime result;
-    EXPECT_TRUE(fmt(tests[i].locale, tests[i].pattern)
-                    ->ParseDateTime(tests[i].input,
-                                    CFGAS_StringFormatter::DateTimeType::kDate,
-                                    &result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.ParseDateTime(Mgr(tests[i].locale), tests[i].input,
+                                  CFGAS_StringFormatter::DateTimeType::kDate,
+                                  &result));
     EXPECT_EQ(tests[i].output, result) << " TEST: " << i;
   }
 }
@@ -481,7 +474,8 @@
 
   for (const auto& test : tests) {
     WideString result;
-    EXPECT_TRUE(fmt(test.locale, test.pattern)->ParseNum(test.input, &result))
+    CFGAS_StringFormatter fmt(test.pattern);
+    EXPECT_TRUE(fmt.ParseNum(Mgr(test.locale), test.input, &result))
         << " TEST: " << test.input << ", " << test.pattern;
     EXPECT_STREQ(test.output, result.c_str())
         << " TEST: " << test.input << ", " << test.pattern;
@@ -489,7 +483,8 @@
 
   for (const auto& test : failures) {
     WideString result;
-    EXPECT_FALSE(fmt(test.locale, test.pattern)->ParseNum(test.input, &result))
+    CFGAS_StringFormatter fmt(test.pattern);
+    EXPECT_FALSE(fmt.ParseNum(Mgr(test.locale), test.input, &result))
         << " TEST: " << test.input << ", " << test.pattern;
   }
 }
@@ -610,7 +605,8 @@
 
   for (const auto& test : tests) {
     WideString result;
-    EXPECT_TRUE(fmt(test.locale, test.pattern)->FormatNum(test.input, &result))
+    CFGAS_StringFormatter fmt(test.pattern);
+    EXPECT_TRUE(fmt.FormatNum(Mgr(test.locale), test.input, &result))
         << " TEST: " << test.input << ", " << test.pattern;
     EXPECT_STREQ(test.output, result.c_str())
         << " TEST: " << test.input << ", " << test.pattern;
@@ -618,14 +614,14 @@
 
   for (const auto& test : failures) {
     WideString result;
-    EXPECT_FALSE(fmt(test.locale, test.pattern)->FormatNum(test.input, &result))
+    CFGAS_StringFormatter fmt(test.pattern);
+    EXPECT_FALSE(fmt.FormatNum(Mgr(test.locale), test.input, &result))
         << " TEST: " << test.input << ", " << test.pattern;
   }
 }
 
 TEST_F(CFGAS_StringFormatterTest, TextParse) {
   struct {
-    const wchar_t* locale;
     const wchar_t* input;
     const wchar_t* pattern;
     const wchar_t* output;
@@ -634,16 +630,16 @@
                //  * - zero or more whitespace
                //  + - one or more whitespace
                // {L"en", L"555-1212", L"text(th_TH){999*9999}", L"5551212"},
-               {L"en", L"ABC-1234-5", L"AAA-9999-X", L"ABC12345"},
-               {L"en", L"ABC-1234-D", L"AAA-9999-X", L"ABC1234D"},
-               {L"en", L"A1C-1234-D", L"OOO-9999-X", L"A1C1234D"},
-               {L"en", L"A1C-1234-D", L"000-9999-X", L"A1C1234D"},
-               {L"en", L"A1C-1234-D text", L"000-9999-X 'text'", L"A1C1234D"}};
+               {L"ABC-1234-5", L"AAA-9999-X", L"ABC12345"},
+               {L"ABC-1234-D", L"AAA-9999-X", L"ABC1234D"},
+               {L"A1C-1234-D", L"OOO-9999-X", L"A1C1234D"},
+               {L"A1C-1234-D", L"000-9999-X", L"A1C1234D"},
+               {L"A1C-1234-D text", L"000-9999-X 'text'", L"A1C1234D"}};
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     WideString result;
-    EXPECT_TRUE(fmt(tests[i].locale, tests[i].pattern)
-                    ->ParseText(tests[i].input, &result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.ParseText(tests[i].input, &result));
     EXPECT_STREQ(tests[i].output, result.c_str()) << " TEST: " << i;
   }
 }
@@ -651,7 +647,8 @@
 TEST_F(CFGAS_StringFormatterTest, InvalidTextParse) {
   // Input does not match mask.
   WideString result;
-  EXPECT_FALSE(fmt(L"en", L"AAA-9999-X")->ParseText(L"123-4567-8", &result));
+  CFGAS_StringFormatter fmt(L"AAA-9999-X");
+  EXPECT_FALSE(fmt.ParseText(L"123-4567-8", &result));
 }
 
 TEST_F(CFGAS_StringFormatterTest, TextFormat) {
@@ -672,102 +669,96 @@
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     WideString result;
-    EXPECT_TRUE(fmt(tests[i].locale, tests[i].pattern)
-                    ->FormatText(tests[i].input, &result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.FormatText(tests[i].input, &result));
     EXPECT_STREQ(tests[i].output, result.c_str()) << " TEST: " << i;
   }
 }
 
 TEST_F(CFGAS_StringFormatterTest, NullParse) {
   struct {
-    const wchar_t* locale;
     const wchar_t* input;
     const wchar_t* pattern;
   } tests[] = {
-      {L"en", L"", L"null{}"},
-      {L"en", L"No data", L"null{'No data'}"},
+      {L"", L"null{}"},
+      {L"No data", L"null{'No data'}"},
   };
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
-    EXPECT_TRUE(
-        fmt(tests[i].locale, tests[i].pattern)->ParseNull(tests[i].input))
-        << " TEST: " << i;
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.ParseNull(tests[i].input)) << " TEST: " << i;
   }
 }
 
 TEST_F(CFGAS_StringFormatterTest, NullFormat) {
   struct {
-    const wchar_t* locale;
     const wchar_t* pattern;
     const wchar_t* output;
-  } tests[] = {{L"en", L"null{'n/a'}", L"n/a"}, {L"en", L"null{}", L""}};
+  } tests[] = {{L"null{'n/a'}", L"n/a"}, {L"null{}", L""}};
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     WideString result;
-    EXPECT_TRUE(fmt(tests[i].locale, tests[i].pattern)->FormatNull(&result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.FormatNull(&result));
     EXPECT_STREQ(tests[i].output, result.c_str()) << " TEST: " << i;
   }
 }
 
 TEST_F(CFGAS_StringFormatterTest, ZeroParse) {
   struct {
-    const wchar_t* locale;
     const wchar_t* input;
     const wchar_t* pattern;
-  } tests[] = {{L"en", L"", L"zero{}"},
-               {L"en", L"9", L"zero{9}"},
-               {L"en", L"a", L"zero{'a'}"}};
+  } tests[] = {{L"", L"zero{}"}, {L"9", L"zero{9}"}, {L"a", L"zero{'a'}"}};
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
-    EXPECT_TRUE(
-        fmt(tests[i].locale, tests[i].pattern)->ParseZero(tests[i].input))
-        << " TEST: " << i;
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.ParseZero(tests[i].input)) << " TEST: " << i;
   }
 }
 
 TEST_F(CFGAS_StringFormatterTest, ZeroFormat) {
   struct {
-    const wchar_t* locale;
     const wchar_t* input;
     const wchar_t* pattern;
     const wchar_t* output;
   } tests[] = {// TODO(dsinclair): The zero format can take a number specifier
                // which we don't take into account.
-               // {L"en", L"", L"zero {9}", L""},
-               // {L"en", L"0", L"zero {9}", L"0"},
-               // {L"en", L"0.0", L"zero{9}", L"0"},
-               {L"en", L"0", L"zero{}", L""}};
+               // {L"", L"zero {9}", L""},
+               // {L"0", L"zero {9}", L"0"},
+               // {L"0.0", L"zero{9}", L"0"},
+               {L"0", L"zero{}", L""}};
 
   for (size_t i = 0; i < pdfium::size(tests); ++i) {
     WideString result;
-    EXPECT_TRUE(fmt(tests[i].locale, tests[i].pattern)->FormatZero(&result));
+    CFGAS_StringFormatter fmt(tests[i].pattern);
+    EXPECT_TRUE(fmt.FormatZero(&result));
     EXPECT_STREQ(tests[i].output, result.c_str()) << " TEST: " << i;
   }
 }
 
 TEST_F(CFGAS_StringFormatterTest, GetCategory) {
   EXPECT_EQ(CFGAS_StringFormatter::Category::kUnknown,
-            fmt(L"en", L"'just text'")->GetCategory());
+            CFGAS_StringFormatter(L"'just text'").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kNull,
-            fmt(L"en", L"null{}")->GetCategory());
+            CFGAS_StringFormatter(L"null{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kZero,
-            fmt(L"en", L"zero{}")->GetCategory());
+            CFGAS_StringFormatter(L"zero{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kNum,
-            fmt(L"en", L"num{}")->GetCategory());
+            CFGAS_StringFormatter(L"num{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kText,
-            fmt(L"en", L"text{}")->GetCategory());
+            CFGAS_StringFormatter(L"text{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kDateTime,
-            fmt(L"en", L"datetime{}")->GetCategory());
+            CFGAS_StringFormatter(L"datetime{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kTime,
-            fmt(L"en", L"time{}")->GetCategory());
+            CFGAS_StringFormatter(L"time{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kDate,
-            fmt(L"en", L"date{}")->GetCategory());
+            CFGAS_StringFormatter(L"date{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kDateTime,
-            fmt(L"en", L"time{} date{}")->GetCategory());
+            CFGAS_StringFormatter(L"time{} date{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kDateTime,
-            fmt(L"en", L"date{} time{}")->GetCategory());
+            CFGAS_StringFormatter(L"date{} time{}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kNum,
-            fmt(L"en", L"num(en_GB){}")->GetCategory());
+            CFGAS_StringFormatter(L"num(en_GB){}").GetCategory());
   EXPECT_EQ(CFGAS_StringFormatter::Category::kDate,
-            fmt(L"en", L"date.long{}")->GetCategory());
+            CFGAS_StringFormatter(L"date.long{}").GetCategory());
 }
diff --git a/xfa/fxfa/parser/cxfa_document.cpp b/xfa/fxfa/parser/cxfa_document.cpp
index a01d8b0..2b08e2a 100644
--- a/xfa/fxfa/parser/cxfa_document.cpp
+++ b/xfa/fxfa/parser/cxfa_document.cpp
@@ -1285,6 +1285,7 @@
   visitor->Trace(notify_);
   visitor->Trace(node_owner_);
   visitor->Trace(m_pRootNode);
+  visitor->Trace(m_pLocaleMgr);
   visitor->Trace(m_pLayoutProcessor);
   visitor->Trace(m_pScriptDataWindow);
   visitor->Trace(m_pScriptEvent);
@@ -1299,7 +1300,7 @@
 void CXFA_Document::ClearLayoutData() {
   m_pLayoutProcessor = nullptr;
   m_pScriptContext.reset();
-  m_pLocaleMgr.reset();
+  m_pLocaleMgr.Clear();
   m_pScriptDataWindow = nullptr;
   m_pScriptEvent = nullptr;
   m_pScriptHost = nullptr;
@@ -1426,11 +1427,12 @@
 
 CXFA_LocaleMgr* CXFA_Document::GetLocaleMgr() {
   if (!m_pLocaleMgr) {
-    m_pLocaleMgr = std::make_unique<CXFA_LocaleMgr>(
+    m_pLocaleMgr = cppgc::MakeGarbageCollected<CXFA_LocaleMgr>(
+        heap_->GetAllocationHandle(), heap_,
         ToNode(GetXFAObject(XFA_HASHCODE_LocaleSet)),
         GetNotify()->GetAppProvider()->GetLanguage());
   }
-  return m_pLocaleMgr.get();
+  return m_pLocaleMgr;
 }
 
 cppgc::Heap* CXFA_Document::GetHeap() const {
diff --git a/xfa/fxfa/parser/cxfa_document.h b/xfa/fxfa/parser/cxfa_document.h
index 94449b0..c6e6b2d 100644
--- a/xfa/fxfa/parser/cxfa_document.h
+++ b/xfa/fxfa/parser/cxfa_document.h
@@ -150,7 +150,7 @@
   cppgc::Member<CXFA_Node> m_pRootNode;
   std::unique_ptr<CFXJSE_Engine> m_pScriptContext;
   cppgc::Member<LayoutProcessorIface> m_pLayoutProcessor;
-  std::unique_ptr<CXFA_LocaleMgr> m_pLocaleMgr;
+  cppgc::Member<CXFA_LocaleMgr> m_pLocaleMgr;
   cppgc::Member<CScript_DataWindow> m_pScriptDataWindow;
   cppgc::Member<CScript_EventPseudoModel> m_pScriptEvent;
   cppgc::Member<CScript_HostPseudoModel> m_pScriptHost;
diff --git a/xfa/fxfa/parser/cxfa_localemgr.cpp b/xfa/fxfa/parser/cxfa_localemgr.cpp
index d866f55..6975bc6 100644
--- a/xfa/fxfa/parser/cxfa_localemgr.cpp
+++ b/xfa/fxfa/parser/cxfa_localemgr.cpp
@@ -13,6 +13,7 @@
 
 #include "core/fxcodec/flate/flatemodule.h"
 #include "core/fxcrt/fx_memory_wrappers.h"
+#include "fxjs/gc/container_trace.h"
 #include "fxjs/xfa/cjx_object.h"
 #include "xfa/fxfa/parser/cxfa_acrobat.h"
 #include "xfa/fxfa/parser/cxfa_common.h"
@@ -1064,8 +1065,8 @@
     0xB3, 0x85, 0xFA, 0x59, 0x2A, 0x7A, 0xFF, 0x3D, 0xC4, 0x3F, 0xDE, 0xCB,
     0x8B, 0xC4};
 
-std::unique_ptr<LocaleIface> GetLocaleFromBuffer(
-    pdfium::span<const uint8_t> src_span) {
+CXFA_XMLLocale* GetLocaleFromBuffer(cppgc::Heap* heap,
+                                    pdfium::span<const uint8_t> src_span) {
   if (src_span.empty())
     return nullptr;
 
@@ -1076,7 +1077,7 @@
   if (!output)
     return nullptr;
 
-  return CXFA_XMLLocale::Create(pdfium::make_span(output.get(), dwSize));
+  return CXFA_XMLLocale::Create(heap, pdfium::make_span(output.get(), dwSize));
 }
 
 uint16_t GetLanguage(WideString wsLanguage) {
@@ -1124,93 +1125,104 @@
 
 }  // namespace
 
-CXFA_LocaleMgr::CXFA_LocaleMgr(CXFA_Node* pLocaleSet, WideString wsDeflcid)
-    : m_pDefLocale(GetLocaleByName(wsDeflcid)),
+CXFA_LocaleMgr::CXFA_LocaleMgr(cppgc::Heap* pHeap,
+                               CXFA_Node* pLocaleSet,
+                               WideString wsDeflcid)
+    : m_pHeap(pHeap),
+      m_pDefLocale(GetLocaleByName(wsDeflcid)),
       m_dwDeflcid(GetLanguage(wsDeflcid)) {
   if (!pLocaleSet)
     return;
 
   for (CXFA_Node* pNodeLocale = pLocaleSet->GetFirstChild(); pNodeLocale;
        pNodeLocale = pNodeLocale->GetNextSibling()) {
-    m_LocaleArray.push_back(std::make_unique<CXFA_NodeLocale>(pNodeLocale));
+    m_LocaleArray.push_back(cppgc::MakeGarbageCollected<CXFA_NodeLocale>(
+        pHeap->GetAllocationHandle(), pNodeLocale));
   }
 }
 
 CXFA_LocaleMgr::~CXFA_LocaleMgr() = default;
 
-LocaleIface* CXFA_LocaleMgr::GetDefLocale() {
-  if (m_pDefLocale)
-    return m_pDefLocale.Get();
-
-  if (!m_LocaleArray.empty())
-    return m_LocaleArray[0].get();
-
-  if (!m_XMLLocaleArray.empty())
-    return m_XMLLocaleArray[0].get();
-
-  std::unique_ptr<LocaleIface> locale(GetLocale(m_dwDeflcid));
-  m_pDefLocale = locale.get();
-  if (locale)
-    m_XMLLocaleArray.push_back(std::move(locale));
-
-  return m_pDefLocale.Get();
+void CXFA_LocaleMgr::Trace(cppgc::Visitor* visitor) const {
+  ContainerTrace(visitor, m_LocaleArray);
+  ContainerTrace(visitor, m_XMLLocaleArray);
 }
 
-std::unique_ptr<LocaleIface> CXFA_LocaleMgr::GetLocale(uint16_t lcid) {
+LocaleIface* CXFA_LocaleMgr::GetDefLocale() {
+  if (m_pDefLocale)
+    return m_pDefLocale;
+
+  if (!m_LocaleArray.empty())
+    return m_LocaleArray[0];
+
+  if (!m_XMLLocaleArray.empty())
+    return m_XMLLocaleArray[0];
+
+  CXFA_XMLLocale* pLocale = GetLocale(m_dwDeflcid);
+  if (pLocale)
+    m_XMLLocaleArray.push_back(pLocale);
+
+  m_pDefLocale = pLocale;
+  return m_pDefLocale;
+}
+
+CXFA_XMLLocale* CXFA_LocaleMgr::GetLocale(uint16_t lcid) {
   switch (lcid) {
     case FX_LANG_zh_CN:
-      return GetLocaleFromBuffer(g_zhCN_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_zhCN_Locale);
     case FX_LANG_zh_TW:
-      return GetLocaleFromBuffer(g_zhTW_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_zhTW_Locale);
     case FX_LANG_zh_HK:
-      return GetLocaleFromBuffer(g_zhHK_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_zhHK_Locale);
     case FX_LANG_ja_JP:
-      return GetLocaleFromBuffer(g_jaJP_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_jaJP_Locale);
     case FX_LANG_ko_KR:
-      return GetLocaleFromBuffer(g_koKR_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_koKR_Locale);
     case FX_LANG_en_GB:
-      return GetLocaleFromBuffer(g_enGB_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_enGB_Locale);
     case FX_LANG_es_LA:
-      return GetLocaleFromBuffer(g_esLA_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_esLA_Locale);
     case FX_LANG_es_ES:
-      return GetLocaleFromBuffer(g_esES_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_esES_Locale);
     case FX_LANG_de_DE:
-      return GetLocaleFromBuffer(g_deDE_Loacale);
+      return GetLocaleFromBuffer(m_pHeap, g_deDE_Loacale);
     case FX_LANG_fr_FR:
-      return GetLocaleFromBuffer(g_frFR_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_frFR_Locale);
     case FX_LANG_it_IT:
-      return GetLocaleFromBuffer(g_itIT_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_itIT_Locale);
     case FX_LANG_pt_BR:
-      return GetLocaleFromBuffer(g_ptBR_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_ptBR_Locale);
     case FX_LANG_nl_NL:
-      return GetLocaleFromBuffer(g_nlNL_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_nlNL_Locale);
     case FX_LANG_ru_RU:
-      return GetLocaleFromBuffer(g_ruRU_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_ruRU_Locale);
     case FX_LANG_en_US:
     default:
-      return GetLocaleFromBuffer(g_enUS_Locale);
+      return GetLocaleFromBuffer(m_pHeap, g_enUS_Locale);
   }
 }
 
 LocaleIface* CXFA_LocaleMgr::GetLocaleByName(const WideString& wsLocaleName) {
   for (size_t i = 0; i < m_LocaleArray.size(); i++) {
-    LocaleIface* pLocale = m_LocaleArray[i].get();
+    LocaleIface* pLocale = m_LocaleArray[i];
     if (pLocale->GetName() == wsLocaleName)
       return pLocale;
   }
   if (wsLocaleName.GetLength() < 2)
     return nullptr;
+
   for (size_t i = 0; i < m_XMLLocaleArray.size(); i++) {
-    LocaleIface* pLocale = m_XMLLocaleArray[i].get();
+    LocaleIface* pLocale = m_XMLLocaleArray[i];
     if (pLocale->GetName() == wsLocaleName)
       return pLocale;
   }
 
-  std::unique_ptr<LocaleIface> pLocale(GetLocale(GetLanguage(wsLocaleName)));
-  LocaleIface* pRetLocale = pLocale.get();
-  if (pLocale)
-    m_XMLLocaleArray.push_back(std::move(pLocale));
-  return pRetLocale;
+  CXFA_XMLLocale* pLocale = GetLocale(GetLanguage(wsLocaleName));
+  if (!pLocale)
+    return nullptr;
+
+  m_XMLLocaleArray.push_back(pLocale);
+  return pLocale;
 }
 
 void CXFA_LocaleMgr::SetDefLocale(LocaleIface* pLocale) {
diff --git a/xfa/fxfa/parser/cxfa_localemgr.h b/xfa/fxfa/parser/cxfa_localemgr.h
index 9370988..12455b1 100644
--- a/xfa/fxfa/parser/cxfa_localemgr.h
+++ b/xfa/fxfa/parser/cxfa_localemgr.h
@@ -12,16 +12,24 @@
 
 #include "core/fxcrt/unowned_ptr.h"
 #include "core/fxcrt/widestring.h"
+#include "fxjs/gc/heap.h"
+#include "v8/include/cppgc/garbage-collected.h"
+#include "v8/include/cppgc/member.h"
 #include "xfa/fgas/crt/locale_mgr_iface.h"
 
 class CXFA_Node;
+class CXFA_NodeLocale;
+class CXFA_XMLLocale;
 class LocaleIface;
 
-class CXFA_LocaleMgr : public LocaleMgrIface {
+class CXFA_LocaleMgr : public cppgc::GarbageCollected<CXFA_LocaleMgr>,
+                       public LocaleMgrIface {
  public:
-  CXFA_LocaleMgr(CXFA_Node* pLocaleSet, WideString wsDeflcid);
+  CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED;
   ~CXFA_LocaleMgr() override;
 
+  void Trace(cppgc::Visitor* visitor) const;
+
   LocaleIface* GetDefLocale() override;
   LocaleIface* GetLocaleByName(const WideString& wsLocaleName) override;
 
@@ -29,13 +37,19 @@
   WideString GetConfigLocaleName(CXFA_Node* pConfig);
 
  private:
-  std::unique_ptr<LocaleIface> GetLocale(uint16_t lcid);
+  CXFA_LocaleMgr(cppgc::Heap* pHeap,
+                 CXFA_Node* pLocaleSet,
+                 WideString wsDeflcid);
 
-  std::vector<std::unique_ptr<LocaleIface>> m_LocaleArray;
-  std::vector<std::unique_ptr<LocaleIface>> m_XMLLocaleArray;
+  // May allocate a new object on the cppgc heap.
+  CXFA_XMLLocale* GetLocale(uint16_t lcid);
 
-  // Owned by m_LocaleArray or m_XMLLocaleArray.
-  UnownedPtr<LocaleIface> m_pDefLocale;
+  UnownedPtr<cppgc::Heap> m_pHeap;
+  std::vector<cppgc::Member<CXFA_NodeLocale>> m_LocaleArray;
+  std::vector<cppgc::Member<CXFA_XMLLocale>> m_XMLLocaleArray;
+
+  // Raw, owned by m_LocaleArray or m_XMLLocaleArray, may be GC'd after them.
+  LocaleIface* m_pDefLocale = nullptr;
 
   WideString m_wsConfigLocale;
   uint16_t m_dwDeflcid;
diff --git a/xfa/fxfa/parser/cxfa_localevalue.cpp b/xfa/fxfa/parser/cxfa_localevalue.cpp
index 20f3730..5049b09 100644
--- a/xfa/fxfa/parser/cxfa_localevalue.cpp
+++ b/xfa/fxfa/parser/cxfa_localevalue.cpp
@@ -65,6 +65,8 @@
 }
 
 class ScopedLocale {
+  CPPGC_STACK_ALLOCATED();  // Raw/Unowned pointers allowed.
+
  public:
   ScopedLocale(CXFA_LocaleMgr* pLocaleMgr, LocaleIface* pNewLocale)
       : m_pLocaleMgr(pLocaleMgr),
@@ -137,8 +139,7 @@
   size_t i = 0;
   for (; !bRet && i < wsPatterns.size(); i++) {
     const WideString& wsFormat = wsPatterns[i];
-    auto pFormat =
-        std::make_unique<CFGAS_StringFormatter>(m_pLocaleMgr.Get(), wsFormat);
+    auto pFormat = std::make_unique<CFGAS_StringFormatter>(wsFormat);
     switch (ValueCategory(pFormat->GetCategory(), m_dwType)) {
       case CFGAS_StringFormatter::Category::kNull:
         bRet = pFormat->ParseNull(wsValue);
@@ -152,9 +153,9 @@
         break;
       case CFGAS_StringFormatter::Category::kNum: {
         WideString fNum;
-        bRet = pFormat->ParseNum(wsValue, &fNum);
+        bRet = pFormat->ParseNum(m_pLocaleMgr.Get(), wsValue, &fNum);
         if (!bRet)
-          bRet = pFormat->FormatNum(wsValue, &wsOutput);
+          bRet = pFormat->FormatNum(m_pLocaleMgr.Get(), wsValue, &wsOutput);
         break;
       }
       case CFGAS_StringFormatter::Category::kText:
@@ -168,10 +169,12 @@
         bRet = ValidateCanonicalDate(wsValue, &dt);
         if (!bRet) {
           bRet = pFormat->ParseDateTime(
-              wsValue, CFGAS_StringFormatter::DateTimeType::kDate, &dt);
+              m_pLocaleMgr.Get(), wsValue,
+              CFGAS_StringFormatter::DateTimeType::kDate, &dt);
           if (!bRet) {
             bRet = pFormat->FormatDateTime(
-                wsValue, CFGAS_StringFormatter::DateTimeType::kDate, &wsOutput);
+                m_pLocaleMgr.Get(), wsValue,
+                CFGAS_StringFormatter::DateTimeType::kDate, &wsOutput);
           }
         }
         break;
@@ -179,21 +182,24 @@
       case CFGAS_StringFormatter::Category::kTime: {
         CFX_DateTime dt;
         bRet = pFormat->ParseDateTime(
-            wsValue, CFGAS_StringFormatter::DateTimeType::kTime, &dt);
+            m_pLocaleMgr.Get(), wsValue,
+            CFGAS_StringFormatter::DateTimeType::kTime, &dt);
         if (!bRet) {
           bRet = pFormat->FormatDateTime(
-              wsValue, CFGAS_StringFormatter::DateTimeType::kTime, &wsOutput);
+              m_pLocaleMgr.Get(), wsValue,
+              CFGAS_StringFormatter::DateTimeType::kTime, &wsOutput);
         }
         break;
       }
       case CFGAS_StringFormatter::Category::kDateTime: {
         CFX_DateTime dt;
         bRet = pFormat->ParseDateTime(
-            wsValue, CFGAS_StringFormatter::DateTimeType::kDateTime, &dt);
+            m_pLocaleMgr.Get(), wsValue,
+            CFGAS_StringFormatter::DateTimeType::kDateTime, &dt);
         if (!bRet) {
           bRet = pFormat->FormatDateTime(
-              wsValue, CFGAS_StringFormatter::DateTimeType::kDateTime,
-              &wsOutput);
+              m_pLocaleMgr.Get(), wsValue,
+              CFGAS_StringFormatter::DateTimeType::kDateTime, &wsOutput);
         }
         break;
       }
@@ -283,8 +289,7 @@
 
   wsResult.clear();
   bool bRet = false;
-  auto pFormat =
-      std::make_unique<CFGAS_StringFormatter>(m_pLocaleMgr.Get(), wsFormat);
+  auto pFormat = std::make_unique<CFGAS_StringFormatter>(wsFormat);
   CFGAS_StringFormatter::Category eCategory =
       ValueCategory(pFormat->GetCategory(), m_dwType);
   switch (eCategory) {
@@ -297,22 +302,25 @@
         bRet = pFormat->FormatZero(&wsResult);
       break;
     case CFGAS_StringFormatter::Category::kNum:
-      bRet = pFormat->FormatNum(m_wsValue, &wsResult);
+      bRet = pFormat->FormatNum(m_pLocaleMgr.Get(), m_wsValue, &wsResult);
       break;
     case CFGAS_StringFormatter::Category::kText:
       bRet = pFormat->FormatText(m_wsValue, &wsResult);
       break;
     case CFGAS_StringFormatter::Category::kDate:
-      bRet = pFormat->FormatDateTime(
-          m_wsValue, CFGAS_StringFormatter::DateTimeType::kDate, &wsResult);
+      bRet = pFormat->FormatDateTime(m_pLocaleMgr.Get(), m_wsValue,
+                                     CFGAS_StringFormatter::DateTimeType::kDate,
+                                     &wsResult);
       break;
     case CFGAS_StringFormatter::Category::kTime:
-      bRet = pFormat->FormatDateTime(
-          m_wsValue, CFGAS_StringFormatter::DateTimeType::kTime, &wsResult);
+      bRet = pFormat->FormatDateTime(m_pLocaleMgr.Get(), m_wsValue,
+                                     CFGAS_StringFormatter::DateTimeType::kTime,
+                                     &wsResult);
       break;
     case CFGAS_StringFormatter::Category::kDateTime:
       bRet = pFormat->FormatDateTime(
-          m_wsValue, CFGAS_StringFormatter::DateTimeType::kDateTime, &wsResult);
+          m_pLocaleMgr.Get(), m_wsValue,
+          CFGAS_StringFormatter::DateTimeType::kDateTime, &wsResult);
       break;
     default:
       wsResult = m_wsValue;
@@ -559,8 +567,7 @@
   bool bRet = false;
   for (size_t i = 0; !bRet && i < wsPatterns.size(); i++) {
     const WideString& wsFormat = wsPatterns[i];
-    auto pFormat =
-        std::make_unique<CFGAS_StringFormatter>(m_pLocaleMgr.Get(), wsFormat);
+    auto pFormat = std::make_unique<CFGAS_StringFormatter>(wsFormat);
     switch (ValueCategory(pFormat->GetCategory(), m_dwType)) {
       case CFGAS_StringFormatter::Category::kNull:
         bRet = pFormat->ParseNull(wsValue);
@@ -574,7 +581,7 @@
         break;
       case CFGAS_StringFormatter::Category::kNum: {
         WideString fNum;
-        bRet = pFormat->ParseNum(wsValue, &fNum);
+        bRet = pFormat->ParseNum(m_pLocaleMgr.Get(), wsValue, &fNum);
         if (bRet)
           m_wsValue = std::move(fNum);
         break;
@@ -587,7 +594,8 @@
         bRet = ValidateCanonicalDate(wsValue, &dt);
         if (!bRet) {
           bRet = pFormat->ParseDateTime(
-              wsValue, CFGAS_StringFormatter::DateTimeType::kDate, &dt);
+              m_pLocaleMgr.Get(), wsValue,
+              CFGAS_StringFormatter::DateTimeType::kDate, &dt);
         }
         if (bRet)
           SetDate(dt);
@@ -596,7 +604,8 @@
       case CFGAS_StringFormatter::Category::kTime: {
         CFX_DateTime dt;
         bRet = pFormat->ParseDateTime(
-            wsValue, CFGAS_StringFormatter::DateTimeType::kTime, &dt);
+            m_pLocaleMgr.Get(), wsValue,
+            CFGAS_StringFormatter::DateTimeType::kTime, &dt);
         if (bRet)
           SetTime(dt);
         break;
@@ -604,7 +613,8 @@
       case CFGAS_StringFormatter::Category::kDateTime: {
         CFX_DateTime dt;
         bRet = pFormat->ParseDateTime(
-            wsValue, CFGAS_StringFormatter::DateTimeType::kDateTime, &dt);
+            m_pLocaleMgr.Get(), wsValue,
+            CFGAS_StringFormatter::DateTimeType::kDateTime, &dt);
         if (bRet)
           SetDateTime(dt);
         break;
diff --git a/xfa/fxfa/parser/cxfa_localevalue.h b/xfa/fxfa/parser/cxfa_localevalue.h
index 2005905..cb154ef 100644
--- a/xfa/fxfa/parser/cxfa_localevalue.h
+++ b/xfa/fxfa/parser/cxfa_localevalue.h
@@ -10,6 +10,7 @@
 #include "core/fxcrt/fx_string.h"
 #include "core/fxcrt/fx_system.h"
 #include "core/fxcrt/unowned_ptr.h"
+#include "v8/include/cppgc/macros.h"
 #include "xfa/fxfa/parser/cxfa_node.h"
 
 class LocaleIface;
@@ -27,6 +28,8 @@
 #define XFA_VT_DATETIME 128
 
 class CXFA_LocaleValue {
+  CPPGC_STACK_ALLOCATED();  // Raw/Unowned pointers allowed.
+
  public:
   CXFA_LocaleValue();
   CXFA_LocaleValue(uint32_t dwType, CXFA_LocaleMgr* pLocaleMgr);
diff --git a/xfa/fxfa/parser/cxfa_nodelocale.cpp b/xfa/fxfa/parser/cxfa_nodelocale.cpp
index 95cb4eb..1f717ed 100644
--- a/xfa/fxfa/parser/cxfa_nodelocale.cpp
+++ b/xfa/fxfa/parser/cxfa_nodelocale.cpp
@@ -38,14 +38,17 @@
   return WideString();
 }
 
-CXFA_NodeLocale::CXFA_NodeLocale(CXFA_Node* pLocale) : m_pLocale(pLocale) {}
+CXFA_NodeLocale::CXFA_NodeLocale(CXFA_Node* pNode) : m_pNode(pNode) {}
 
 CXFA_NodeLocale::~CXFA_NodeLocale() = default;
 
+void CXFA_NodeLocale::Trace(cppgc::Visitor* visitor) const {
+  visitor->Trace(m_pNode);
+}
+
 WideString CXFA_NodeLocale::GetName() const {
-  return WideString(m_pLocale
-                        ? m_pLocale->JSObject()->GetCData(XFA_Attribute::Name)
-                        : nullptr);
+  return WideString(m_pNode ? m_pNode->JSObject()->GetCData(XFA_Attribute::Name)
+                            : nullptr);
 }
 
 WideString CXFA_NodeLocale::GetDecimalSymbol() const {
@@ -70,9 +73,9 @@
 
 WideString CXFA_NodeLocale::GetDateTimeSymbols() const {
   CXFA_DateTimeSymbols* pSymbols =
-      m_pLocale ? m_pLocale->GetChild<CXFA_DateTimeSymbols>(
-                      0, XFA_Element::DateTimeSymbols, false)
-                : nullptr;
+      m_pNode ? m_pNode->GetChild<CXFA_DateTimeSymbols>(
+                    0, XFA_Element::DateTimeSymbols, false)
+              : nullptr;
   return pSymbols ? pSymbols->JSObject()->GetContent(false) : WideString();
 }
 
@@ -145,7 +148,7 @@
 WideString CXFA_NodeLocale::GetSymbol(XFA_Element eElement,
                                       WideStringView symbol_type) const {
   CXFA_Node* pSymbols =
-      m_pLocale ? m_pLocale->GetChild<CXFA_Node>(0, eElement, false) : nullptr;
+      m_pNode ? m_pNode->GetChild<CXFA_Node>(0, eElement, false) : nullptr;
   CXFA_Node* pSymbol = GetNodeByName(pSymbols, symbol_type);
   return pSymbol ? pSymbol->JSObject()->GetContent(false) : WideString();
 }
@@ -154,9 +157,9 @@
                                               int index,
                                               bool bAbbr) const {
   CXFA_CalendarSymbols* pCalendar =
-      m_pLocale ? m_pLocale->GetChild<CXFA_CalendarSymbols>(
-                      0, XFA_Element::CalendarSymbols, false)
-                : nullptr;
+      m_pNode ? m_pNode->GetChild<CXFA_CalendarSymbols>(
+                    0, XFA_Element::CalendarSymbols, false)
+              : nullptr;
   if (!pCalendar)
     return WideString();
 
diff --git a/xfa/fxfa/parser/cxfa_nodelocale.h b/xfa/fxfa/parser/cxfa_nodelocale.h
index 4b3c205..8ad9222 100644
--- a/xfa/fxfa/parser/cxfa_nodelocale.h
+++ b/xfa/fxfa/parser/cxfa_nodelocale.h
@@ -7,7 +7,9 @@
 #ifndef XFA_FXFA_PARSER_CXFA_NODELOCALE_H_
 #define XFA_FXFA_PARSER_CXFA_NODELOCALE_H_
 
-#include "core/fxcrt/unowned_ptr.h"
+#include "fxjs/gc/heap.h"
+#include "v8/include/cppgc/garbage-collected.h"
+#include "v8/include/cppgc/member.h"
 #include "xfa/fgas/crt/locale_iface.h"
 #include "xfa/fxfa/fxfa_basic.h"
 
@@ -15,11 +17,14 @@
 
 WideString XFA_PatternToString(LocaleIface::NumSubcategory category);
 
-class CXFA_NodeLocale final : public LocaleIface {
+class CXFA_NodeLocale final : public cppgc::GarbageCollected<CXFA_NodeLocale>,
+                              public LocaleIface {
  public:
-  explicit CXFA_NodeLocale(CXFA_Node* pLocale);
+  CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED;
   ~CXFA_NodeLocale() override;
 
+  void Trace(cppgc::Visitor* visitor) const;
+
   // LocaleIface
   WideString GetName() const override;
   WideString GetDecimalSymbol() const override;
@@ -39,13 +44,15 @@
   WideString GetNumPattern(NumSubcategory eType) const override;
 
  private:
+  explicit CXFA_NodeLocale(CXFA_Node* pNode);
+
   CXFA_Node* GetNodeByName(CXFA_Node* pParent, WideStringView wsName) const;
   WideString GetSymbol(XFA_Element eElement, WideStringView symbol_type) const;
   WideString GetCalendarSymbol(XFA_Element eElement,
                                int index,
                                bool bAbbr) const;
 
-  UnownedPtr<CXFA_Node> const m_pLocale;
+  cppgc::Member<CXFA_Node> const m_pNode;
 };
 
 #endif  // XFA_FXFA_PARSER_CXFA_NODELOCALE_H_
diff --git a/xfa/fxfa/parser/cxfa_xmllocale.cpp b/xfa/fxfa/parser/cxfa_xmllocale.cpp
index f2b881f..aaad186 100644
--- a/xfa/fxfa/parser/cxfa_xmllocale.cpp
+++ b/xfa/fxfa/parser/cxfa_xmllocale.cpp
@@ -29,27 +29,23 @@
 }  // namespace
 
 // static
-std::unique_ptr<CXFA_XMLLocale> CXFA_XMLLocale::Create(
-    pdfium::span<uint8_t> data) {
+CXFA_XMLLocale* CXFA_XMLLocale::Create(cppgc::Heap* heap,
+                                       pdfium::span<uint8_t> data) {
   auto stream = pdfium::MakeRetain<CFX_ReadOnlyMemoryStream>(data);
   CFX_XMLParser parser(stream);
   auto doc = parser.Parse();
   if (!doc)
     return nullptr;
 
-  CFX_XMLElement* locale = nullptr;
   for (auto* child = doc->GetRoot()->GetFirstChild(); child;
        child = child->GetNextSibling()) {
     CFX_XMLElement* elem = ToXMLElement(child);
     if (elem && elem->GetName().EqualsASCII("locale")) {
-      locale = elem;
-      break;
+      return cppgc::MakeGarbageCollected<CXFA_XMLLocale>(
+          heap->GetAllocationHandle(), std::move(doc), elem);
     }
   }
-  if (!locale)
-    return nullptr;
-
-  return std::make_unique<CXFA_XMLLocale>(std::move(doc), locale);
+  return nullptr;
 }
 
 CXFA_XMLLocale::CXFA_XMLLocale(std::unique_ptr<CFX_XMLDocument> doc,
@@ -61,6 +57,8 @@
 
 CXFA_XMLLocale::~CXFA_XMLLocale() = default;
 
+void CXFA_XMLLocale::Trace(cppgc::Visitor* visitor) const {}
+
 WideString CXFA_XMLLocale::GetName() const {
   return locale_->GetAttribute(L"name");
 }
diff --git a/xfa/fxfa/parser/cxfa_xmllocale.h b/xfa/fxfa/parser/cxfa_xmllocale.h
index 8819eb1..1fc2a98 100644
--- a/xfa/fxfa/parser/cxfa_xmllocale.h
+++ b/xfa/fxfa/parser/cxfa_xmllocale.h
@@ -10,20 +10,25 @@
 #include <memory>
 
 #include "core/fxcrt/unowned_ptr.h"
+#include "fxjs/gc/heap.h"
 #include "third_party/base/span.h"
+#include "v8/include/cppgc/garbage-collected.h"
 #include "xfa/fgas/crt/locale_iface.h"
 
 class CFX_XMLDocument;
 class CFX_XMLElement;
 
-class CXFA_XMLLocale final : public LocaleIface {
+class CXFA_XMLLocale final : public cppgc::GarbageCollected<CXFA_XMLLocale>,
+                             public LocaleIface {
  public:
-  static std::unique_ptr<CXFA_XMLLocale> Create(pdfium::span<uint8_t> data);
+  // Object is created on cppgc heap.
+  static CXFA_XMLLocale* Create(cppgc::Heap* heap, pdfium::span<uint8_t> data);
 
-  explicit CXFA_XMLLocale(std::unique_ptr<CFX_XMLDocument> root,
-                          CFX_XMLElement* locale);
+  CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED;
   ~CXFA_XMLLocale() override;
 
+  void Trace(cppgc::Visitor* visitor) const;
+
   // LocaleIface
   WideString GetName() const override;
   WideString GetDecimalSymbol() const override;
@@ -43,6 +48,8 @@
   WideString GetNumPattern(NumSubcategory eType) const override;
 
  private:
+  CXFA_XMLLocale(std::unique_ptr<CFX_XMLDocument> root, CFX_XMLElement* locale);
+
   WideString GetPattern(CFX_XMLElement* pElement,
                         WideStringView bsTag,
                         WideStringView wsName) const;
diff --git a/xfa/fxfa/parser/cxfa_xmllocale_unittest.cpp b/xfa/fxfa/parser/cxfa_xmllocale_unittest.cpp
index b1f6de5..d6cd3ea 100644
--- a/xfa/fxfa/parser/cxfa_xmllocale_unittest.cpp
+++ b/xfa/fxfa/parser/cxfa_xmllocale_unittest.cpp
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "testing/fxgc_unittest.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -88,32 +89,35 @@
     "</currencySymbols>"
     "</locale>";
 
-std::unique_ptr<CXFA_XMLLocale> CreateLocaleHelper() {
-  return CXFA_XMLLocale::Create(pdfium::as_writable_bytes(
-      pdfium::make_span(const_cast<char*>(kXMLData), strlen(kXMLData))));
+CXFA_XMLLocale* CreateLocaleHelper(cppgc::Heap* heap) {
+  return CXFA_XMLLocale::Create(
+      heap, pdfium::as_writable_bytes(pdfium::make_span(
+                const_cast<char*>(kXMLData), strlen(kXMLData))));
 }
 
 }  // namespace
 
-TEST(CXFA_XMLLocaleTest, Create) {
-  auto locale = CreateLocaleHelper();
+class CXFA_XMLLocaleTest : public FXGCUnitTest {};
+
+TEST_F(CXFA_XMLLocaleTest, Create) {
+  auto* locale = CreateLocaleHelper(heap());
   EXPECT_TRUE(locale != nullptr);
 }
 
-TEST(CXFA_XMLLocaleTest, CreateBadXML) {
-  auto locale = CXFA_XMLLocale::Create(pdfium::span<uint8_t>());
+TEST_F(CXFA_XMLLocaleTest, CreateBadXML) {
+  auto* locale = CXFA_XMLLocale::Create(heap(), pdfium::span<uint8_t>());
   EXPECT_TRUE(locale == nullptr);
 }
 
-TEST(CXFA_XMLLocaleTest, GetName) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetName) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"en_US", locale->GetName());
 }
 
-TEST(CXFA_XMLLocaleTest, GetNumericSymbols) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetNumericSymbols) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L".", locale->GetDecimalSymbol());
@@ -123,15 +127,15 @@
   EXPECT_EQ(L"$", locale->GetCurrencySymbol());
 }
 
-TEST(CXFA_XMLLocaleTest, GetDateTimeSymbols) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetDateTimeSymbols) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"GyMdkHmsSEDFwWahKzZ", locale->GetDateTimeSymbols());
 }
 
-TEST(CXFA_XMLLocaleTest, GetMonthName) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetMonthName) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"", locale->GetMonthName(24, false));
@@ -140,8 +144,8 @@
   EXPECT_EQ(L"February", locale->GetMonthName(1, false));
 }
 
-TEST(CXFA_XMLLocaleTest, GetDayName) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetDayName) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"", locale->GetDayName(24, false));
@@ -150,24 +154,24 @@
   EXPECT_EQ(L"Monday", locale->GetDayName(1, false));
 }
 
-TEST(CXFA_XMLLocaleTest, GetMeridiemName) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetMeridiemName) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"AM", locale->GetMeridiemName(true));
   EXPECT_EQ(L"PM", locale->GetMeridiemName(false));
 }
 
-TEST(CXFA_XMLLocaleTest, GetEraName) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetEraName) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"AD", locale->GetEraName(true));
   EXPECT_EQ(L"BC", locale->GetEraName(false));
 }
 
-TEST(CXFA_XMLLocaleTest, GetDatePattern) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetDatePattern) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"M/D/YY",
@@ -182,8 +186,8 @@
             locale->GetDatePattern(LocaleIface::DateTimeSubcategory::kLong));
 }
 
-TEST(CXFA_XMLLocaleTest, GetTimePattern) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetTimePattern) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"h:MM A",
@@ -198,8 +202,8 @@
             locale->GetTimePattern(LocaleIface::DateTimeSubcategory::kLong));
 }
 
-TEST(CXFA_XMLLocaleTest, GetNumPattern) {
-  auto locale = CreateLocaleHelper();
+TEST_F(CXFA_XMLLocaleTest, GetNumPattern) {
+  auto* locale = CreateLocaleHelper(heap());
   ASSERT_TRUE(locale != nullptr);
 
   EXPECT_EQ(L"z,zzz,zzz,zzz,zzz,zzz%",