mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-15 08:53:07 +09:00
473 lines
14 KiB
C++
473 lines
14 KiB
C++
/* -*- 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/. */
|
|
|
|
/* Implementation of the Intl.PluralRules proposal. */
|
|
|
|
#include "builtin/intl/PluralRules.h"
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Casting.h"
|
|
|
|
#include "jscntxt.h"
|
|
|
|
#include "builtin/intl/CommonFunctions.h"
|
|
#include "builtin/intl/ICUHeader.h"
|
|
#include "builtin/intl/ScopedICUObject.h"
|
|
#include "vm/GlobalObject.h"
|
|
#include "vm/String.h"
|
|
|
|
#include "jsobjinlines.h"
|
|
|
|
#include "vm/NativeObject-inl.h"
|
|
|
|
using namespace js;
|
|
|
|
using mozilla::AssertedCast;
|
|
|
|
using js::intl::CallICU;
|
|
using js::intl::GetAvailableLocales;
|
|
using js::intl::IcuLocale;
|
|
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
|
|
using js::intl::StringsAreEqual;
|
|
|
|
/**************** PluralRules *****************/
|
|
|
|
const ClassOps PluralRulesObject::classOps_ = {
|
|
nullptr, /* addProperty */
|
|
nullptr, /* delProperty */
|
|
nullptr, /* getProperty */
|
|
nullptr, /* setProperty */
|
|
nullptr, /* enumerate */
|
|
nullptr, /* resolve */
|
|
nullptr, /* mayResolve */
|
|
PluralRulesObject::finalize
|
|
};
|
|
|
|
const Class PluralRulesObject::class_ = {
|
|
js_Object_str,
|
|
JSCLASS_HAS_RESERVED_SLOTS(PluralRulesObject::SLOT_COUNT) |
|
|
JSCLASS_FOREGROUND_FINALIZE,
|
|
&PluralRulesObject::classOps_
|
|
};
|
|
|
|
#if JS_HAS_TOSOURCE
|
|
static bool
|
|
pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
args.rval().setString(cx->names().PluralRules);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static const JSFunctionSpec pluralRules_static_methods[] = {
|
|
JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_PluralRules_supportedLocalesOf", 1, 0),
|
|
JS_FS_END
|
|
};
|
|
|
|
static const JSFunctionSpec pluralRules_methods[] = {
|
|
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_PluralRules_resolvedOptions", 0, 0),
|
|
JS_SELF_HOSTED_FN("select", "Intl_PluralRules_select", 1, 0),
|
|
#if JS_HAS_TOSOURCE
|
|
JS_FN(js_toSource_str, pluralRules_toSource, 0, 0),
|
|
#endif
|
|
JS_FS_END
|
|
};
|
|
|
|
/**
|
|
* PluralRules constructor.
|
|
* Spec: ECMAScript 402 API, PluralRules, 1.1
|
|
*/
|
|
static bool
|
|
PluralRules(JSContext* cx, const CallArgs& args, bool construct)
|
|
{
|
|
RootedObject obj(cx);
|
|
|
|
if (!construct) {
|
|
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
|
|
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, GlobalObject::getOrCreatePluralRulesPrototype(cx, cx->global()));
|
|
if (!proto)
|
|
return false;
|
|
obj = NewObjectWithGivenProto<PluralRulesObject>(cx, proto);
|
|
if (!obj)
|
|
return false;
|
|
|
|
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
|
|
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
|
|
}
|
|
|
|
RootedValue locales(cx, args.get(0));
|
|
RootedValue options(cx, args.get(1));
|
|
|
|
if (!intl::InitializeObject(cx, obj, cx->names().InitializePluralRules, locales, options))
|
|
return false;
|
|
|
|
args.rval().setObject(*obj);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
PluralRules(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
return PluralRules(cx, args, args.isConstructing());
|
|
}
|
|
|
|
bool
|
|
js::intl_PluralRules(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
MOZ_ASSERT(args.length() == 2);
|
|
return PluralRules(cx, args, true);
|
|
}
|
|
|
|
void
|
|
js::PluralRulesObject::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<PluralRulesObject>().getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT);
|
|
if (!slot.isUndefined()) {
|
|
if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
|
|
uplrules_close(pr);
|
|
}
|
|
}
|
|
|
|
JSObject*
|
|
js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
|
|
{
|
|
RootedFunction ctor(cx);
|
|
ctor = global->createConstructor(cx, &PluralRules, cx->names().PluralRules, 0);
|
|
if (!ctor)
|
|
return nullptr;
|
|
|
|
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &PluralRulesObject::class_));
|
|
if (!proto)
|
|
return nullptr;
|
|
proto->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
|
|
|
|
if (!LinkConstructorAndPrototype(cx, ctor, proto))
|
|
return nullptr;
|
|
|
|
if (!JS_DefineFunctions(cx, ctor, pluralRules_static_methods))
|
|
return nullptr;
|
|
|
|
if (!JS_DefineFunctions(cx, proto, pluralRules_methods))
|
|
return nullptr;
|
|
|
|
RootedValue options(cx);
|
|
if (!intl::CreateDefaultOptions(cx, &options))
|
|
return nullptr;
|
|
|
|
if (!intl::InitializeObject(cx, proto, cx->names().InitializePluralRules, UndefinedHandleValue,
|
|
options))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
RootedValue ctorValue(cx, ObjectValue(*ctor));
|
|
if (!DefineProperty(cx, Intl, cx->names().PluralRules, ctorValue, nullptr, nullptr, 0))
|
|
return nullptr;
|
|
|
|
return proto;
|
|
}
|
|
|
|
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 (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
|
|
return false;
|
|
args.rval().set(result);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* This creates new UNumberFormat with calculated digit formatting
|
|
* properties for PluralRules.
|
|
*
|
|
* This is similar to NewUNumberFormat but doesn't allow for currency or
|
|
* percent types.
|
|
*
|
|
*/
|
|
static UNumberFormat*
|
|
NewUNumberFormatForPluralRules(JSContext* cx, HandleObject 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());
|
|
if (!locale)
|
|
return nullptr;
|
|
|
|
uint32_t uMinimumIntegerDigits = 1;
|
|
uint32_t uMinimumFractionDigits = 0;
|
|
uint32_t uMaximumFractionDigits = 3;
|
|
int32_t uMinimumSignificantDigits = -1;
|
|
int32_t uMaximumSignificantDigits = -1;
|
|
|
|
RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
|
|
bool hasP;
|
|
if (!HasProperty(cx, internals, id, &hasP))
|
|
return nullptr;
|
|
if (hasP) {
|
|
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
|
|
&value))
|
|
return nullptr;
|
|
uMinimumSignificantDigits = value.toInt32();
|
|
|
|
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
|
|
&value))
|
|
return nullptr;
|
|
uMaximumSignificantDigits = value.toInt32();
|
|
} else {
|
|
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
|
|
&value))
|
|
return nullptr;
|
|
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
|
|
|
|
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
|
|
&value))
|
|
return nullptr;
|
|
uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
|
|
|
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);
|
|
if (U_FAILURE(status)) {
|
|
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);
|
|
} else {
|
|
unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
|
|
unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
|
|
unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
|
|
}
|
|
|
|
return toClose.forget();
|
|
}
|
|
|
|
bool
|
|
js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
RootedObject pluralRules(cx, &args[0].toObject());
|
|
|
|
UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
|
|
if (!nf)
|
|
return false;
|
|
|
|
ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
|
|
|
|
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());
|
|
if (!locale)
|
|
return false;
|
|
|
|
if (!GetProperty(cx, internals, internals, cx->names().type, &value))
|
|
return false;
|
|
JSAutoByteString type(cx, value.toString());
|
|
if (!type)
|
|
return false;
|
|
|
|
double x = args[1].toNumber();
|
|
|
|
// We need a NumberFormat in order to format the number
|
|
// using the number formatting options (minimum/maximum*Digits)
|
|
// before we push the result to PluralRules
|
|
//
|
|
// This should be fixed in ICU 59 and we'll be able to switch to that
|
|
// API: http://bugs.icu-project.org/trac/ticket/12763
|
|
//
|
|
RootedValue fmtNumValue(cx);
|
|
if (!intl_FormatNumber(cx, nf, x, &fmtNumValue))
|
|
return false;
|
|
RootedString fmtNumValueString(cx, fmtNumValue.toString());
|
|
AutoStableStringChars stableChars(cx);
|
|
if (!stableChars.initTwoByte(cx, fmtNumValueString))
|
|
return false;
|
|
|
|
const UChar* uFmtNumValue = Char16ToUChar(stableChars.twoByteRange().begin().get());
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
UFormattable* fmt = unum_parseToUFormattable(nf, nullptr, uFmtNumValue,
|
|
stableChars.twoByteRange().length(), 0, &status);
|
|
if (U_FAILURE(status)) {
|
|
intl::ReportInternalError(cx);
|
|
return false;
|
|
}
|
|
|
|
ScopedICUObject<UFormattable, ufmt_close> closeUFormattable(fmt);
|
|
|
|
double y = ufmt_getDouble(fmt, &status);
|
|
if (U_FAILURE(status)) {
|
|
intl::ReportInternalError(cx);
|
|
return false;
|
|
}
|
|
|
|
UPluralType category;
|
|
|
|
if (StringsAreEqual(type, "cardinal")) {
|
|
category = UPLURAL_TYPE_CARDINAL;
|
|
} else {
|
|
MOZ_ASSERT(StringsAreEqual(type, "ordinal"));
|
|
category = UPLURAL_TYPE_ORDINAL;
|
|
}
|
|
|
|
UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
|
|
if (U_FAILURE(status)) {
|
|
intl::ReportInternalError(cx);
|
|
return false;
|
|
}
|
|
|
|
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
|
|
|
|
JSString* str = CallICU(cx, [pr, y](UChar* chars, int32_t size, UErrorCode* status) {
|
|
return uplrules_select(pr, y, chars, size, status);
|
|
});
|
|
if (!str)
|
|
return false;
|
|
|
|
args.rval().setString(str);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
MOZ_ASSERT(args.length() == 2);
|
|
|
|
JSAutoByteString locale(cx, args[0].toString());
|
|
if (!locale)
|
|
return false;
|
|
|
|
JSAutoByteString type(cx, args[1].toString());
|
|
if (!type)
|
|
return false;
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
UPluralType category;
|
|
|
|
if (StringsAreEqual(type, "cardinal")) {
|
|
category = UPLURAL_TYPE_CARDINAL;
|
|
} else {
|
|
MOZ_ASSERT(StringsAreEqual(type, "ordinal"));
|
|
category = UPLURAL_TYPE_ORDINAL;
|
|
}
|
|
|
|
UPluralRules* pr = uplrules_openForType(
|
|
IcuLocale(locale.ptr()),
|
|
category,
|
|
&status
|
|
);
|
|
if (U_FAILURE(status)) {
|
|
intl::ReportInternalError(cx);
|
|
return false;
|
|
}
|
|
|
|
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
|
|
|
|
// We should get a C API for that in ICU 59 and switch to it
|
|
// https://ssl.icu-project.org/trac/ticket/12772
|
|
//
|
|
icu::StringEnumeration* kwenum =
|
|
reinterpret_cast<icu::PluralRules*>(pr)->getKeywords(status);
|
|
UEnumeration* ue = uenum_openFromStringEnumeration(kwenum, &status);
|
|
|
|
if (U_FAILURE(status)) {
|
|
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;
|
|
int32_t catSize;
|
|
const char* cat;
|
|
|
|
do {
|
|
cat = uenum_next(ue, &catSize, &status);
|
|
if (U_FAILURE(status)) {
|
|
intl::ReportInternalError(cx);
|
|
return false;
|
|
}
|
|
|
|
if (!cat)
|
|
break;
|
|
|
|
JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
|
|
if (!str)
|
|
return false;
|
|
|
|
element.setString(str);
|
|
if (!DefineElement(cx, res, i, element))
|
|
return false;
|
|
i++;
|
|
} while (true);
|
|
|
|
args.rval().setObject(*res);
|
|
return true;
|
|
}
|