Bug 1431957 - Move functionality used in the implementation of multiple Intl.* constructors into builtin/intl/CommonFunctions.*. r=anba
authorJeff Walden <jwalden@mit.edu>
Fri, 19 Jan 2018 16:14:03 -0800
changeset 401025 20c515e44ae858c2cd27a97f37cd447145a80e2a
parent 401024 ffb9f4e463dbc67d6b3f1e543766d678941aaed0
child 401026 e6db3ed5c6f61308d044594df1dc2a6336703263
push id99283
push userjwalden@mit.edu
push dateSat, 27 Jan 2018 07:54:29 +0000
treeherdermozilla-inbound@c50cbf6e8176 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanba
bugs1431957
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1431957 - Move functionality used in the implementation of multiple Intl.* constructors into builtin/intl/CommonFunctions.*. r=anba
js/src/builtin/Intl.cpp
js/src/builtin/intl/CommonFunctions.cpp
js/src/builtin/intl/CommonFunctions.h
js/src/moz.build
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -23,16 +23,17 @@
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfriendapi.h"
 #include "jsobj.h"
 #include "jsstr.h"
 #include "jsutil.h"
 
+#include "builtin/intl/CommonFunctions.h"
 #include "builtin/intl/ICUStubs.h"
 #include "builtin/intl/ScopedICUObject.h"
 #include "builtin/IntlTimeZoneData.h"
 #include "ds/Sort.h"
 #include "gc/FreeOp.h"
 #include "js/Date.h"
 #include "vm/DateTime.h"
 #include "vm/GlobalObject.h"
@@ -55,171 +56,22 @@ using mozilla::IsNaN;
 using mozilla::IsNegativeZero;
 using mozilla::PodCopy;
 using mozilla::Range;
 using mozilla::RangedPtr;
 
 using JS::ClippedTime;
 using JS::TimeClip;
 
-/******************** Common to Intl constructors ********************/
-
-static bool
-IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
-               HandleValue locales, HandleValue options)
-{
-    FixedInvokeArgs<3> args(cx);
-
-    args[0].setObject(*obj);
-    args[1].set(locales);
-    args[2].set(options);
-
-    RootedValue ignored(cx);
-    if (!js::CallSelfHostedFunction(cx, initializer, NullHandleValue, args, &ignored))
-        return false;
-
-    MOZ_ASSERT(ignored.isUndefined(),
-               "Unexpected return value from non-legacy Intl object initializer");
-    return true;
-}
-
-enum class DateTimeFormatOptions
-{
-    Standard,
-    EnableMozExtensions,
-};
-
-static bool
-LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
-                     HandleValue thisValue, HandleValue locales, HandleValue options,
-                     DateTimeFormatOptions dtfOptions, MutableHandleValue result)
-{
-    FixedInvokeArgs<5> args(cx);
-
-    args[0].setObject(*obj);
-    args[1].set(thisValue);
-    args[2].set(locales);
-    args[3].set(options);
-    args[4].setBoolean(dtfOptions == DateTimeFormatOptions::EnableMozExtensions);
-
-    if (!js::CallSelfHostedFunction(cx, initializer, NullHandleValue, args, result))
-        return false;
-
-    MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
-    return true;
-}
-
-// CountAvailable and GetAvailable describe the signatures used for ICU API
-// to determine available locales for various functionality.
-using CountAvailable = int32_t (*)();
-using GetAvailable = const char* (*)(int32_t localeIndex);
-
-static bool
-intl_availableLocales(JSContext* cx, CountAvailable countAvailable,
-                      GetAvailable getAvailable, MutableHandleValue result)
-{
-    RootedObject locales(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
-    if (!locales)
-        return false;
-
-#if ENABLE_INTL_API
-    RootedAtom a(cx);
-    uint32_t count = countAvailable();
-    for (uint32_t i = 0; i < count; i++) {
-        const char* locale = getAvailable(i);
-        auto lang = DuplicateString(cx, locale);
-        if (!lang)
-            return false;
-        char* p;
-        while ((p = strchr(lang.get(), '_')))
-            *p = '-';
-        a = Atomize(cx, lang.get(), strlen(lang.get()));
-        if (!a)
-            return false;
-        if (!DefineDataProperty(cx, locales, a->asPropertyName(), TrueHandleValue))
-            return false;
-    }
-#endif
-    result.setObject(*locales);
-    return true;
-}
-
-/**
- * Returns the object holding the internal properties for obj.
- */
-static JSObject*
-GetInternals(JSContext* cx, HandleObject obj)
-{
-    FixedInvokeArgs<1> args(cx);
-
-    args[0].setObject(*obj);
-
-    RootedValue v(cx);
-    if (!js::CallSelfHostedFunction(cx, cx->names().getInternals, NullHandleValue, args, &v))
-        return nullptr;
-
-    return &v.toObject();
-}
-
-static bool
-equal(const char* s1, const char* s2)
-{
-    return !strcmp(s1, s2);
-}
-
-static const char*
-icuLocale(const char* locale)
-{
-    if (equal(locale, "und"))
-        return ""; // ICU root locale
-    return locale;
-}
-
-// Starting with ICU 59, UChar defaults to char16_t.
-static_assert(mozilla::IsSame<UChar, char16_t>::value,
-              "SpiderMonkey doesn't support redefining UChar to a different type");
-
-// The inline capacity we use for the char16_t Vectors.
-static const size_t INITIAL_CHAR_BUFFER_SIZE = 32;
-
-template <typename ICUStringFunction, size_t InlineCapacity>
-static int32_t
-Call(JSContext* cx, const ICUStringFunction& strFn, Vector<char16_t, InlineCapacity>& chars)
-{
-    MOZ_ASSERT(chars.length() == 0);
-    MOZ_ALWAYS_TRUE(chars.resize(InlineCapacity));
-
-    UErrorCode status = U_ZERO_ERROR;
-    int32_t size = strFn(chars.begin(), InlineCapacity, &status);
-    if (status == U_BUFFER_OVERFLOW_ERROR) {
-        MOZ_ASSERT(size >= 0);
-        if (!chars.resize(size_t(size)))
-            return -1;
-        status = U_ZERO_ERROR;
-        strFn(chars.begin(), size, &status);
-    }
-    if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
-        return -1;
-    }
-    MOZ_ASSERT(size >= 0);
-    return size;
-}
-
-template <typename ICUStringFunction>
-static JSString*
-Call(JSContext* cx, const ICUStringFunction& strFn)
-{
-    Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
-    int32_t size = Call(cx, strFn, chars);
-    if (size < 0)
-        return nullptr;
-    return NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
-}
-
+using js::intl::CallICU;
+using js::intl::DateTimeFormatOptions;
+using js::intl::GetAvailableLocales;
+using js::intl::IcuLocale;
+using js::intl::INITIAL_CHAR_BUFFER_SIZE;
+using js::intl::StringsAreEqual;
 
 /******************** Collator ********************/
 
 const ClassOps CollatorObject::classOps_ = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* enumerate */
     nullptr, /* newEnumerate */
@@ -291,17 +143,17 @@ Collator(JSContext* cx, const CallArgs& 
 
     collator->setReservedSlot(CollatorObject::INTERNALS_SLOT, NullValue());
     collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
 
     HandleValue locales = args.get(0);
     HandleValue options = args.get(1);
 
     // Step 6.
-    if (!IntlInitialize(cx, collator, cx->names().InitializeCollator, locales, options))
+    if (!intl::InitializeObject(cx, collator, cx->names().InitializeCollator, locales, options))
         return false;
 
     args.rval().setObject(*collator);
     return true;
 }
 
 static bool
 Collator(JSContext* cx, unsigned argc, Value* vp)
@@ -367,17 +219,17 @@ CreateCollatorPrototype(JSContext* cx, H
 
 bool
 js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     RootedValue result(cx);
-    if (!intl_availableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result))
+    if (!GetAvailableLocales(cx, ucol_countAvailable, ucol_getAvailable, &result))
         return false;
     args.rval().set(result);
     return true;
 }
 
 bool
 js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -386,24 +238,24 @@ js::intl_availableCollations(JSContext* 
     MOZ_ASSERT(args[0].isString());
 
     JSAutoByteString locale(cx, args[0].toString());
     if (!locale)
         return false;
     UErrorCode status = U_ZERO_ERROR;
     UEnumeration* values = ucol_getKeywordValuesForLocale("co", locale.ptr(), false, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UEnumeration, uenum_close> toClose(values);
 
     uint32_t count = uenum_count(values, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
 
     RootedObject collations(cx, NewDenseEmptyArray(cx));
     if (!collations)
         return false;
 
     uint32_t index = 0;
@@ -412,25 +264,25 @@ js::intl_availableCollations(JSContext* 
     // ES2017 Intl, 10.2.3 Internal Slots.
     if (!DefineDataElement(cx, collations, index++, NullHandleValue))
         return false;
 
     RootedValue element(cx);
     for (uint32_t i = 0; i < count; i++) {
         const char* collation = uenum_next(values, nullptr, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return false;
         }
 
         // Per ECMA-402, 10.2.3, we don't include standard and search:
         // "The values 'standard' and 'search' must not be used as elements in
         // any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co
         // array."
-        if (equal(collation, "standard") || equal(collation, "search"))
+        if (StringsAreEqual(collation, "standard") || StringsAreEqual(collation, "search"))
             continue;
 
         // ICU returns old-style keyword values; map them to BCP 47 equivalents.
         JSString* jscollation = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("co", collation));
         if (!jscollation)
             return false;
         element = StringValue(jscollation);
         if (!DefineDataElement(cx, collations, index++, element))
@@ -445,17 +297,17 @@ js::intl_availableCollations(JSContext* 
  * Returns a new UCollator with the locale and collation options
  * of the given Collator.
  */
 static UCollator*
 NewUCollator(JSContext* cx, Handle<CollatorObject*> collator)
 {
     RootedValue value(cx);
 
-    RootedObject internals(cx, GetInternals(cx, collator));
+    RootedObject internals(cx, intl::GetInternalsObject(cx, collator));
     if (!internals)
         return nullptr;
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return nullptr;
     JSAutoByteString locale(cx, value.toString());
     if (!locale)
         return nullptr;
@@ -560,31 +412,31 @@ NewUCollator(JSContext* cx, Handle<Colla
             uCaseFirst = UCOL_LOWER_FIRST;
         } else {
             MOZ_ASSERT(StringEqualsAscii(caseFirst, "false"));
             uCaseFirst = UCOL_OFF;
         }
     }
 
     UErrorCode status = U_ZERO_ERROR;
-    UCollator* coll = ucol_open(icuLocale(locale.ptr()), &status);
+    UCollator* coll = ucol_open(IcuLocale(locale.ptr()), &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return nullptr;
     }
 
     ucol_setAttribute(coll, UCOL_STRENGTH, uStrength, &status);
     ucol_setAttribute(coll, UCOL_CASE_LEVEL, uCaseLevel, &status);
     ucol_setAttribute(coll, UCOL_ALTERNATE_HANDLING, uAlternate, &status);
     ucol_setAttribute(coll, UCOL_NUMERIC_COLLATION, uNumeric, &status);
     ucol_setAttribute(coll, UCOL_NORMALIZATION_MODE, uNormalization, &status);
     ucol_setAttribute(coll, UCOL_CASE_FIRST, uCaseFirst, &status);
     if (U_FAILURE(status)) {
         ucol_close(coll);
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return nullptr;
     }
 
     return coll;
 }
 
 static bool
 intl_CompareStrings(JSContext* cx, UCollator* coll, HandleString str1, HandleString str2,
@@ -692,43 +544,43 @@ js::SharedIntlData::ensureUpperCaseFirst
     if (!upperCaseFirstLocales.init()) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     UErrorCode status = U_ZERO_ERROR;
     UEnumeration* available = ucol_openAvailableLocales(&status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UEnumeration, uenum_close> toClose(available);
 
     RootedAtom locale(cx);
     while (true) {
         int32_t size;
         const char* rawLocale = uenum_next(available, &size, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return false;
         }
 
         if (rawLocale == nullptr)
             break;
 
         UCollator* collator = ucol_open(rawLocale, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return false;
         }
         ScopedICUObject<UCollator, ucol_close> toCloseCollator(collator);
 
         UColAttributeValue caseFirst = ucol_getAttribute(collator, UCOL_CASE_FIRST, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return false;
         }
 
         if (caseFirst != UCOL_UPPER_FIRST)
             continue;
 
         MOZ_ASSERT(size >= 0);
         locale = Atomize(cx, rawLocale, size_t(size));
@@ -866,18 +718,19 @@ NumberFormat(JSContext* cx, const CallAr
     numberFormat->setReservedSlot(NumberFormatObject::INTERNALS_SLOT, NullValue());
     numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
 
     RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv());
     HandleValue locales = args.get(0);
     HandleValue options = args.get(1);
 
     // Step 3.
-    return LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
-                                locales, options, DateTimeFormatOptions::Standard, args.rval());
+    return intl::LegacyInitializeObject(cx, numberFormat, cx->names().InitializeNumberFormat,
+                                        thisValue, locales, options,
+                                        DateTimeFormatOptions::Standard, args.rval());
 }
 
 static bool
 NumberFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return NumberFormat(cx, args, args.isConstructing());
 }
@@ -944,17 +797,17 @@ CreateNumberFormatPrototype(JSContext* c
 
 bool
 js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     RootedValue result(cx);
-    if (!intl_availableLocales(cx, unum_countAvailable, unum_getAvailable, &result))
+    if (!GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable, &result))
         return false;
     args.rval().set(result);
     return true;
 }
 
 bool
 js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -962,19 +815,19 @@ js::intl_numberingSystem(JSContext* cx, 
     MOZ_ASSERT(args.length() == 1);
     MOZ_ASSERT(args[0].isString());
 
     JSAutoByteString locale(cx, args[0].toString());
     if (!locale)
         return false;
 
     UErrorCode status = U_ZERO_ERROR;
-    UNumberingSystem* numbers = unumsys_open(icuLocale(locale.ptr()), &status);
+    UNumberingSystem* numbers = unumsys_open(IcuLocale(locale.ptr()), &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
 
     ScopedICUObject<UNumberingSystem, unumsys_close> toClose(numbers);
 
     const char* name = unumsys_getName(numbers);
     JSString* jsname = JS_NewStringCopyZ(cx, name);
     if (!jsname)
@@ -990,17 +843,17 @@ js::intl_numberingSystem(JSContext* cx, 
  * properties for PluralRules.
  *
  * This is similar to NewUNumberFormat but doesn't allow for currency or
  * percent types.
  */
 static UNumberFormat*
 NewUNumberFormatForPluralRules(JSContext* cx, Handle<PluralRulesObject*> pluralRules)
 {
-    RootedObject internals(cx, GetInternals(cx, pluralRules));
+    RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
     if (!internals)
        return nullptr;
 
     RootedValue value(cx);
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return nullptr;
     JSAutoByteString locale(cx, value.toString());
@@ -1036,19 +889,19 @@ NewUNumberFormatForPluralRules(JSContext
 
         if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits, &value))
             return nullptr;
         uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
     }
 
     UErrorCode status = U_ZERO_ERROR;
     UNumberFormat* nf =
-        unum_open(UNUM_DECIMAL, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status);
+        unum_open(UNUM_DECIMAL, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return nullptr;
     }
     ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
 
     if (uMinimumSignificantDigits != -1) {
         unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
         unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
         unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
@@ -1066,17 +919,17 @@ NewUNumberFormatForPluralRules(JSContext
  * Returns a new UNumberFormat with the locale and number formatting options
  * of the given NumberFormat.
  */
 static UNumberFormat*
 NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
 {
     RootedValue value(cx);
 
-    RootedObject internals(cx, GetInternals(cx, numberFormat));
+    RootedObject internals(cx, intl::GetInternalsObject(cx, numberFormat));
     if (!internals)
        return nullptr;
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return nullptr;
     JSAutoByteString locale(cx, value.toString());
     if (!locale)
         return nullptr;
@@ -1164,27 +1017,27 @@ NewUNumberFormat(JSContext* cx, Handle<N
         uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
     }
 
     if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
         return nullptr;
     uUseGrouping = value.toBoolean();
 
     UErrorCode status = U_ZERO_ERROR;
-    UNumberFormat* nf = unum_open(uStyle, nullptr, 0, icuLocale(locale.ptr()), nullptr, &status);
+    UNumberFormat* nf = unum_open(uStyle, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return nullptr;
     }
     ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
 
     if (uCurrency) {
         unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return nullptr;
         }
     }
     if (uMinimumSignificantDigits != -1) {
         unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
         unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
         unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
     } else {
@@ -1201,17 +1054,17 @@ NewUNumberFormat(JSContext* cx, Handle<N
 static JSString*
 PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double* x,
                        UFieldPositionIterator* fpositer)
 {
     // PartitionNumberPattern doesn't consider -0.0 to be negative.
     if (IsNegativeZero(*x))
         *x = 0.0;
 
-    return Call(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
+    return CallICU(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
         return unum_formatDoubleForFields(nf, *x, chars, size, fpositer, status);
     });
 }
 
 static bool
 intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
 {
     // Passing null for |fpositer| will just not compute partition information,
@@ -1296,17 +1149,17 @@ GetFieldTypeForNumberField(UNumberFormat
 
 static bool
 intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
 {
     UErrorCode status = U_ZERO_ERROR;
 
     UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
 
     MOZ_ASSERT(fpositer);
     ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(fpositer);
 
     RootedString overallResult(cx, PartitionNumberPattern(cx, nf, &x, fpositer));
     if (!overallResult)
@@ -1727,18 +1580,18 @@ DateTimeFormat(JSContext* cx, const Call
     dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT,
                                     PrivateValue(nullptr));
 
     RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
     HandleValue locales = args.get(0);
     HandleValue options = args.get(1);
 
     // Step 3.
-    return LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
-                                thisValue, locales, options, dtfOptions, args.rval());
+    return intl::LegacyInitializeObject(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
+                                        thisValue, locales, options, dtfOptions, args.rval());
 }
 
 static bool
 DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return DateTimeFormat(cx, args, args.isConstructing(), DateTimeFormatOptions::Standard);
 }
@@ -1832,34 +1685,34 @@ js::AddMozDateTimeFormatConstructor(JSCo
 
 bool
 js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     RootedValue result(cx);
-    if (!intl_availableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
+    if (!GetAvailableLocales(cx, udat_countAvailable, udat_getAvailable, &result))
         return false;
     args.rval().set(result);
     return true;
 }
 
 static bool
 DefaultCalendar(JSContext* cx, const JSAutoByteString& locale, MutableHandleValue rval)
 {
     UErrorCode status = U_ZERO_ERROR;
     UCalendar* cal = ucal_open(nullptr, 0, locale.ptr(), UCAL_DEFAULT, &status);
 
     // This correctly handles nullptr |cal| when opening failed.
     ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal);
 
     const char* calendar = ucal_getType(cal, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
 
     // ICU returns old-style keyword values; map them to BCP 47 equivalents
     JSString* str = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("ca", calendar));
     if (!str)
         return false;
 
@@ -1901,47 +1754,47 @@ js::intl_availableCalendars(JSContext* c
 
     if (!DefineDataElement(cx, calendars, index++, element))
         return false;
 
     // Now get the calendars that "would make a difference", i.e., not the default.
     UErrorCode status = U_ZERO_ERROR;
     UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UEnumeration, uenum_close> toClose(values);
 
     uint32_t count = uenum_count(values, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
 
     for (; count > 0; count--) {
         const char* calendar = uenum_next(values, nullptr, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return false;
         }
 
         // ICU returns old-style keyword values; map them to BCP 47 equivalents
         calendar = uloc_toUnicodeLocaleType("ca", calendar);
 
         JSString* jscalendar = JS_NewStringCopyZ(cx, calendar);
         if (!jscalendar)
             return false;
         element = StringValue(jscalendar);
         if (!DefineDataElement(cx, calendars, index++, element))
             return false;
 
         // ICU doesn't return calendar aliases, append them here.
         for (const auto& calendarAlias : calendarAliases) {
-            if (equal(calendar, calendarAlias.calendar)) {
+            if (StringsAreEqual(calendar, calendarAlias.calendar)) {
                 JSString* jscalendar = JS_NewStringCopyZ(cx, calendarAlias.alias);
                 if (!jscalendar)
                     return false;
                 element = StringValue(jscalendar);
                 if (!DefineDataElement(cx, calendars, index++, element))
                     return false;
             }
         }
@@ -2030,17 +1883,17 @@ js::SharedIntlData::TimeZoneHasher::matc
         return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars, lookup.length);
     return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
 }
 
 static bool
 IsLegacyICUTimeZone(const char* timeZone)
 {
     for (const auto& legacyTimeZone : js::timezone::legacyICUTimeZones) {
-        if (equal(timeZone, legacyTimeZone))
+        if (StringsAreEqual(timeZone, legacyTimeZone))
             return true;
     }
     return false;
 }
 
 bool
 js::SharedIntlData::ensureTimeZones(JSContext* cx)
 {
@@ -2054,27 +1907,27 @@ js::SharedIntlData::ensureTimeZones(JSCo
     if (!availableTimeZones.init()) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     UErrorCode status = U_ZERO_ERROR;
     UEnumeration* values = ucal_openTimeZones(&status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UEnumeration, uenum_close> toClose(values);
 
     RootedAtom timeZone(cx);
     while (true) {
         int32_t size;
         const char* rawTimeZone = uenum_next(values, &size, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return false;
         }
 
         if (rawTimeZone == nullptr)
             break;
 
         // Skip legacy ICU time zone names.
         if (IsLegacyICUTimeZone(rawTimeZone))
@@ -2286,17 +2139,17 @@ js::intl_canonicalizeTimeZone(JSContext*
     }
 
     AutoStableStringChars stableChars(cx);
     if (!stableChars.initTwoByte(cx, timeZone))
         return false;
 
     mozilla::Range<const char16_t> tzchars = stableChars.twoByteRange();
 
-    JSString* str = Call(cx, [&tzchars](UChar* chars, uint32_t size, UErrorCode* status) {
+    JSString* str = CallICU(cx, [&tzchars](UChar* chars, uint32_t size, UErrorCode* status) {
         return ucal_getCanonicalTimeZoneID(tzchars.begin().get(), tzchars.length(),
                                            chars, size, nullptr, status);
     });
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
@@ -2308,17 +2161,17 @@ js::intl_defaultTimeZone(JSContext* cx, 
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     // The current default might be stale, because JS::ResetTimeZone() doesn't
     // immediately update ICU's default time zone. So perform an update if
     // needed.
     js::ResyncICUDefaultTimeZone();
 
-    JSString* str = Call(cx, ucal_getDefaultTimeZone);
+    JSString* str = CallICU(cx, ucal_getDefaultTimeZone);
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 bool
@@ -2327,24 +2180,24 @@ js::intl_defaultTimeZoneOffset(JSContext
     MOZ_ASSERT(args.length() == 0);
 
     UErrorCode status = U_ZERO_ERROR;
     const UChar* uTimeZone = nullptr;
     int32_t uTimeZoneLength = 0;
     const char* rootLocale = "";
     UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UCalendar, ucal_close> toClose(cal);
 
     int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
 
     args.rval().setInt32(offset);
     return true;
 }
 
 bool
@@ -2362,17 +2215,17 @@ js::intl_isDefaultTimeZone(JSContext* cx
     }
 
     // The current default might be stale, because JS::ResetTimeZone() doesn't
     // immediately update ICU's default time zone. So perform an update if
     // needed.
     js::ResyncICUDefaultTimeZone();
 
     Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
-    int32_t size = Call(cx, ucal_getDefaultTimeZone, chars);
+    int32_t size = CallICU(cx, ucal_getDefaultTimeZone, chars);
     if (size < 0)
         return false;
 
     JSLinearString* str = args[0].toString()->ensureLinear(cx);
     if (!str)
         return false;
 
     bool equals;
@@ -2403,27 +2256,28 @@ js::intl_patternForSkeleton(JSContext* c
 
     AutoStableStringChars skeleton(cx);
     if (!skeleton.initTwoByte(cx, args[1].toString()))
         return false;
 
     mozilla::Range<const char16_t> skelChars = skeleton.twoByteRange();
 
     UErrorCode status = U_ZERO_ERROR;
-    UDateTimePatternGenerator* gen = udatpg_open(icuLocale(locale.ptr()), &status);
+    UDateTimePatternGenerator* gen = udatpg_open(IcuLocale(locale.ptr()), &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UDateTimePatternGenerator, udatpg_close> toClose(gen);
 
-    JSString* str = Call(cx, [gen, &skelChars](UChar* chars, uint32_t size, UErrorCode* status) {
-        return udatpg_getBestPattern(gen, skelChars.begin().get(), skelChars.length(),
-                                     chars, size, status);
-    });
+    JSString* str =
+        CallICU(cx, [gen, &skelChars](UChar* chars, uint32_t size, UErrorCode* status) {
+            return udatpg_getBestPattern(gen, skelChars.begin().get(), skelChars.length(),
+                                         chars, size, status);
+        });
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 bool
@@ -2476,26 +2330,26 @@ js::intl_patternForStyle(JSContext* cx, 
 
     AutoStableStringChars timeZone(cx);
     if (!timeZone.initTwoByte(cx, args[3].toString()))
         return false;
 
     mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
 
     UErrorCode status = U_ZERO_ERROR;
-    UDateFormat* df = udat_open(timeStyle, dateStyle, icuLocale(locale.ptr()),
+    UDateFormat* df = udat_open(timeStyle, dateStyle, IcuLocale(locale.ptr()),
                                 timeZoneChars.begin().get(), timeZoneChars.length(),
                                 nullptr, -1, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UDateFormat, udat_close> toClose(df);
 
-    JSString* str = Call(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) {
+    JSString* str = CallICU(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) {
         return udat_toPattern(df, false, chars, size, status);
     });
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
@@ -2503,17 +2357,17 @@ js::intl_patternForStyle(JSContext* cx, 
  * Returns a new UDateFormat with the locale and date-time formatting options
  * of the given DateTimeFormat.
  */
 static UDateFormat*
 NewUDateFormat(JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat)
 {
     RootedValue value(cx);
 
-    RootedObject internals(cx, GetInternals(cx, dateTimeFormat));
+    RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat));
     if (!internals)
        return nullptr;
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return nullptr;
     JSAutoByteString locale(cx, value.toString());
     if (!locale)
         return nullptr;
@@ -2537,21 +2391,21 @@ NewUDateFormat(JSContext* cx, Handle<Dat
     AutoStableStringChars pattern(cx);
     if (!pattern.initTwoByte(cx, value.toString()))
         return nullptr;
 
     mozilla::Range<const char16_t> patternChars = pattern.twoByteRange();
 
     UErrorCode status = U_ZERO_ERROR;
     UDateFormat* df =
-        udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()),
+        udat_open(UDAT_PATTERN, UDAT_PATTERN, IcuLocale(locale.ptr()),
                   timeZoneChars.begin().get(), timeZoneChars.length(),
                   patternChars.begin().get(), patternChars.length(), &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return nullptr;
     }
 
     // ECMAScript requires the Gregorian calendar to be used from the beginning
     // of ECMAScript time.
     UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(df));
     ucal_setGregorianChange(cal, StartOfTime, &status);
 
@@ -2560,17 +2414,17 @@ NewUDateFormat(JSContext* cx, Handle<Dat
     return df;
 }
 
 static bool
 intl_FormatDateTime(JSContext* cx, UDateFormat* df, ClippedTime x, MutableHandleValue result)
 {
     MOZ_ASSERT(x.isValid());
 
-    JSString* str = Call(cx, [df, x](UChar* chars, int32_t size, UErrorCode* status) {
+    JSString* str = CallICU(cx, [df, x](UChar* chars, int32_t size, UErrorCode* status) {
         return udat_format(df, x.toDouble(), chars, size, nullptr, status);
     });
     if (!str)
         return false;
 
     result.setString(str);
     return true;
 }
@@ -2665,23 +2519,23 @@ static bool
 intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, ClippedTime x,
                            MutableHandleValue result)
 {
     MOZ_ASSERT(x.isValid());
 
     UErrorCode status = U_ZERO_ERROR;
     UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(fpositer);
 
     RootedString overallResult(cx);
-    overallResult = Call(cx, [df, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
+    overallResult = CallICU(cx, [df, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
         return udat_formatForFields(df, x.toDouble(), chars, size, fpositer, status);
     });
     if (!overallResult)
         return false;
 
     RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
     if (!partsArray)
         return false;
@@ -2874,18 +2728,21 @@ PluralRules(JSContext* cx, unsigned argc
 
     pluralRules->setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
     pluralRules->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
 
     HandleValue locales = args.get(0);
     HandleValue options = args.get(1);
 
     // Step 3.
-    if (!IntlInitialize(cx, pluralRules, cx->names().InitializePluralRules, locales, options))
+    if (!intl::InitializeObject(cx, pluralRules, cx->names().InitializePluralRules, locales,
+                                options))
+    {
         return false;
+    }
 
     args.rval().setObject(*pluralRules);
     return true;
 }
 
 void
 PluralRulesObject::finalize(FreeOp* fop, JSObject* obj)
 {
@@ -2929,17 +2786,17 @@ bool
 js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     RootedValue result(cx);
     // We're going to use ULocale availableLocales as per ICU recommendation:
     // https://ssl.icu-project.org/trac/ticket/12756
-    if (!intl_availableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
+    if (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
         return false;
     args.rval().set(result);
     return true;
 }
 
 bool
 js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -2951,17 +2808,17 @@ js::intl_SelectPluralRule(JSContext* cx,
     double x = args[1].toNumber();
 
     UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
     if (!nf)
         return false;
 
     ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
 
-    RootedObject internals(cx, GetInternals(cx, pluralRules));
+    RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
     if (!internals)
         return false;
 
     RootedValue value(cx);
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return false;
     JSAutoByteString locale(cx, value.toString());
@@ -2982,24 +2839,24 @@ js::intl_SelectPluralRule(JSContext* cx,
         } else {
             MOZ_ASSERT(StringEqualsAscii(type, "ordinal"));
             category = UPLURAL_TYPE_ORDINAL;
         }
     }
 
     // TODO: Cache UPluralRules in PluralRulesObject::UPluralRulesSlot.
     UErrorCode status = U_ZERO_ERROR;
-    UPluralRules* pr = uplrules_openForType(icuLocale(locale.ptr()), category, &status);
+    UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
 
-    JSString* str = Call(cx, [pr, x, nf](UChar* chars, int32_t size, UErrorCode* status) {
+    JSString* str = CallICU(cx, [pr, x, nf](UChar* chars, int32_t size, UErrorCode* status) {
         return uplrules_selectWithFormat(pr, x, nf, chars, size, status);
     });
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
@@ -3022,42 +2879,42 @@ js::intl_GetPluralCategories(JSContext* 
     if (StringEqualsAscii(type, "cardinal")) {
         category = UPLURAL_TYPE_CARDINAL;
     } else {
         MOZ_ASSERT(StringEqualsAscii(type, "ordinal"));
         category = UPLURAL_TYPE_ORDINAL;
     }
 
     UErrorCode status = U_ZERO_ERROR;
-    UPluralRules* pr = uplrules_openForType(icuLocale(locale.ptr()), category, &status);
+    UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
 
     UEnumeration* ue = uplrules_getKeywords(pr, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UEnumeration, uenum_close> closeEnum(ue);
 
     RootedObject res(cx, NewDenseEmptyArray(cx));
     if (!res)
         return false;
 
     RootedValue element(cx);
     uint32_t i = 0;
 
     do {
         int32_t catSize;
         const char* cat = uenum_next(ue, &catSize, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return false;
         }
 
         if (!cat)
             break;
 
         MOZ_ASSERT(catSize >= 0);
         JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
@@ -3147,18 +3004,21 @@ RelativeTimeFormat(JSContext* cx, unsign
 
     relativeTimeFormat->setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue());
     relativeTimeFormat->setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
 
     HandleValue locales = args.get(0);
     HandleValue options = args.get(1);
 
     // Step 3.
-    if (!IntlInitialize(cx, relativeTimeFormat, cx->names().InitializeRelativeTimeFormat, locales, options))
+    if (!intl::InitializeObject(cx, relativeTimeFormat, cx->names().InitializeRelativeTimeFormat,
+                                locales, options))
+    {
         return false;
+    }
 
     args.rval().setObject(*relativeTimeFormat);
     return true;
 }
 
 void
 RelativeTimeFormatObject::finalize(FreeOp* fop, JSObject* obj)
 {
@@ -3233,17 +3093,17 @@ bool
 js::intl_RelativeTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     RootedValue result(cx);
     // We're going to use ULocale availableLocales as per ICU recommendation:
     // https://ssl.icu-project.org/trac/ticket/12756
-    if (!intl_availableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
+    if (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
         return false;
     args.rval().set(result);
     return true;
 }
 
 enum class RelativeTimeType
 {
     /**
@@ -3262,17 +3122,17 @@ js::intl_FormatRelativeTime(JSContext* c
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 3);
 
     RootedObject relativeTimeFormat(cx, &args[0].toObject());
 
     double t = args[1].toNumber();
 
-    RootedObject internals(cx, GetInternals(cx, relativeTimeFormat));
+    RootedObject internals(cx, intl::GetInternalsObject(cx, relativeTimeFormat));
     if (!internals)
         return false;
 
     RootedValue value(cx);
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return false;
     JSAutoByteString locale(cx, value.toString());
@@ -3343,31 +3203,34 @@ js::intl_FormatRelativeTime(JSContext* c
 
     // ICU doesn't handle -0 well: work around this by converting it to +0.
     // See: http://bugs.icu-project.org/trac/ticket/12936
     if (IsNegativeZero(t))
         t = +0.0;
 
     UErrorCode status = U_ZERO_ERROR;
     URelativeDateTimeFormatter* rtf =
-        ureldatefmt_open(icuLocale(locale.ptr()), nullptr, relDateTimeStyle,
+        ureldatefmt_open(IcuLocale(locale.ptr()), nullptr, relDateTimeStyle,
                          UDISPCTX_CAPITALIZATION_FOR_STANDALONE, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
 
     ScopedICUObject<URelativeDateTimeFormatter, ureldatefmt_close> closeRelativeTimeFormat(rtf);
 
-    JSString* str = Call(cx, [rtf, t, relDateTimeUnit, relDateTimeType](UChar* chars, int32_t size, UErrorCode* status) {
-        auto fmt = relDateTimeType == RelativeTimeType::Text
-                   ? ureldatefmt_format
-                   : ureldatefmt_formatNumeric;
-        return fmt(rtf, t, relDateTimeUnit, chars, size, status);
-    });
+    JSString* str =
+        CallICU(cx, [rtf, t, relDateTimeUnit, relDateTimeType](UChar* chars, int32_t size,
+                                                               UErrorCode* status)
+        {
+            auto fmt = relDateTimeType == RelativeTimeType::Text
+                       ? ureldatefmt_format
+                       : ureldatefmt_formatNumeric;
+            return fmt(rtf, t, relDateTimeUnit, chars, size, status);
+        });
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 
@@ -3411,17 +3274,17 @@ js::intl_toLocaleLowerCase(JSContext* cx
 
     RootedString string(cx, args[0].toString());
 
     const char* locale = CaseMappingLocale(cx, args[1].toString());
     if (!locale)
         return false;
 
     // Call String.prototype.toLowerCase() for language independent casing.
-    if (equal(locale, "")) {
+    if (StringsAreEqual(locale, "")) {
         JSString* str = js::StringToLowerCase(cx, string);
         if (!str)
             return false;
 
         args.rval().setString(str);
         return true;
     }
 
@@ -3429,17 +3292,17 @@ js::intl_toLocaleLowerCase(JSContext* cx
     if (!inputChars.initTwoByte(cx, string))
         return false;
     mozilla::Range<const char16_t> input = inputChars.twoByteRange();
 
     // Maximum case mapping length is three characters.
     static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
                   "Case conversion doesn't overflow int32_t indices");
 
-    JSString* str = Call(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
+    JSString* str = CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
         return u_strToLower(chars, size, input.begin().get(), input.length(), locale, status);
     });
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
@@ -3454,17 +3317,17 @@ js::intl_toLocaleUpperCase(JSContext* cx
 
     RootedString string(cx, args[0].toString());
 
     const char* locale = CaseMappingLocale(cx, args[1].toString());
     if (!locale)
         return false;
 
     // Call String.prototype.toUpperCase() for language independent casing.
-    if (equal(locale, "")) {
+    if (StringsAreEqual(locale, "")) {
         JSString* str = js::StringToUpperCase(cx, string);
         if (!str)
             return false;
 
         args.rval().setString(str);
         return true;
     }
 
@@ -3472,17 +3335,17 @@ js::intl_toLocaleUpperCase(JSContext* cx
     if (!inputChars.initTwoByte(cx, string))
         return false;
     mozilla::Range<const char16_t> input = inputChars.twoByteRange();
 
     // Maximum case mapping length is three characters.
     static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
                   "Case conversion doesn't overflow int32_t indices");
 
-    JSString* str = Call(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
+    JSString* str = CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
         return u_strToUpper(chars, size, input.begin().get(), input.length(), locale, status);
     });
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
@@ -3500,17 +3363,17 @@ js::intl_GetCalendarInfo(JSContext* cx, 
     if (!locale)
         return false;
 
     UErrorCode status = U_ZERO_ERROR;
     const UChar* uTimeZone = nullptr;
     int32_t uTimeZoneLength = 0;
     UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.ptr(), UCAL_DEFAULT, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UCalendar, ucal_close> toClose(cal);
 
     RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
     if (!info)
         return false;
 
@@ -3523,27 +3386,27 @@ js::intl_GetCalendarInfo(JSContext* cx, 
 
     int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
     v.setInt32(minDays);
     if (!DefineDataProperty(cx, info, cx->names().minDays, v))
         return false;
 
     UCalendarWeekdayType prevDayType = ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
 
     RootedValue weekendStart(cx), weekendEnd(cx);
 
     for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) {
         UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i);
         UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status);
         if (U_FAILURE(status)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            intl::ReportInternalError(cx);
             return false;
         }
 
         if (prevDayType != type) {
             switch (type) {
               case UCAL_WEEKDAY:
                 // If the first Weekday after Weekend is Sunday (1),
                 // then the last Weekend day is Saturday (7).
@@ -3553,17 +3416,17 @@ js::intl_GetCalendarInfo(JSContext* cx, 
               case UCAL_WEEKEND:
                 weekendStart.setInt32(i);
                 break;
               case UCAL_WEEKEND_ONSET:
               case UCAL_WEEKEND_CEASE:
                 // At the time this code was added, ICU apparently never behaves this way,
                 // so just throw, so that users will report a bug and we can decide what to
                 // do.
-                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+                intl::ReportInternalError(cx);
                 return false;
               default:
                 break;
             }
         }
 
         prevDayType = type;
     }
@@ -3793,17 +3656,19 @@ ComputeSingleDisplayName(JSContext* cx, 
         }
 
         // This part must be the final part with no trailing data.
         if (iter != end) {
             ReportBadKey(cx, pattern);
             return nullptr;
         }
 
-        return Call(cx, [fmt, symbolType, index](UChar* chars, int32_t size, UErrorCode* status) {
+        return CallICU(cx, [fmt, symbolType, index](UChar* chars, int32_t size,
+                                                    UErrorCode* status)
+        {
             return udat_getSymbols(fmt, symbolType, index, chars, size, status);
         });
     }
 
     ReportBadKey(cx, pattern);
     return nullptr;
 }
 
@@ -3844,29 +3709,29 @@ js::intl_ComputeDisplayNames(JSContext* 
     // 4. Let result be ArrayCreate(0).
     RootedArrayObject result(cx, NewDenseUnallocatedArray(cx, keys->length()));
     if (!result)
         return false;
 
     UErrorCode status = U_ZERO_ERROR;
 
     UDateFormat* fmt =
-        udat_open(UDAT_DEFAULT, UDAT_DEFAULT, icuLocale(locale.ptr()),
+        udat_open(UDAT_DEFAULT, UDAT_DEFAULT, IcuLocale(locale.ptr()),
         nullptr, 0, nullptr, 0, &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);
 
     // UDateTimePatternGenerator will be needed for translations of date and
     // time fields like "month", "week", "day" etc.
-    UDateTimePatternGenerator* dtpg = udatpg_open(icuLocale(locale.ptr()), &status);
+    UDateTimePatternGenerator* dtpg = udatpg_open(IcuLocale(locale.ptr()), &status);
     if (U_FAILURE(status)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
 
     // 5. For each element of keys,
     RootedString keyValStr(cx);
     RootedValue v(cx);
     for (uint32_t i = 0; i < keys->length(); i++) {
@@ -3911,17 +3776,17 @@ js::intl_GetLocaleInfo(JSContext* cx, un
 
     RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
     if (!info)
         return false;
 
     if (!DefineDataProperty(cx, info, cx->names().locale, args[0]))
         return false;
 
-    bool rtl = uloc_isRightToLeft(icuLocale(locale.ptr()));
+    bool rtl = uloc_isRightToLeft(IcuLocale(locale.ptr()));
 
     RootedValue dir(cx, StringValue(rtl ? cx->names().rtl : cx->names().ltr));
 
     if (!DefineDataProperty(cx, info, cx->names().direction, dir))
         return false;
 
     args.rval().setObject(*info);
     return true;
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/intl/CommonFunctions.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Operations used to implement multiple Intl.* classes. */
+
+#include "builtin/intl/CommonFunctions.h"
+
+#include "mozilla/Assertions.h"
+
+#include "jscntxt.h"
+#include "jsfriendapi.h" // for GetErrorMessage, JSMSG_INTERNAL_INTL_ERROR
+#include "jsobj.h"
+
+#include "js/Value.h"
+#include "vm/SelfHosting.h"
+#include "vm/Stack.h"
+
+#include "jsobjinlines.h"
+
+bool
+js::intl::InitializeObject(JSContext* cx, JS::Handle<JSObject*> obj,
+                           JS::Handle<PropertyName*> initializer, JS::Handle<JS::Value> locales,
+                           JS::Handle<JS::Value> options)
+{
+    FixedInvokeArgs<3> args(cx);
+
+    args[0].setObject(*obj);
+    args[1].set(locales);
+    args[2].set(options);
+
+    RootedValue ignored(cx);
+    if (!CallSelfHostedFunction(cx, initializer, JS::NullHandleValue, args, &ignored))
+        return false;
+
+    MOZ_ASSERT(ignored.isUndefined(),
+               "Unexpected return value from non-legacy Intl object initializer");
+    return true;
+}
+
+bool
+js::intl::LegacyInitializeObject(JSContext* cx, JS::Handle<JSObject*> obj,
+                                 JS::Handle<PropertyName*> initializer,
+                                 JS::Handle<JS::Value> thisValue, JS::Handle<JS::Value> locales,
+                                 JS::Handle<JS::Value> options, DateTimeFormatOptions dtfOptions,
+                                 JS::MutableHandle<JS::Value> result)
+{
+    FixedInvokeArgs<5> args(cx);
+
+    args[0].setObject(*obj);
+    args[1].set(thisValue);
+    args[2].set(locales);
+    args[3].set(options);
+    args[4].setBoolean(dtfOptions == DateTimeFormatOptions::EnableMozExtensions);
+
+    if (!CallSelfHostedFunction(cx, initializer, NullHandleValue, args, result))
+        return false;
+
+    MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
+    return true;
+}
+
+JSObject*
+js::intl::GetInternalsObject(JSContext* cx, JS::Handle<JSObject*> obj)
+{
+    FixedInvokeArgs<1> args(cx);
+
+    args[0].setObject(*obj);
+
+    RootedValue v(cx);
+    if (!js::CallSelfHostedFunction(cx, cx->names().getInternals, NullHandleValue, args, &v))
+        return nullptr;
+
+    return &v.toObject();
+}
+
+void
+js::intl::ReportInternalError(JSContext* cx)
+{
+    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+}
+
+bool
+js::intl::GetAvailableLocales(JSContext* cx, CountAvailable countAvailable,
+                              GetAvailable getAvailable, JS::MutableHandle<JS::Value> result)
+{
+    RootedObject locales(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
+    if (!locales)
+        return false;
+
+#if ENABLE_INTL_API
+    RootedAtom a(cx);
+    uint32_t count = countAvailable();
+    for (uint32_t i = 0; i < count; i++) {
+        const char* locale = getAvailable(i);
+        auto lang = DuplicateString(cx, locale);
+        if (!lang)
+            return false;
+        char* p;
+        while ((p = strchr(lang.get(), '_')))
+            *p = '-';
+        a = Atomize(cx, lang.get(), strlen(lang.get()));
+        if (!a)
+            return false;
+        if (!DefineDataProperty(cx, locales, a->asPropertyName(), TrueHandleValue))
+            return false;
+    }
+#endif
+
+    result.setObject(*locales);
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/intl/CommonFunctions.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef builtin_intl_CommonFunctions_h
+#define builtin_intl_CommonFunctions_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/TypeTraits.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "builtin/intl/ICUStubs.h"
+#include "js/RootingAPI.h"
+#include "js/Vector.h"
+#include "vm/String.h"
+
+namespace JS { class Value; }
+
+class JSObject;
+
+namespace js {
+
+namespace intl {
+
+/**
+ * Initialize a new Intl.* object using the named self-hosted function.
+ */
+extern bool
+InitializeObject(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<PropertyName*> initializer,
+                 JS::Handle<JS::Value> locales, JS::Handle<JS::Value> options);
+
+enum class DateTimeFormatOptions
+{
+    Standard,
+    EnableMozExtensions,
+};
+
+/**
+ * Initialize an existing object as an Intl.* object using the named
+ * self-hosted function.  This is only for a few old Intl.* constructors, for
+ * legacy reasons -- new ones should use the function above instead.
+ */
+extern bool
+LegacyInitializeObject(JSContext* cx, JS::Handle<JSObject*> obj,
+                       JS::Handle<PropertyName*> initializer, JS::Handle<JS::Value> thisValue,
+                       JS::Handle<JS::Value> locales, JS::Handle<JS::Value> options,
+                       DateTimeFormatOptions dtfOptions, JS::MutableHandle<JS::Value> result);
+
+/**
+ * Returns the object holding the internal properties for obj.
+ */
+extern JSObject*
+GetInternalsObject(JSContext* cx, JS::Handle<JSObject*> obj);
+
+/** Report an Intl internal error not directly tied to a spec step. */
+extern void
+ReportInternalError(JSContext* cx);
+
+static inline bool
+StringsAreEqual(const char* s1, const char* s2)
+{
+    return !strcmp(s1, s2);
+}
+
+static inline const char*
+IcuLocale(const char* locale)
+{
+    if (StringsAreEqual(locale, "und"))
+        return ""; // ICU root locale
+
+    return locale;
+}
+
+// Starting with ICU 59, UChar defaults to char16_t.
+static_assert(mozilla::IsSame<UChar, char16_t>::value,
+              "SpiderMonkey doesn't support redefining UChar to a different type");
+
+// The inline capacity we use for a Vector<char16_t>.  Use this to ensure that
+// our uses of ICU string functions, below and elsewhere, will try to fill the
+// buffer's entire inline capacity before growing it and heap-allocating.
+constexpr size_t INITIAL_CHAR_BUFFER_SIZE = 32;
+
+template <typename ICUStringFunction, size_t InlineCapacity>
+static int32_t
+CallICU(JSContext* cx, const ICUStringFunction& strFn, Vector<char16_t, InlineCapacity>& chars)
+{
+    MOZ_ASSERT(chars.length() == 0);
+    MOZ_ALWAYS_TRUE(chars.resize(InlineCapacity));
+
+    UErrorCode status = U_ZERO_ERROR;
+    int32_t size = strFn(chars.begin(), InlineCapacity, &status);
+    if (status == U_BUFFER_OVERFLOW_ERROR) {
+        MOZ_ASSERT(size >= 0);
+        if (!chars.resize(size_t(size)))
+            return -1;
+        status = U_ZERO_ERROR;
+        strFn(chars.begin(), size, &status);
+    }
+    if (U_FAILURE(status)) {
+        ReportInternalError(cx);
+        return -1;
+    }
+
+    MOZ_ASSERT(size >= 0);
+    return size;
+}
+
+template <typename ICUStringFunction>
+static JSString*
+CallICU(JSContext* cx, const ICUStringFunction& strFn)
+{
+    Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+
+    int32_t size = CallICU(cx, strFn, chars);
+    if (size < 0)
+        return nullptr;
+
+    return NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
+}
+
+// CountAvailable and GetAvailable describe the signatures used for ICU API
+// to determine available locales for various functionality.
+using CountAvailable = int32_t (*)();
+using GetAvailable = const char* (*)(int32_t localeIndex);
+
+/**
+ * Return an object whose own property names are the locales indicated as
+ * available by |countAvailable| that provides an overall count, and by
+ * |getAvailable| that when called passing a number less than that count,
+ * returns the corresponding locale as a borrowed string.  For example:
+ *
+ *   RootedValue v(cx);
+ *   if (!GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable, &v))
+ *       return false;
+ */
+extern bool
+GetAvailableLocales(JSContext* cx, CountAvailable countAvailable, GetAvailable getAvailable,
+                    JS::MutableHandle<JS::Value> result);
+
+} // namespace intl
+
+} // namespace js
+
+#endif /* builtin_intl_CommonFunctions_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -155,16 +155,17 @@ EXPORTS.js += [
     '../public/WeakMapPtr.h',
 ]
 
 UNIFIED_SOURCES += [
     'builtin/AtomicsObject.cpp',
     'builtin/DataViewObject.cpp',
     'builtin/Eval.cpp',
     'builtin/Intl.cpp',
+    'builtin/intl/CommonFunctions.cpp',
     'builtin/MapObject.cpp',
     'builtin/ModuleObject.cpp',
     'builtin/Object.cpp',
     'builtin/Profilers.cpp',
     'builtin/Promise.cpp',
     'builtin/Reflect.cpp',
     'builtin/ReflectParse.cpp',
     'builtin/SIMD.cpp',