ListFormat.cpp (11750B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "builtin/intl/ListFormat.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/intl/ListFormat.h" 11 12 #include <stddef.h> 13 14 #include "builtin/Array.h" 15 #include "builtin/intl/CommonFunctions.h" 16 #include "builtin/intl/FormatBuffer.h" 17 #include "builtin/intl/LocaleNegotiation.h" 18 #include "gc/GCContext.h" 19 #include "js/Utility.h" 20 #include "js/Vector.h" 21 #include "vm/JSContext.h" 22 #include "vm/PlainObject.h" // js::PlainObject 23 #include "vm/StringType.h" 24 25 #include "vm/JSObject-inl.h" 26 #include "vm/NativeObject-inl.h" 27 #include "vm/ObjectOperations-inl.h" 28 29 using namespace js; 30 using namespace js::intl; 31 32 const JSClassOps ListFormatObject::classOps_ = { 33 nullptr, // addProperty 34 nullptr, // delProperty 35 nullptr, // enumerate 36 nullptr, // newEnumerate 37 nullptr, // resolve 38 nullptr, // mayResolve 39 ListFormatObject::finalize, // finalize 40 nullptr, // call 41 nullptr, // construct 42 nullptr, // trace 43 }; 44 const JSClass ListFormatObject::class_ = { 45 "Intl.ListFormat", 46 JSCLASS_HAS_RESERVED_SLOTS(ListFormatObject::SLOT_COUNT) | 47 JSCLASS_HAS_CACHED_PROTO(JSProto_ListFormat) | 48 JSCLASS_FOREGROUND_FINALIZE, 49 &ListFormatObject::classOps_, 50 &ListFormatObject::classSpec_, 51 }; 52 53 const JSClass& ListFormatObject::protoClass_ = PlainObject::class_; 54 55 static bool listFormat_supportedLocalesOf(JSContext* cx, unsigned argc, 56 Value* vp); 57 58 static bool listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) { 59 CallArgs args = CallArgsFromVp(argc, vp); 60 args.rval().setString(cx->names().ListFormat); 61 return true; 62 } 63 64 static const JSFunctionSpec listFormat_static_methods[] = { 65 JS_FN("supportedLocalesOf", listFormat_supportedLocalesOf, 1, 0), 66 JS_FS_END, 67 }; 68 69 static const JSFunctionSpec listFormat_methods[] = { 70 JS_SELF_HOSTED_FN("resolvedOptions", "Intl_ListFormat_resolvedOptions", 0, 71 0), 72 JS_SELF_HOSTED_FN("format", "Intl_ListFormat_format", 1, 0), 73 JS_SELF_HOSTED_FN("formatToParts", "Intl_ListFormat_formatToParts", 1, 0), 74 JS_FN("toSource", listFormat_toSource, 0, 0), 75 JS_FS_END, 76 }; 77 78 static const JSPropertySpec listFormat_properties[] = { 79 JS_STRING_SYM_PS(toStringTag, "Intl.ListFormat", JSPROP_READONLY), 80 JS_PS_END, 81 }; 82 83 static bool ListFormat(JSContext* cx, unsigned argc, Value* vp); 84 85 const ClassSpec ListFormatObject::classSpec_ = { 86 GenericCreateConstructor<ListFormat, 0, gc::AllocKind::FUNCTION>, 87 GenericCreatePrototype<ListFormatObject>, 88 listFormat_static_methods, 89 nullptr, 90 listFormat_methods, 91 listFormat_properties, 92 nullptr, 93 ClassSpec::DontDefineConstructor, 94 }; 95 96 /** 97 * Intl.ListFormat([ locales [, options]]) 98 */ 99 static bool ListFormat(JSContext* cx, unsigned argc, Value* vp) { 100 CallArgs args = CallArgsFromVp(argc, vp); 101 102 // Step 1. 103 if (!ThrowIfNotConstructing(cx, args, "Intl.ListFormat")) { 104 return false; 105 } 106 107 // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). 108 RootedObject proto(cx); 109 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ListFormat, 110 &proto)) { 111 return false; 112 } 113 114 Rooted<ListFormatObject*> listFormat( 115 cx, NewObjectWithClassProto<ListFormatObject>(cx, proto)); 116 if (!listFormat) { 117 return false; 118 } 119 120 HandleValue locales = args.get(0); 121 HandleValue options = args.get(1); 122 123 // Step 3. 124 if (!intl::InitializeObject(cx, listFormat, cx->names().InitializeListFormat, 125 locales, options)) { 126 return false; 127 } 128 129 args.rval().setObject(*listFormat); 130 return true; 131 } 132 133 void js::ListFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) { 134 MOZ_ASSERT(gcx->onMainThread()); 135 136 mozilla::intl::ListFormat* lf = 137 obj->as<ListFormatObject>().getListFormatSlot(); 138 if (lf) { 139 intl::RemoveICUCellMemory(gcx, obj, ListFormatObject::EstimatedMemoryUse); 140 delete lf; 141 } 142 } 143 144 /** 145 * Returns a new ListFormat with the locale and list formatting options 146 * of the given ListFormat. 147 */ 148 static mozilla::intl::ListFormat* NewListFormat( 149 JSContext* cx, Handle<ListFormatObject*> listFormat) { 150 RootedObject internals(cx, intl::GetInternalsObject(cx, listFormat)); 151 if (!internals) { 152 return nullptr; 153 } 154 155 RootedValue value(cx); 156 157 if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) { 158 return nullptr; 159 } 160 UniqueChars locale = intl::EncodeLocale(cx, value.toString()); 161 if (!locale) { 162 return nullptr; 163 } 164 165 mozilla::intl::ListFormat::Options options; 166 167 using ListFormatType = mozilla::intl::ListFormat::Type; 168 if (!GetProperty(cx, internals, internals, cx->names().type, &value)) { 169 return nullptr; 170 } 171 { 172 JSLinearString* strType = value.toString()->ensureLinear(cx); 173 if (!strType) { 174 return nullptr; 175 } 176 177 if (StringEqualsLiteral(strType, "conjunction")) { 178 options.mType = ListFormatType::Conjunction; 179 } else if (StringEqualsLiteral(strType, "disjunction")) { 180 options.mType = ListFormatType::Disjunction; 181 } else { 182 MOZ_ASSERT(StringEqualsLiteral(strType, "unit")); 183 options.mType = ListFormatType::Unit; 184 } 185 } 186 187 using ListFormatStyle = mozilla::intl::ListFormat::Style; 188 if (!GetProperty(cx, internals, internals, cx->names().style, &value)) { 189 return nullptr; 190 } 191 { 192 JSLinearString* strStyle = value.toString()->ensureLinear(cx); 193 if (!strStyle) { 194 return nullptr; 195 } 196 197 if (StringEqualsLiteral(strStyle, "long")) { 198 options.mStyle = ListFormatStyle::Long; 199 } else if (StringEqualsLiteral(strStyle, "short")) { 200 options.mStyle = ListFormatStyle::Short; 201 } else { 202 MOZ_ASSERT(StringEqualsLiteral(strStyle, "narrow")); 203 options.mStyle = ListFormatStyle::Narrow; 204 } 205 } 206 207 auto result = mozilla::intl::ListFormat::TryCreate( 208 mozilla::MakeStringSpan(locale.get()), options); 209 210 if (result.isOk()) { 211 return result.unwrap().release(); 212 } 213 214 js::intl::ReportInternalError(cx, result.unwrapErr()); 215 return nullptr; 216 } 217 218 static mozilla::intl::ListFormat* GetOrCreateListFormat( 219 JSContext* cx, Handle<ListFormatObject*> listFormat) { 220 // Obtain a cached mozilla::intl::ListFormat object. 221 mozilla::intl::ListFormat* lf = listFormat->getListFormatSlot(); 222 if (lf) { 223 return lf; 224 } 225 226 lf = NewListFormat(cx, listFormat); 227 if (!lf) { 228 return nullptr; 229 } 230 listFormat->setListFormatSlot(lf); 231 232 intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse); 233 return lf; 234 } 235 236 /** 237 * FormatList ( listFormat, list ) 238 */ 239 static bool FormatList(JSContext* cx, mozilla::intl::ListFormat* lf, 240 const mozilla::intl::ListFormat::StringList& list, 241 MutableHandleValue result) { 242 intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> formatBuffer(cx); 243 auto formatResult = lf->Format(list, formatBuffer); 244 if (formatResult.isErr()) { 245 js::intl::ReportInternalError(cx, formatResult.unwrapErr()); 246 return false; 247 } 248 249 JSString* str = formatBuffer.toString(cx); 250 if (!str) { 251 return false; 252 } 253 result.setString(str); 254 return true; 255 } 256 257 /** 258 * FormatListToParts ( listFormat, list ) 259 */ 260 static bool FormatListToParts(JSContext* cx, mozilla::intl::ListFormat* lf, 261 const mozilla::intl::ListFormat::StringList& list, 262 MutableHandleValue result) { 263 intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx); 264 mozilla::intl::ListFormat::PartVector parts; 265 auto formatResult = lf->FormatToParts(list, buffer, parts); 266 if (formatResult.isErr()) { 267 intl::ReportInternalError(cx, formatResult.unwrapErr()); 268 return false; 269 } 270 271 RootedString overallResult(cx, buffer.toString(cx)); 272 if (!overallResult) { 273 return false; 274 } 275 276 Rooted<ArrayObject*> partsArray( 277 cx, NewDenseFullyAllocatedArray(cx, parts.length())); 278 if (!partsArray) { 279 return false; 280 } 281 partsArray->ensureDenseInitializedLength(0, parts.length()); 282 283 RootedObject singlePart(cx); 284 RootedValue val(cx); 285 286 size_t index = 0; 287 size_t beginIndex = 0; 288 for (const mozilla::intl::ListFormat::Part& part : parts) { 289 singlePart = NewPlainObject(cx); 290 if (!singlePart) { 291 return false; 292 } 293 294 if (part.first == mozilla::intl::ListFormat::PartType::Element) { 295 val = StringValue(cx->names().element); 296 } else { 297 val = StringValue(cx->names().literal); 298 } 299 300 if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) { 301 return false; 302 } 303 304 // There could be an empty string so the endIndex coule be equal to 305 // beginIndex. 306 MOZ_ASSERT(part.second >= beginIndex); 307 JSLinearString* partStr = NewDependentString(cx, overallResult, beginIndex, 308 part.second - beginIndex); 309 if (!partStr) { 310 return false; 311 } 312 val = StringValue(partStr); 313 if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) { 314 return false; 315 } 316 317 beginIndex = part.second; 318 partsArray->initDenseElement(index++, ObjectValue(*singlePart)); 319 } 320 321 MOZ_ASSERT(index == parts.length()); 322 MOZ_ASSERT(beginIndex == buffer.length()); 323 result.setObject(*partsArray); 324 325 return true; 326 } 327 328 bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) { 329 CallArgs args = CallArgsFromVp(argc, vp); 330 MOZ_ASSERT(args.length() == 3); 331 332 Rooted<ListFormatObject*> listFormat( 333 cx, &args[0].toObject().as<ListFormatObject>()); 334 335 bool formatToParts = args[2].toBoolean(); 336 337 mozilla::intl::ListFormat* lf = GetOrCreateListFormat(cx, listFormat); 338 if (!lf) { 339 return false; 340 } 341 342 // Collect all strings and their lengths. 343 // 344 // 'strings' takes the ownership of those strings, and 'list' will be passed 345 // to mozilla::intl::ListFormat as a Span. 346 Vector<UniqueTwoByteChars, mozilla::intl::DEFAULT_LIST_LENGTH> strings(cx); 347 mozilla::intl::ListFormat::StringList list; 348 349 Rooted<ArrayObject*> listObj(cx, &args[1].toObject().as<ArrayObject>()); 350 RootedValue value(cx); 351 uint32_t listLen = listObj->length(); 352 for (uint32_t i = 0; i < listLen; i++) { 353 if (!GetElement(cx, listObj, listObj, i, &value)) { 354 return false; 355 } 356 357 JSLinearString* linear = value.toString()->ensureLinear(cx); 358 if (!linear) { 359 return false; 360 } 361 362 size_t linearLength = linear->length(); 363 364 UniqueTwoByteChars chars = cx->make_pod_array<char16_t>(linearLength); 365 if (!chars) { 366 return false; 367 } 368 CopyChars(chars.get(), *linear); 369 370 if (!strings.append(std::move(chars))) { 371 return false; 372 } 373 374 if (!list.emplaceBack(strings[i].get(), linearLength)) { 375 return false; 376 } 377 } 378 379 if (formatToParts) { 380 return FormatListToParts(cx, lf, list, args.rval()); 381 } 382 return FormatList(cx, lf, list, args.rval()); 383 } 384 385 /** 386 * Intl.ListFormat.supportedLocalesOf ( locales [ , options ] ) 387 */ 388 static bool listFormat_supportedLocalesOf(JSContext* cx, unsigned argc, 389 Value* vp) { 390 CallArgs args = CallArgsFromVp(argc, vp); 391 392 // Steps 1-3. 393 auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::ListFormat, 394 args.get(0), args.get(1)); 395 if (!array) { 396 return false; 397 } 398 args.rval().setObject(*array); 399 return true; 400 }