EncodedJSValue JSC_HOST_CALL constructJSWorker(ExecState& state) { VM& vm = state.vm(); auto scope = DECLARE_THROW_SCOPE(vm); ASSERT(jsCast<DOMConstructorObject*>(state.jsCallee())); ASSERT(jsCast<DOMConstructorObject*>(state.jsCallee())->globalObject()); auto& globalObject = *jsCast<DOMConstructorObject*>(state.jsCallee())->globalObject(); if (!state.argumentCount()) return throwVMError(&state, scope, createNotEnoughArgumentsError(&state)); String scriptURL = state.uncheckedArgument(0).toWTFString(&state); RETURN_IF_EXCEPTION(scope, encodedJSValue()); // See section 4.8.2 step 14 of WebWorkers for why this is the lexicalGlobalObject. auto& window = asJSDOMWindow(state.lexicalGlobalObject())->wrapped(); ASSERT(window.document()); return JSValue::encode(toJSNewlyCreated(state, globalObject, scope, Worker::create(*window.document(), scriptURL, globalObject.runtimeFlags()))); }
EncodedJSValue JSC_HOST_CALL constructJSMutationObserver(ExecState& exec) { VM& vm = exec.vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (exec.argumentCount() < 1) return throwVMError(&exec, scope, createNotEnoughArgumentsError(&exec)); JSObject* object = exec.uncheckedArgument(0).getObject(); CallData callData; if (!object || object->methodTable()->getCallData(object, callData) == CallType::None) return throwArgumentTypeError(exec, scope, 0, "callback", "MutationObserver", nullptr, "MutationCallback"); DOMConstructorObject* jsConstructor = jsCast<DOMConstructorObject*>(exec.jsCallee()); auto callback = JSMutationCallback::create(object, jsConstructor->globalObject()); JSObject* jsObserver = asObject(toJSNewlyCreated(&exec, jsConstructor->globalObject(), MutationObserver::create(WTFMove(callback)))); PrivateName propertyName; jsObserver->putDirect(vm, propertyName, object); return JSValue::encode(jsObserver); }
JSValue IntlNumberFormat::formatToParts(ExecState& exec, double value) { VM& vm = exec.vm(); auto scope = DECLARE_THROW_SCOPE(vm); // FormatNumberToParts (ECMA-402) // https://tc39.github.io/ecma402/#sec-formatnumbertoparts // https://tc39.github.io/ecma402/#sec-partitionnumberpattern if (!m_initializedNumberFormat) return throwTypeError(&exec, scope, "Intl.NumberFormat.prototype.formatToParts called on value that's not an object initialized as a NumberFormat"_s); UErrorCode status = U_ZERO_ERROR; auto fieldItr = std::unique_ptr<UFieldPositionIterator, UFieldPositionIteratorDeleter>(ufieldpositer_open(&status)); if (U_FAILURE(status)) return throwTypeError(&exec, scope, "failed to open field position iterator"_s); status = U_ZERO_ERROR; Vector<UChar, 32> result(32); auto resultLength = unum_formatDoubleForFields(m_numberFormat.get(), value, result.data(), result.size(), fieldItr.get(), &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; result.grow(resultLength); unum_formatDoubleForFields(m_numberFormat.get(), value, result.data(), resultLength, fieldItr.get(), &status); } if (U_FAILURE(status)) return throwTypeError(&exec, scope, "failed to format a number."_s); int32_t literalFieldType = -1; auto literalField = IntlNumberFormatField(literalFieldType, resultLength); Vector<IntlNumberFormatField> fields(resultLength, literalField); int32_t beginIndex = 0; int32_t endIndex = 0; auto fieldType = ufieldpositer_next(fieldItr.get(), &beginIndex, &endIndex); while (fieldType >= 0) { auto size = endIndex - beginIndex; for (auto i = beginIndex; i < endIndex; ++i) { // Only override previous value if new value is more specific. if (fields[i].size >= size) fields[i] = IntlNumberFormatField(fieldType, size); } fieldType = ufieldpositer_next(fieldItr.get(), &beginIndex, &endIndex); } JSGlobalObject* globalObject = exec.jsCallee()->globalObject(vm); JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0); if (!parts) return throwOutOfMemoryError(&exec, scope); unsigned index = 0; auto resultString = String(result.data(), resultLength); auto typePropertyName = Identifier::fromString(&vm, "type"); auto literalString = jsString(&exec, "literal"_s); int32_t currentIndex = 0; while (currentIndex < resultLength) { auto startIndex = currentIndex; auto fieldType = fields[currentIndex].type; while (currentIndex < resultLength && fields[currentIndex].type == fieldType) ++currentIndex; auto partType = fieldType == literalFieldType ? literalString : jsString(&exec, partTypeString(UNumberFormatFields(fieldType), value)); auto partValue = jsSubstring(&vm, resultString, startIndex, currentIndex - startIndex); JSObject* part = constructEmptyObject(&exec); part->putDirect(vm, typePropertyName, partType); part->putDirect(vm, vm.propertyNames->value, partValue); parts->putDirectIndex(&exec, index++, part); RETURN_IF_EXCEPTION(scope, { }); } return parts; }
void IntlNumberFormat::initializeNumberFormat(ExecState& state, JSValue locales, JSValue optionsValue) { VM& vm = state.vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 11.1.2 InitializeNumberFormat (numberFormat, locales, options) (ECMA-402) // https://tc39.github.io/ecma402/#sec-initializenumberformat auto requestedLocales = canonicalizeLocaleList(state, locales); RETURN_IF_EXCEPTION(scope, void()); JSObject* options; if (optionsValue.isUndefined()) options = constructEmptyObject(&state, state.lexicalGlobalObject()->nullPrototypeObjectStructure()); else { options = optionsValue.toObject(&state); RETURN_IF_EXCEPTION(scope, void()); } HashMap<String, String> opt; String matcher = intlStringOption(state, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit"); RETURN_IF_EXCEPTION(scope, void()); opt.add("localeMatcher"_s, matcher); auto& availableLocales = state.jsCallee()->globalObject(vm)->intlNumberFormatAvailableLocales(); auto result = resolveLocale(state, availableLocales, requestedLocales, opt, relevantNumberExtensionKeys, WTF_ARRAY_LENGTH(relevantNumberExtensionKeys), IntlNFInternal::localeData); m_locale = result.get("locale"_s); if (m_locale.isEmpty()) { throwTypeError(&state, scope, "failed to initialize NumberFormat due to invalid locale"_s); return; } m_numberingSystem = result.get("nu"_s); String styleString = intlStringOption(state, options, Identifier::fromString(&vm, "style"), { "decimal", "percent", "currency" }, "style must be either \"decimal\", \"percent\", or \"currency\"", "decimal"); RETURN_IF_EXCEPTION(scope, void()); if (styleString == "decimal") m_style = Style::Decimal; else if (styleString == "percent") m_style = Style::Percent; else if (styleString == "currency") m_style = Style::Currency; else ASSERT_NOT_REACHED(); String currency = intlStringOption(state, options, Identifier::fromString(&vm, "currency"), { }, nullptr, nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!currency.isNull()) { if (currency.length() != 3 || !currency.isAllSpecialCharacters<isASCIIAlpha>()) { throwException(&state, scope, createRangeError(&state, "currency is not a well-formed currency code"_s)); return; } } unsigned currencyDigits = 0; if (m_style == Style::Currency) { if (currency.isNull()) { throwTypeError(&state, scope, "currency must be a string"_s); return; } currency = currency.convertToASCIIUppercase(); m_currency = currency; currencyDigits = computeCurrencyDigits(currency); } String currencyDisplayString = intlStringOption(state, options, Identifier::fromString(&vm, "currencyDisplay"), { "code", "symbol", "name" }, "currencyDisplay must be either \"code\", \"symbol\", or \"name\"", "symbol"); RETURN_IF_EXCEPTION(scope, void()); if (m_style == Style::Currency) { if (currencyDisplayString == "code") m_currencyDisplay = CurrencyDisplay::Code; else if (currencyDisplayString == "symbol") m_currencyDisplay = CurrencyDisplay::Symbol; else if (currencyDisplayString == "name") m_currencyDisplay = CurrencyDisplay::Name; else ASSERT_NOT_REACHED(); } unsigned minimumIntegerDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumIntegerDigits"), 1, 21, 1); RETURN_IF_EXCEPTION(scope, void()); m_minimumIntegerDigits = minimumIntegerDigits; unsigned minimumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : 0; unsigned minimumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumFractionDigits"), 0, 20, minimumFractionDigitsDefault); RETURN_IF_EXCEPTION(scope, void()); m_minimumFractionDigits = minimumFractionDigits; unsigned maximumFractionDigitsDefault; if (m_style == Style::Currency) maximumFractionDigitsDefault = std::max(minimumFractionDigits, currencyDigits); else if (m_style == Style::Percent) maximumFractionDigitsDefault = minimumFractionDigits; else maximumFractionDigitsDefault = std::max(minimumFractionDigits, 3u); unsigned maximumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "maximumFractionDigits"), minimumFractionDigits, 20, maximumFractionDigitsDefault); RETURN_IF_EXCEPTION(scope, void()); m_maximumFractionDigits = maximumFractionDigits; JSValue minimumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "minimumSignificantDigits")); RETURN_IF_EXCEPTION(scope, void()); JSValue maximumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "maximumSignificantDigits")); RETURN_IF_EXCEPTION(scope, void()); if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) { unsigned minimumSignificantDigits = intlDefaultNumberOption(state, minimumSignificantDigitsValue, Identifier::fromString(&vm, "minimumSignificantDigits"), 1, 21, 1); RETURN_IF_EXCEPTION(scope, void()); unsigned maximumSignificantDigits = intlDefaultNumberOption(state, maximumSignificantDigitsValue, Identifier::fromString(&vm, "maximumSignificantDigits"), minimumSignificantDigits, 21, 21); RETURN_IF_EXCEPTION(scope, void()); m_minimumSignificantDigits = minimumSignificantDigits; m_maximumSignificantDigits = maximumSignificantDigits; } bool usesFallback; bool useGrouping = intlBooleanOption(state, options, Identifier::fromString(&vm, "useGrouping"), usesFallback); if (usesFallback) useGrouping = true; RETURN_IF_EXCEPTION(scope, void()); m_useGrouping = useGrouping; UNumberFormatStyle style = UNUM_DEFAULT; switch (m_style) { case Style::Decimal: style = UNUM_DECIMAL; break; case Style::Percent: style = UNUM_PERCENT; break; case Style::Currency: switch (m_currencyDisplay) { case CurrencyDisplay::Code: style = UNUM_CURRENCY_ISO; break; case CurrencyDisplay::Symbol: style = UNUM_CURRENCY; break; case CurrencyDisplay::Name: style = UNUM_CURRENCY_PLURAL; break; default: ASSERT_NOT_REACHED(); } break; default: ASSERT_NOT_REACHED(); } UErrorCode status = U_ZERO_ERROR; m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(style, nullptr, 0, m_locale.utf8().data(), nullptr, &status)); if (U_FAILURE(status)) { throwTypeError(&state, scope, "failed to initialize NumberFormat"_s); return; } if (m_style == Style::Currency) { unum_setTextAttribute(m_numberFormat.get(), UNUM_CURRENCY_CODE, StringView(m_currency).upconvertedCharacters(), m_currency.length(), &status); if (U_FAILURE(status)) { throwTypeError(&state, scope, "failed to initialize NumberFormat"_s); return; } } if (!m_minimumSignificantDigits) { unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits); unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits); unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits); } else { unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true); unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits); unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits); } unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, m_useGrouping); unum_setAttribute(m_numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); m_initializedNumberFormat = true; }
EncodedJSValue JSC_HOST_CALL constructJSHTMLElement(ExecState& exec) { VM& vm = exec.vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* jsConstructor = jsCast<JSDOMConstructorBase*>(exec.jsCallee()); ASSERT(jsConstructor); auto* context = jsConstructor->scriptExecutionContext(); if (!context) return throwConstructorScriptExecutionContextUnavailableError(exec, scope, "HTMLElement"); ASSERT(context->isDocument()); JSValue newTargetValue = exec.thisValue(); auto* newTarget = newTargetValue.getObject(); auto* globalObject = jsCast<JSDOMGlobalObject*>(newTarget->globalObject(vm)); JSValue htmlElementConstructorValue = JSHTMLElement::getConstructor(vm, globalObject); if (newTargetValue == htmlElementConstructorValue) return throwVMTypeError(&exec, scope, "new.target is not a valid custom element constructor"_s); auto& document = downcast<Document>(*context); auto* window = document.domWindow(); if (!window) return throwVMTypeError(&exec, scope, "new.target is not a valid custom element constructor"_s); auto* registry = window->customElementRegistry(); if (!registry) return throwVMTypeError(&exec, scope, "new.target is not a valid custom element constructor"_s); auto* elementInterface = registry->findInterface(newTarget); if (!elementInterface) return throwVMTypeError(&exec, scope, "new.target does not define a custom element"_s); if (!elementInterface->isUpgradingElement()) { Structure* baseStructure = getDOMStructure<JSHTMLElement>(vm, *globalObject); auto* newElementStructure = InternalFunction::createSubclassStructure(&exec, newTargetValue, baseStructure); RETURN_IF_EXCEPTION(scope, encodedJSValue()); Ref<HTMLElement> element = HTMLElement::create(elementInterface->name(), document); element->setIsDefinedCustomElement(*elementInterface); auto* jsElement = JSHTMLElement::create(newElementStructure, globalObject, element.get()); cacheWrapper(globalObject->world(), element.ptr(), jsElement); return JSValue::encode(jsElement); } Element* elementToUpgrade = elementInterface->lastElementInConstructionStack(); if (!elementToUpgrade) { throwInvalidStateError(exec, scope, "Cannot instantiate a custom element inside its own constructor during upgrades"_s); return JSValue::encode(jsUndefined()); } JSValue elementWrapperValue = toJS(&exec, jsConstructor->globalObject(), *elementToUpgrade); ASSERT(elementWrapperValue.isObject()); JSValue newPrototype = newTarget->get(&exec, vm.propertyNames->prototype); RETURN_IF_EXCEPTION(scope, encodedJSValue()); JSObject* elementWrapperObject = asObject(elementWrapperValue); JSObject::setPrototype(elementWrapperObject, &exec, newPrototype, true /* shouldThrowIfCantSet */); RETURN_IF_EXCEPTION(scope, encodedJSValue()); elementInterface->didUpgradeLastElementInConstructionStack(); return JSValue::encode(elementWrapperValue); }
void IntlDateTimeFormat::initializeDateTimeFormat(ExecState& exec, JSValue locales, JSValue originalOptions) { VM& vm = exec.vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 12.1.1 InitializeDateTimeFormat (dateTimeFormat, locales, options) (ECMA-402 2.0) // 1. If dateTimeFormat.[[initializedIntlObject]] is true, throw a TypeError exception. // 2. Set dateTimeFormat.[[initializedIntlObject]] to true. // 3. Let requestedLocales be CanonicalizeLocaleList(locales). Vector<String> requestedLocales = canonicalizeLocaleList(exec, locales); // 4. ReturnIfAbrupt(requestedLocales), RETURN_IF_EXCEPTION(scope, void()); // 5. Let options be ToDateTimeOptions(options, "any", "date"). JSObject* options = toDateTimeOptionsAnyDate(exec, originalOptions); // 6. ReturnIfAbrupt(options). RETURN_IF_EXCEPTION(scope, void()); // 7. Let opt be a new Record. HashMap<String, String> localeOpt; // 8. Let matcher be GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit"). String localeMatcher = intlStringOption(exec, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit"); // 9. ReturnIfAbrupt(matcher). RETURN_IF_EXCEPTION(scope, void()); // 10. Set opt.[[localeMatcher]] to matcher. localeOpt.add(vm.propertyNames->localeMatcher.string(), localeMatcher); // 11. Let localeData be the value of %DateTimeFormat%.[[localeData]]. // 12. Let r be ResolveLocale( %DateTimeFormat%.[[availableLocales]], requestedLocales, opt, %DateTimeFormat%.[[relevantExtensionKeys]], localeData). const HashSet<String> availableLocales = exec.jsCallee()->globalObject()->intlDateTimeFormatAvailableLocales(); HashMap<String, String> resolved = resolveLocale(exec, availableLocales, requestedLocales, localeOpt, relevantExtensionKeys, WTF_ARRAY_LENGTH(relevantExtensionKeys), localeData); // 13. Set dateTimeFormat.[[locale]] to the value of r.[[locale]]. m_locale = resolved.get(vm.propertyNames->locale.string()); if (m_locale.isEmpty()) { throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat due to invalid locale")); return; } // 14. Set dateTimeFormat.[[calendar]] to the value of r.[[ca]]. m_calendar = resolved.get(ASCIILiteral("ca")); // Switch to preferred aliases. if (m_calendar == "gregory") m_calendar = ASCIILiteral("gregorian"); else if (m_calendar == "islamicc") m_calendar = ASCIILiteral("islamic-civil"); else if (m_calendar == "ethioaa") m_calendar = ASCIILiteral("ethiopic-amete-alem"); // 15. Set dateTimeFormat.[[numberingSystem]] to the value of r.[[nu]]. m_numberingSystem = resolved.get(ASCIILiteral("nu")); // 16. Let dataLocale be the value of r.[[dataLocale]]. String dataLocale = resolved.get(ASCIILiteral("dataLocale")); // 17. Let tz be Get(options, "timeZone"). JSValue tzValue = options->get(&exec, vm.propertyNames->timeZone); // 18. ReturnIfAbrupt(tz). RETURN_IF_EXCEPTION(scope, void()); // 19. If tz is not undefined, then String tz; if (!tzValue.isUndefined()) { // a. Let tz be ToString(tz). String originalTz = tzValue.toWTFString(&exec); // b. ReturnIfAbrupt(tz). RETURN_IF_EXCEPTION(scope, void()); // c. If the result of IsValidTimeZoneName(tz) is false, then i. Throw a RangeError exception. // d. Let tz be CanonicalizeTimeZoneName(tz). tz = canonicalizeTimeZoneName(originalTz); if (tz.isNull()) { throwRangeError(&exec, scope, String::format("invalid time zone: %s", originalTz.utf8().data())); return; } } else { // 20. Else, // a. Let tz be DefaultTimeZone(). tz = defaultTimeZone(); } // 21. Set dateTimeFormat.[[timeZone]] to tz. m_timeZone = tz; // 22. Let opt be a new Record. // Rather than building a record, build the skeleton pattern. StringBuilder skeletonBuilder; // 23. For each row of Table 3, except the header row, do: // a. Let prop be the name given in the Property column of the row. // b. Let value be GetOption(options, prop, "string", «the strings given in the Values column of the row», undefined). // c. ReturnIfAbrupt(value). // d. Set opt.[[<prop>]] to value. auto narrowShortLong = { "narrow", "short", "long" }; auto twoDigitNumeric = { "2-digit", "numeric" }; auto twoDigitNumericNarrowShortLong = { "2-digit", "numeric", "narrow", "short", "long" }; auto shortLong = { "short", "long" }; String weekday = intlStringOption(exec, options, vm.propertyNames->weekday, narrowShortLong, "weekday must be \"narrow\", \"short\", or \"long\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!weekday.isNull()) { if (weekday == "narrow") skeletonBuilder.appendLiteral("EEEEE"); else if (weekday == "short") skeletonBuilder.appendLiteral("EEE"); else if (weekday == "long") skeletonBuilder.appendLiteral("EEEE"); } String era = intlStringOption(exec, options, vm.propertyNames->era, narrowShortLong, "era must be \"narrow\", \"short\", or \"long\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!era.isNull()) { if (era == "narrow") skeletonBuilder.appendLiteral("GGGGG"); else if (era == "short") skeletonBuilder.appendLiteral("GGG"); else if (era == "long") skeletonBuilder.appendLiteral("GGGG"); } String year = intlStringOption(exec, options, vm.propertyNames->year, twoDigitNumeric, "year must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!year.isNull()) { if (year == "2-digit") skeletonBuilder.appendLiteral("yy"); else if (year == "numeric") skeletonBuilder.append('y'); } String month = intlStringOption(exec, options, vm.propertyNames->month, twoDigitNumericNarrowShortLong, "month must be \"2-digit\", \"numeric\", \"narrow\", \"short\", or \"long\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!month.isNull()) { if (month == "2-digit") skeletonBuilder.appendLiteral("MM"); else if (month == "numeric") skeletonBuilder.append('M'); else if (month == "narrow") skeletonBuilder.appendLiteral("MMMMM"); else if (month == "short") skeletonBuilder.appendLiteral("MMM"); else if (month == "long") skeletonBuilder.appendLiteral("MMMM"); } String day = intlStringOption(exec, options, vm.propertyNames->day, twoDigitNumeric, "day must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!day.isNull()) { if (day == "2-digit") skeletonBuilder.appendLiteral("dd"); else if (day == "numeric") skeletonBuilder.append('d'); } String hour = intlStringOption(exec, options, vm.propertyNames->hour, twoDigitNumeric, "hour must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); // We need hour12 to make the hour skeleton pattern decision, so do this early. // 32. Let hr12 be GetOption(options, "hour12", "boolean", undefined, undefined). bool isHour12Undefined; bool hr12 = intlBooleanOption(exec, options, vm.propertyNames->hour12, isHour12Undefined); // 33. ReturnIfAbrupt(hr12). RETURN_IF_EXCEPTION(scope, void()); if (!hour.isNull()) { if (isHour12Undefined) { if (hour == "2-digit") skeletonBuilder.appendLiteral("jj"); else if (hour == "numeric") skeletonBuilder.append('j'); } else if (hr12) { if (hour == "2-digit") skeletonBuilder.appendLiteral("hh"); else if (hour == "numeric") skeletonBuilder.append('h'); } else { if (hour == "2-digit") skeletonBuilder.appendLiteral("HH"); else if (hour == "numeric") skeletonBuilder.append('H'); } } String minute = intlStringOption(exec, options, vm.propertyNames->minute, twoDigitNumeric, "minute must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!minute.isNull()) { if (minute == "2-digit") skeletonBuilder.appendLiteral("mm"); else if (minute == "numeric") skeletonBuilder.append('m'); } String second = intlStringOption(exec, options, vm.propertyNames->second, twoDigitNumeric, "second must be \"2-digit\" or \"numeric\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!second.isNull()) { if (second == "2-digit") skeletonBuilder.appendLiteral("ss"); else if (second == "numeric") skeletonBuilder.append('s'); } String timeZoneName = intlStringOption(exec, options, vm.propertyNames->timeZoneName, shortLong, "timeZoneName must be \"short\" or \"long\"", nullptr); RETURN_IF_EXCEPTION(scope, void()); if (!timeZoneName.isNull()) { if (timeZoneName == "short") skeletonBuilder.append('z'); else if (timeZoneName == "long") skeletonBuilder.appendLiteral("zzzz"); } // 24. Let dataLocaleData be Get(localeData, dataLocale). // 25. Let formats be Get(dataLocaleData, "formats"). // 26. Let matcher be GetOption(options, "formatMatcher", "string", «"basic", "best fit"», "best fit"). intlStringOption(exec, options, vm.propertyNames->formatMatcher, { "basic", "best fit" }, "formatMatcher must be either \"basic\" or \"best fit\"", "best fit"); // 27. ReturnIfAbrupt(matcher). RETURN_IF_EXCEPTION(scope, void()); // Always use ICU date format generator, rather than our own pattern list and matcher. // Covers steps 28-36. UErrorCode status = U_ZERO_ERROR; UDateTimePatternGenerator* generator = udatpg_open(dataLocale.utf8().data(), &status); if (U_FAILURE(status)) { throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat")); return; } String skeleton = skeletonBuilder.toString(); StringView skeletonView(skeleton); Vector<UChar, 32> patternBuffer(32); status = U_ZERO_ERROR; auto patternLength = udatpg_getBestPattern(generator, skeletonView.upconvertedCharacters(), skeletonView.length(), patternBuffer.data(), patternBuffer.size(), &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; patternBuffer.grow(patternLength); udatpg_getBestPattern(generator, skeletonView.upconvertedCharacters(), skeletonView.length(), patternBuffer.data(), patternLength, &status); } udatpg_close(generator); if (U_FAILURE(status)) { throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat")); return; } StringView pattern(patternBuffer.data(), patternLength); setFormatsFromPattern(pattern); status = U_ZERO_ERROR; StringView timeZoneView(m_timeZone); m_dateFormat = std::unique_ptr<UDateFormat, UDateFormatDeleter>(udat_open(UDAT_PATTERN, UDAT_PATTERN, m_locale.utf8().data(), timeZoneView.upconvertedCharacters(), timeZoneView.length(), pattern.upconvertedCharacters(), pattern.length(), &status)); if (U_FAILURE(status)) { throwTypeError(&exec, scope, ASCIILiteral("failed to initialize DateTimeFormat")); return; } // 37. Set dateTimeFormat.[[boundFormat]] to undefined. // Already undefined. // 38. Set dateTimeFormat.[[initializedDateTimeFormat]] to true. m_initializedDateTimeFormat = true; // 39. Return dateTimeFormat. }
void IntlPluralRules::initializePluralRules(ExecState& exec, JSValue locales, JSValue optionsValue) { VM& vm = exec.vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 13.1.1 InitializePluralRules (pluralRules, locales, options) // https://tc39.github.io/ecma402/#sec-initializepluralrules Vector<String> requestedLocales = canonicalizeLocaleList(exec, locales); RETURN_IF_EXCEPTION(scope, void()); JSObject* options; if (optionsValue.isUndefined()) options = constructEmptyObject(&exec, exec.lexicalGlobalObject()->nullPrototypeObjectStructure()); else { options = optionsValue.toObject(&exec); RETURN_IF_EXCEPTION(scope, void()); } HashMap<String, String> localeOpt; String localeMatcher = intlStringOption(exec, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit"); RETURN_IF_EXCEPTION(scope, void()); localeOpt.add(vm.propertyNames->localeMatcher.string(), localeMatcher); const HashSet<String> availableLocales = exec.jsCallee()->globalObject(vm)->intlNumberFormatAvailableLocales(); HashMap<String, String> resolved = resolveLocale(exec, availableLocales, requestedLocales, localeOpt, nullptr, 0, IntlPRInternal::localeData); m_locale = resolved.get(vm.propertyNames->locale.string()); if (m_locale.isEmpty()) { throwTypeError(&exec, scope, "failed to initialize PluralRules due to invalid locale"_s); return; } String typeString = intlStringOption(exec, options, Identifier::fromString(&vm, "type"), { "cardinal", "ordinal" }, "type must be \"cardinal\" or \"ordinal\"", "cardinal"); RETURN_IF_EXCEPTION(scope, void()); m_type = typeString == "ordinal" ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL; unsigned minimumIntegerDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "minimumIntegerDigits"), 1, 21, 1); RETURN_IF_EXCEPTION(scope, void()); m_minimumIntegerDigits = minimumIntegerDigits; unsigned minimumFractionDigitsDefault = 0; unsigned minimumFractionDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "minimumFractionDigits"), 0, 20, minimumFractionDigitsDefault); RETURN_IF_EXCEPTION(scope, void()); m_minimumFractionDigits = minimumFractionDigits; unsigned maximumFractionDigitsDefault = std::max(minimumFractionDigits, 3u); unsigned maximumFractionDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "maximumFractionDigits"), minimumFractionDigits, 20, maximumFractionDigitsDefault); RETURN_IF_EXCEPTION(scope, void()); m_maximumFractionDigits = maximumFractionDigits; JSValue minimumSignificantDigitsValue = options->get(&exec, Identifier::fromString(&vm, "minimumSignificantDigits")); RETURN_IF_EXCEPTION(scope, void()); JSValue maximumSignificantDigitsValue = options->get(&exec, Identifier::fromString(&vm, "maximumSignificantDigits")); RETURN_IF_EXCEPTION(scope, void()); if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) { unsigned minimumSignificantDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "minimumSignificantDigits"), 1, 21, 1); RETURN_IF_EXCEPTION(scope, void()); unsigned maximumSignificantDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "maximumSignificantDigits"), minimumSignificantDigits, 21, 21); RETURN_IF_EXCEPTION(scope, void()); m_minimumSignificantDigits = minimumSignificantDigits; m_maximumSignificantDigits = maximumSignificantDigits; } UErrorCode status = U_ZERO_ERROR; m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, m_locale.utf8().data(), nullptr, &status)); if (U_FAILURE(status)) { throwTypeError(&exec, scope, "failed to initialize PluralRules"_s); return; } if (m_minimumSignificantDigits) { unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true); unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits.value()); unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits.value()); } else { unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits); unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits); unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits); } status = U_ZERO_ERROR; m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(m_locale.utf8().data(), m_type, &status)); if (U_FAILURE(status)) { throwTypeError(&exec, scope, "failed to initialize PluralRules"_s); return; } m_initializedPluralRules = true; }