Bug 1291407 - Add ListFormat API. draft
authorZibi Braniecki <gandalf@mozilla.com>
Sun, 27 Nov 2016 16:45:16 -0800
changeset 444760 1f40b3f95bf388a55d9cb4c2dc2146b660d14adf
parent 444732 76d3407b49c95aa16114e7e3d71193f8bfbe8958
child 538390 0778f2ccdd4b670eadd8d2fc5a3cd26342e8c347
push id37365
push userzbraniecki@mozilla.com
push dateMon, 28 Nov 2016 20:21:38 +0000
bugs1291407
milestone53.0a1
Bug 1291407 - Add ListFormat API. MozReview-Commit-ID: GjndXWd2pJL
js/public/Class.h
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/shell/js.cpp
js/src/vm/CommonPropertyNames.h
js/src/vm/GlobalObject.h
js/src/vm/SelfHosting.cpp
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -731,17 +731,17 @@ struct JSClass {
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
 #define JSCLASS_GLOBAL_SLOT_COUNT                                             \
-    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 39)
+    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 40)
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                                    \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
 
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -24,16 +24,17 @@
 
 #include "builtin/IntlTimeZoneData.h"
 #if ENABLE_INTL_API
 #include "unicode/ucal.h"
 #include "unicode/ucol.h"
 #include "unicode/udat.h"
 #include "unicode/udatpg.h"
 #include "unicode/uenum.h"
+#include "unicode/ulistformatter.h"
 #include "unicode/unum.h"
 #include "unicode/unumsys.h"
 #include "unicode/ustring.h"
 #endif
 #include "vm/DateTime.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/Stack.h"
@@ -2826,16 +2827,265 @@ js::intl_FormatDateTime(JSContext* cx, u
     if (!isDateTimeFormatInstance)
         udat_close(df);
     if (!success)
         return false;
     args.rval().set(result);
     return true;
 }
 
+/**************** ListFormat *****************/
+
+static void listFormat_finalize(FreeOp* fop, JSObject* obj);
+
+static const uint32_t ULIST_FORMAT_SLOT = 0;
+static const uint32_t LIST_FORMAT_SLOTS_COUNT = 1;
+
+static const ClassOps ListFormatClassOps = {
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    listFormat_finalize
+};
+
+static const Class ListFormatClass = {
+    js_Object_str,
+    JSCLASS_HAS_RESERVED_SLOTS(LIST_FORMAT_SLOTS_COUNT) |
+    JSCLASS_FOREGROUND_FINALIZE,
+    &ListFormatClassOps
+};
+
+#if JS_HAS_TOSOURCE
+static bool
+listFormat_toSource(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    args.rval().setString(cx->names().ListFormat);
+    return true;
+}
+#endif
+
+static const JSFunctionSpec listFormat_static_methods[] = {
+    JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_ListFormat_supportedLocalesOf", 1, 0),
+    JS_FS_END
+};
+
+static const JSFunctionSpec listFormat_methods[] = {
+    JS_SELF_HOSTED_FN("resolvedOptions", "Intl_ListFormat_resolvedOptions", 0, 0),
+    JS_SELF_HOSTED_FN("format", "Intl_ListFormat_format", 1, 0),
+#if JS_HAS_TOSOURCE
+    JS_FN(js_toSource_str, listFormat_toSource, 0, 0),
+#endif
+    JS_FS_END
+};
+
+static bool
+ListFormat(JSContext* cx, const CallArgs& args, bool construct)
+{
+    RootedObject obj(cx);
+
+    if (!construct) {
+        JSObject* intl = cx->global()->getOrCreateIntlObject(cx);
+        if (!intl)
+            return false;
+        RootedValue self(cx, args.thisv());
+        if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
+            obj = ToObject(cx, self);
+            if (!obj)
+                return false;
+
+            bool extensible;
+            if (!IsExtensible(cx, obj, &extensible))
+                return false;
+            if (!extensible)
+                return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
+        } else {
+            construct = true;
+        }
+    }
+    if (construct) {
+        RootedObject proto(cx, cx->global()->getOrCreateListFormatPrototype(cx));
+        if (!proto)
+            return false;
+        obj = NewObjectWithGivenProto(cx, &ListFormatClass, proto);
+        if (!obj)
+            return false;
+
+        obj->as<NativeObject>().setReservedSlot(ULIST_FORMAT_SLOT, PrivateValue(nullptr));
+    }
+
+    RootedValue locales(cx, args.get(0));
+    RootedValue options(cx, args.get(1));
+
+    if (!IntlInitialize(cx, obj, cx->names().InitializeListFormat, locales, options))
+        return false;
+
+    args.rval().setObject(*obj);
+    return true;
+}
+
+static bool
+ListFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return ListFormat(cx, args, args.isConstructing());
+}
+
+bool
+js::intl_ListFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+    return ListFormat(cx, args, true);
+}
+
+static void
+listFormat_finalize(FreeOp* fop, JSObject* obj)
+{
+    MOZ_ASSERT(fop->onMainThread());
+
+    // This is-undefined check shouldn't be necessary, but for internal
+    // brokenness in object allocation code.  For the moment, hack around it by
+    // explicitly guarding against the possibility of the reserved slot not
+    // containing a private.  See bug 949220.
+    const Value& slot = obj->as<NativeObject>().getReservedSlot(ULIST_FORMAT_SLOT);
+    if (!slot.isUndefined()) {
+        if (UListFormatter* lf = static_cast<UListFormatter*>(slot.toPrivate()))
+            ulistfmt_close(lf);
+    }
+}
+
+static JSObject*
+CreateListFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+{
+    RootedFunction ctor(cx);
+    ctor = global->createConstructor(cx, &ListFormat, cx->names().ListFormat, 0);
+    if (!ctor)
+        return nullptr;
+
+    RootedNativeObject proto(cx, global->createBlankPrototype(cx, &ListFormatClass));
+    if (!proto)
+        return nullptr;
+    proto->setReservedSlot(ULIST_FORMAT_SLOT, PrivateValue(nullptr));
+
+    if (!LinkConstructorAndPrototype(cx, ctor, proto))
+        return nullptr;
+
+    if (!JS_DefineFunctions(cx, ctor, listFormat_static_methods))
+        return nullptr;
+
+    if (!JS_DefineFunctions(cx, proto, listFormat_methods))
+        return nullptr;
+
+    RootedValue options(cx);
+    if (!CreateDefaultOptions(cx, &options))
+        return nullptr;
+
+    if (!IntlInitialize(cx, proto, cx->names().InitializeListFormat, UndefinedHandleValue,
+                        options))
+    {
+        return nullptr;
+    }
+
+    RootedValue ctorValue(cx, ObjectValue(*ctor));
+    if (!DefineProperty(cx, Intl, cx->names().ListFormat, ctorValue, nullptr, nullptr, 0))
+        return nullptr;
+
+    return proto;
+}
+
+bool
+js::intl_ListFormat_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:
+    // [http://bugs.icu-project.org/trac/ticket/12756].
+    if (!intl_availableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
+        return false;
+    args.rval().set(result);
+    return true;
+}
+
+bool
+js::intl_FormatListFormat(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedObject listFormat(cx, &args[0].toObject());
+    RootedArrayObject elements(cx, &args[1].toObject().as<ArrayObject>());
+    if (!elements)
+        return false;
+
+
+    RootedObject internals(cx, GetInternals(cx, listFormat));
+    if (!internals)
+        return false;
+
+    RootedValue value(cx);
+
+    if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
+        return false;
+    JSAutoByteString locale(cx, value.toString());
+    if (!locale)
+        return false;
+
+    if (!GetProperty(cx, internals, internals, cx->names().type, &value))
+        return false;
+    JSAutoByteString type(cx, value.toString());
+    if (!type)
+        return false;
+
+    UErrorCode status = U_ZERO_ERROR;
+
+    UListFormatter* lf = ulistfmt_open(icuLocale(locale.ptr()), &status);
+
+    ScopedICUObject<UListFormatter, ulistfmt_close> closeListFormat(lf);
+
+    int32_t stringCount = elements->length();
+    const UChar* strings[stringCount];
+    int32_t stringLengths[stringCount];
+
+    RootedValue elemValue(cx);
+    RootedString elemValStr(cx);
+
+    for (int32_t i = 0; i < stringCount; i++) {
+        if (!GetElement(cx, elements, elements, i, &elemValue))
+            return false;
+
+        AutoStableStringChars stableChars(cx);
+        stableChars.initTwoByte(cx, elemValue.toString());
+        mozilla::Range<const char16_t> pchars = stableChars.twoByteRange();
+
+        strings[i] = Char16ToUChar(pchars.begin().get());
+        stringLengths[i] = pchars.length();
+        printf("%d - %d\n", i, int(pchars.length()));
+    }
+
+    Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+    int32_t resCapacity = INITIAL_CHAR_BUFFER_SIZE;
+
+    int size = ulistfmt_format(lf, strings, stringLengths, stringCount,
+                    Char16ToUChar(chars.begin()), resCapacity, &status);
+
+
+    JSString* word = NewStringCopyN<CanGC>(cx,
+        chars.begin(), size);
+
+    args.rval().setString(word);
+    return true;
+}
+
+
 bool
 js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
 
     JSAutoByteString locale(cx, args[0].toString());
     if (!locale)
@@ -2974,16 +3224,19 @@ GlobalObject::initIntlObject(JSContext* 
     if (!collatorProto)
         return false;
     RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
     if (!dateTimeFormatProto)
         return false;
     RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
     if (!numberFormatProto)
         return false;
+    RootedObject listFormatProto(cx, CreateListFormatPrototype(cx, intl, global));
+    if (!listFormatProto)
+        return false;
 
     // The |Intl| object is fully set up now, so define the global property.
     RootedValue intlValue(cx, ObjectValue(*intl));
     if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr,
                         JSPROP_RESOLVING))
     {
         return false;
     }
@@ -2995,16 +3248,17 @@ GlobalObject::initIntlObject(JSContext* 
     // objects with the proper [[Prototype]] as "the original value of
     // |Intl.Collator.prototype|" and similar.  For builtin classes like
     // |String.prototype| we have |JSProto_*| that enables
     // |getPrototype(JSProto_*)|, but that has global-object-property-related
     // baggage we don't need or want, so we use one-off reserved slots.
     global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
     global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
     global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
+    global->setReservedSlot(LIST_FORMAT_PROTO, ObjectValue(*listFormatProto));
 
     // Also cache |Intl| to implement spec language that conditions behavior
     // based on values being equal to "the standard built-in |Intl| object".
     // Use |setConstructor| to correspond with |JSProto_Intl|.
     //
     // XXX We should possibly do a one-off reserved slot like above.
     global->setConstructor(JSProto_Intl, ObjectValue(*intl));
     return true;
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -358,16 +358,39 @@ intl_patternForSkeleton(JSContext* cx, u
  *
  * Spec: ECMAScript Internationalization API Specification, 12.3.2.
  *
  * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x)
  */
 extern MOZ_MUST_USE bool
 intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
 
+/******************** ListFormat ********************/
+
+/**
+ * Returns a new ListFormat instance.
+ * Self-hosted code cannot cache this constructor (as it does for others in
+ * Utilities.js) because it is initialized after self-hosted code is compiled.
+ *
+ * Usage: listFormat = intl_ListFormat(locales, options)
+ */
+extern MOZ_MUST_USE bool
+intl_ListFormat(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+intl_ListFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp);
+
+extern MOZ_MUST_USE bool
+intl_FormatListFormat(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+MOZ_MUST_USE JSObject*
+CreateListFormatPrototype(JSContext* cx,  HandleObject Intl, Handle<GlobalObject*> global);
+*/
+
 /**
  * Returns a plain object with calendar information for a single valid locale
  * (callers must perform this validation).  The object will have these
  * properties:
  *
  *   firstDayOfWeek
  *     an integer in the range 1=Sunday to 7=Saturday indicating the day
  *     considered the first day of the week in calendars, e.g. 1 for en-US,
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -1246,17 +1246,18 @@ function initializeIntlObject(obj) {
 
 
 /**
  * Mark |internals| as having the given type and lazy data.
  */
 function setLazyData(internals, type, lazyData)
 {
     assert(internals.type === "partial", "can't set lazy data for anything but a newborn");
-    assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type");
+    assert(type === "Collator" || type === "DateTimeFormat" ||
+           type == "NumberFormat" || type === "ListFormat", "bad type");
     assert(IsObject(lazyData), "non-object lazy data");
 
     // Set in reverse order so that the .type change is a barrier.
     internals.lazyData = lazyData;
     internals.type = type;
 }
 
 
@@ -1296,17 +1297,18 @@ function maybeInternalProperties(interna
  * Return whether |obj| has an[[initializedIntlObject]] property set to true.
  */
 function isInitializedIntlObject(obj) {
 #ifdef DEBUG
     var internals = callFunction(std_WeakMap_get, internalsMap, obj);
     if (IsObject(internals)) {
         assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type");
         var type = internals.type;
-        assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type");
+        assert(type === "partial" || type === "Collator" ||
+               type === "DateTimeFormat" || type === "NumberFormat" || type === "ListFormat", "unexpected type");
         assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData");
         assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps");
     } else {
         assert(internals === undefined, "bad mapping for |obj|");
     }
 #endif
     return callFunction(std_WeakMap_has, internalsMap, obj);
 }
@@ -1353,16 +1355,18 @@ function getInternals(obj)
         return internals.internalProps;
 
     var internalProps;
     var type = internals.type;
     if (type === "Collator")
         internalProps = resolveCollatorInternals(lazyData)
     else if (type === "DateTimeFormat")
         internalProps = resolveDateTimeFormatInternals(lazyData)
+    else if (type === "ListFormat")
+        internalProps = resolveListFormatInternals(lazyData)
     else
         internalProps = resolveNumberFormatInternals(lazyData);
     setInternalProperties(internals, internalProps);
     return internalProps;
 }
 
 
 /********** Intl.Collator **********/
@@ -2964,16 +2968,162 @@ function resolveICUPattern(pattern, resu
             if (c === "h" || c === "K")
                 _DefineDataProperty(result, "hour12", true);
             else if (c === "H" || c === "k")
                 _DefineDataProperty(result, "hour12", false);
         }
     }
 }
 
+/********** Intl.ListFormat **********/
+
+var listFormatInternalProperties = {
+    _availableLocales: null,
+    availableLocales: function()
+    {
+        var locales = this._availableLocales;
+        if (locales)
+            return locales;
+
+        locales = intl_ListFormat_availableLocales();
+        addSpecialMissingLanguageTags(locales);
+        return (this._availableLocales = locales);
+    }
+};
+
+var noRelevantExtensionKeys = [];
+
+function resolveListFormatInternals(lazyListFormatData) {
+    assert(IsObject(lazyListFormatData), "lazy data not an object?");
+
+    var internalProps = std_Object_create(null);
+
+    var requestedLocales = lazyListFormatData.requestedLocales;
+
+    var ListFormat = listFormatInternalProperties;
+
+    // Step 13.
+    const r = ResolveLocale(callFunction(ListFormat.availableLocales, ListFormat),
+                          lazyListFormatData.requestedLocales,
+                          lazyListFormatData.opt,
+                          noRelevantExtensionKeys, undefined);
+
+    // Step 14.
+    internalProps.locale = r.locale;
+    internalProps.type = lazyListFormatData.type;
+    internalProps.style = lazyListFormatData.style;
+
+    return internalProps;
+}
+
+function getListFormatInternals(obj, methodName) {
+    var internals = getIntlObjectInternals(obj, "ListFormat", methodName);
+    assert(internals.type === "ListFormat", "bad type escaped getIntlObjectInternals");
+
+    var internalProps = maybeInternalProperties(internals);
+    if (internalProps)
+        return internalProps;
+
+    internalProps = resolveListFormatInternals(internals.lazyData);
+    setInternalProperties(internals, internalProps);
+    return internalProps;
+}
+
+function InitializeListFormat(listFormat, locales, options) {
+    assert(IsObject(listFormat), "InitializeListFormat");
+
+    // Step 1.
+    if (isInitializedIntlObject(listFormat))
+        ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+
+    let internals = initializeIntlObject(listFormat);
+
+    // Lazy ListFormat data has the following structure:
+    //
+    //   {
+    //     requestedLocales: List of locales,
+    //     type: "standard" / "unit",
+    //     style: "long" / "short" / "narrow",
+    //
+    //     opt: // opt object computer in InitializeListFormat
+    //       {
+    //         localeMatcher: "lookup" / "best fit",
+    //       }
+    //   }
+		//
+    // Note that lazy data is only installed as a final step of initialization,
+    // so every ListFormat lazy data object has *all* these properties, never a
+    // subset of them.
+    const lazyListFormatData = std_Object_create(null);
+
+    // Step 3.
+    let requestedLocales = CanonicalizeLocaleList(locales);
+    lazyListFormatData.requestedLocales = requestedLocales;
+
+    // Steps 4-5.
+    if (options === undefined)
+        options = {};
+    else
+        options = ToObject(options);
+
+    // Step 6
+    const type = GetOption(options, "type", "string", ["standard", "unit"], "standard");
+
+    // Step 7.
+    lazyListFormatData.type = type;
+
+    // Step 8.
+    let opt = new Record();
+    lazyListFormatData.opt = opt;
+
+    // Steps 9-10.
+    let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
+    opt.localeMatcher = matcher;
+
+    setLazyData(internals, "ListFormat", lazyListFormatData)
+}
+
+function Intl_ListFormat_supportedLocalesOf(locales /*, options*/) {
+    var options = arguments.length > 1 ? arguments[1] : undefined;
+
+    // Step 1.
+    var availableLocales = callFunction(listFormatInternalProperties.availableLocales,
+                                        listFormatInternalProperties);
+    // Step 2.
+    let requestedLocales = CanonicalizeLocaleList(locales);
+
+    // Step 3.
+    return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+function Intl_ListFormat_format(value) {
+    // Step 1.
+    let listFormat = this;
+    // Step 2.
+    let internals = getListFormatInternals(listFormat, "format");
+
+    // Steps 3-4.
+    let n = value;
+
+    // Step 5.
+    return intl_FormatListFormat(listFormat, n);
+}
+
+function Intl_ListFormat_resolvedOptions() {
+    var internals = getListFormatInternals(this, "resolvedOptions");
+
+    var result = {
+        locale: internals.locale,
+        style: internals.style,
+        type: internals.type,
+    };
+
+    return result;
+}
+
 function Intl_getCanonicalLocales(locales) {
   let codes = CanonicalizeLocaleList(locales);
   let result = [];
 
   let len = codes.length;
   let k = 0;
 
   while (k < len) {
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -59,16 +59,17 @@
 #ifdef XP_WIN
 # include "jswin.h"
 #endif
 #include "jswrapper.h"
 #include "shellmoduleloader.out.h"
 
 #include "builtin/ModuleObject.h"
 #include "builtin/TestingFunctions.h"
+#include "builtin/Intl.h"
 #include "frontend/Parser.h"
 #include "gc/GCInternals.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitcodeMap.h"
 #include "jit/OptimizationTracking.h"
 #include "js/Debug.h"
@@ -908,16 +909,31 @@ AddIntlExtras(JSContext* cx, unsigned ar
     static const JSFunctionSpec funcs[] = {
         JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
         JS_FS_END
     };
 
     if (!JS_DefineFunctions(cx, intl, funcs))
         return false;
 
+    /*
+    Rooted<GlobalObject*> global(cx, cx->global());
+    if (!global)
+        return false;
+
+    RootedObject listFormatProto(cx, js::CreateListFormatPrototype(cx, intl, global));
+    if (!listFormatProto)
+        return false;
+
+    RootedValue elem(cx);
+    elem.setObject(*listFormatProto);
+    if (!DefineProperty(cx, intl, cx->names().ListFormat, elem))
+        return false;
+    */
+
     args.rval().setUndefined();
     return true;
 }
 #endif // ENABLE_INTL_API
 
 static bool
 EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
              int lineno, bool compileOnly)
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -147,16 +147,17 @@
     macro(ignoreCase, ignoreCase, "ignoreCase") \
     macro(ignorePunctuation, ignorePunctuation, "ignorePunctuation") \
     macro(includes, includes, "includes") \
     macro(incumbentGlobal, incumbentGlobal, "incumbentGlobal") \
     macro(index, index, "index") \
     macro(Infinity, Infinity, "Infinity") \
     macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
     macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
+    macro(InitializeListFormat, InitializeListFormat, "InitializeListFormat") \
     macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \
     macro(innermost, innermost, "innermost") \
     macro(inNursery, inNursery, "inNursery") \
     macro(input, input, "input") \
     macro(int8, int8, "int8") \
     macro(int16, int16, "int16") \
     macro(int32, int32, "int32") \
     macro(Int8x16, Int8x16, "Int8x16") \
@@ -176,16 +177,17 @@
     macro(keys, keys, "keys") \
     macro(label, label, "label") \
     macro(lastIndex, lastIndex, "lastIndex") \
     macro(LegacyGeneratorCloseInternal, LegacyGeneratorCloseInternal, "LegacyGeneratorCloseInternal") \
     macro(length, length, "length") \
     macro(let, let, "let") \
     macro(line, line, "line") \
     macro(lineNumber, lineNumber, "lineNumber") \
+    macro(ListFormat, ListFormat, "ListFormat") \
     macro(literal, literal, "literal") \
     macro(loc, loc, "loc") \
     macro(locale, locale, "locale") \
     macro(lookupGetter, lookupGetter, "__lookupGetter__") \
     macro(lookupSetter, lookupSetter, "__lookupSetter__") \
     macro(MapConstructorInit, MapConstructorInit, "MapConstructorInit") \
     macro(MapIterator, MapIterator, "Map Iterator") \
     macro(maximumFractionDigits, maximumFractionDigits, "maximumFractionDigits") \
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -99,16 +99,17 @@ class GlobalObject : public NativeObject
         STAR_GENERATOR_FUNCTION,
         ASYNC_FUNCTION_PROTO,
         ASYNC_FUNCTION,
         MAP_ITERATOR_PROTO,
         SET_ITERATOR_PROTO,
         COLLATOR_PROTO,
         NUMBER_FORMAT_PROTO,
         DATE_TIME_FORMAT_PROTO,
+        LIST_FORMAT_PROTO,
         MODULE_PROTO,
         IMPORT_ENTRY_PROTO,
         EXPORT_ENTRY_PROTO,
         REGEXP_STATICS,
         WARNED_ONCE_FLAGS,
         RUNTIME_CODEGEN_ENABLED,
         DEBUGGERS,
         INTRINSICS,
@@ -482,16 +483,20 @@ class GlobalObject : public NativeObject
     JSObject* getOrCreateNumberFormatPrototype(JSContext* cx) {
         return getOrCreateObject(cx, NUMBER_FORMAT_PROTO, initIntlObject);
     }
 
     JSObject* getOrCreateDateTimeFormatPrototype(JSContext* cx) {
         return getOrCreateObject(cx, DATE_TIME_FORMAT_PROTO, initIntlObject);
     }
 
+    JSObject* getOrCreateListFormatPrototype(JSContext* cx) {
+        return getOrCreateObject(cx, LIST_FORMAT_PROTO, initIntlObject);
+    }
+
     static bool ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global);
 
     JSObject* maybeGetModulePrototype() {
         Value value = getSlot(MODULE_PROTO);
         return value.isUndefined() ? nullptr : &value.toObject();
     }
 
     JSObject* maybeGetImportEntryPrototype() {
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2477,16 +2477,18 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
     JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
     JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
     JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
     JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
     JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
+    JS_FN("intl_ListFormat_availableLocales", intl_ListFormat_availableLocales, 0, 0),
+    JS_FN("intl_FormatListFormat", intl_FormatListFormat, 2, 0),
 
     JS_INLINABLE_FN("IsRegExpObject",
                     intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
                     IsRegExpObject),
     JS_FN("CallRegExpMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<RegExpObject>>, 2,0),
     JS_INLINABLE_FN("RegExpMatcher", RegExpMatcher, 4,0,
                     RegExpMatcher),