зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1328386 - Part 6: Implement legacy constructor semantics for Intl.NumberFormat per ECMA-402, 4th edition. r=Waldo
This commit is contained in:
Родитель
8bfe298d5f
Коммит
26c8fa8110
|
@ -852,7 +852,7 @@ struct JSClass {
|
|||
// 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 \
|
||||
|
|
|
@ -806,7 +806,32 @@ IntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initialize
|
|||
|
||||
RootedValue thisv(cx, NullValue());
|
||||
RootedValue ignored(cx);
|
||||
return js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored);
|
||||
if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(ignored.isUndefined(),
|
||||
"Unexpected return value from non-legacy Intl object initializer");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
|
||||
HandleValue thisValue, HandleValue locales, HandleValue options,
|
||||
MutableHandleValue result)
|
||||
{
|
||||
FixedInvokeArgs<4> args(cx);
|
||||
|
||||
args[0].setObject(*obj);
|
||||
args[1].set(thisValue);
|
||||
args[2].set(locales);
|
||||
args[3].set(options);
|
||||
|
||||
RootedValue thisv(cx, NullValue());
|
||||
if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -1426,64 +1451,33 @@ static const JSPropertySpec numberFormat_properties[] = {
|
|||
static bool
|
||||
NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
|
||||
{
|
||||
RootedObject obj(cx);
|
||||
// Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
|
||||
|
||||
// We're following ECMA-402 1st Edition when NumberFormat is called
|
||||
// because of backward compatibility issues.
|
||||
// See https://github.com/tc39/ecma402/issues/57
|
||||
if (!construct) {
|
||||
// ES Intl 1st ed., 11.1.2.1 step 3
|
||||
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
|
||||
if (!intl)
|
||||
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
|
||||
RootedObject proto(cx);
|
||||
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
|
||||
return false;
|
||||
|
||||
if (!proto) {
|
||||
proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
|
||||
if (!proto)
|
||||
return false;
|
||||
RootedValue self(cx, args.thisv());
|
||||
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
|
||||
// ES Intl 1st ed., 11.1.2.1 step 4
|
||||
obj = ToObject(cx, self);
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
// ES Intl 1st ed., 11.1.2.1 step 5
|
||||
bool extensible;
|
||||
if (!IsExtensible(cx, obj, &extensible))
|
||||
return false;
|
||||
if (!extensible)
|
||||
return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
|
||||
} else {
|
||||
// ES Intl 1st ed., 11.1.2.1 step 3.a
|
||||
construct = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (construct) {
|
||||
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
|
||||
RootedObject proto(cx);
|
||||
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
|
||||
return false;
|
||||
Rooted<NumberFormatObject*> numberFormat(cx);
|
||||
numberFormat = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
|
||||
if (!numberFormat)
|
||||
return false;
|
||||
|
||||
if (!proto) {
|
||||
proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
|
||||
if (!proto)
|
||||
return false;
|
||||
}
|
||||
|
||||
obj = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
obj->as<NumberFormatObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT,
|
||||
PrivateValue(nullptr));
|
||||
}
|
||||
numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
|
||||
|
||||
RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv());
|
||||
RootedValue locales(cx, args.get(0));
|
||||
RootedValue options(cx, args.get(1));
|
||||
|
||||
// Step 3.
|
||||
if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options))
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
return LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
|
||||
locales, options, args.rval());
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -1523,7 +1517,8 @@ NumberFormatObject::finalize(FreeOp* fop, JSObject* obj)
|
|||
}
|
||||
|
||||
static JSObject*
|
||||
CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
|
||||
CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
|
||||
MutableHandleObject constructor)
|
||||
{
|
||||
RootedFunction ctor(cx);
|
||||
ctor = GlobalObject::createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0);
|
||||
|
@ -1574,17 +1569,20 @@ CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
|
|||
return nullptr;
|
||||
|
||||
// 11.2.1 and 11.3
|
||||
if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue,
|
||||
options))
|
||||
RootedValue thisOrResult(cx, ObjectValue(*proto));
|
||||
if (!LegacyIntlInitialize(cx, proto, cx->names().InitializeNumberFormat, thisOrResult,
|
||||
UndefinedHandleValue, options, &thisOrResult))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(&thisOrResult.toObject() == proto);
|
||||
|
||||
// 8.1
|
||||
RootedValue ctorValue(cx, ObjectValue(*ctor));
|
||||
if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0))
|
||||
return nullptr;
|
||||
|
||||
constructor.set(ctor);
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
@ -1713,7 +1711,7 @@ NewUNumberFormatForPluralRules(JSContext* cx, Handle<PluralRulesObject*> pluralR
|
|||
* of the given NumberFormat.
|
||||
*/
|
||||
static UNumberFormat*
|
||||
NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
|
||||
NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
|
||||
{
|
||||
RootedValue value(cx);
|
||||
|
||||
|
@ -2326,56 +2324,30 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
|
|||
MOZ_ASSERT(args[1].isNumber());
|
||||
MOZ_ASSERT(args[2].isBoolean());
|
||||
|
||||
RootedObject numberFormat(cx, &args[0].toObject());
|
||||
Rooted<NumberFormatObject*> numberFormat(cx, &args[0].toObject().as<NumberFormatObject>());
|
||||
|
||||
// Obtain a UNumberFormat object, cached if possible.
|
||||
bool isNumberFormatInstance = numberFormat->is<NumberFormatObject>();
|
||||
UNumberFormat* nf;
|
||||
if (isNumberFormatInstance) {
|
||||
void* priv =
|
||||
numberFormat->as<NumberFormatObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT)
|
||||
.toPrivate();
|
||||
nf = static_cast<UNumberFormat*>(priv);
|
||||
if (!nf) {
|
||||
nf = NewUNumberFormat(cx, numberFormat);
|
||||
if (!nf)
|
||||
return false;
|
||||
numberFormat->as<NumberFormatObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT,
|
||||
PrivateValue(nf));
|
||||
}
|
||||
} else {
|
||||
// There's no good place to cache the ICU number format for an object
|
||||
// that has been initialized as a NumberFormat but is not a
|
||||
// NumberFormat instance. One possibility might be to add a
|
||||
// NumberFormat instance as an internal property to each such object.
|
||||
// Obtain a cached UNumberFormat object.
|
||||
void* priv =
|
||||
numberFormat->getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT).toPrivate();
|
||||
UNumberFormat* nf = static_cast<UNumberFormat*>(priv);
|
||||
if (!nf) {
|
||||
nf = NewUNumberFormat(cx, numberFormat);
|
||||
if (!nf)
|
||||
return false;
|
||||
numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nf));
|
||||
}
|
||||
|
||||
// Use the UNumberFormat to actually format the number.
|
||||
double d = args[1].toNumber();
|
||||
RootedValue result(cx);
|
||||
|
||||
bool success;
|
||||
#if defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
|
||||
if (args[2].toBoolean()) {
|
||||
success = intl_FormatNumberToParts(cx, nf, d, &result);
|
||||
} else
|
||||
#endif // defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
|
||||
{
|
||||
MOZ_ASSERT(!args[2].toBoolean(),
|
||||
"shouldn't be doing formatToParts without an ICU that "
|
||||
"supports it");
|
||||
success = intl_FormatNumber(cx, nf, d, &result);
|
||||
return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval());
|
||||
}
|
||||
|
||||
if (!isNumberFormatInstance)
|
||||
unum_close(nf);
|
||||
if (!success)
|
||||
return false;
|
||||
args.rval().set(result);
|
||||
return true;
|
||||
#else
|
||||
MOZ_ASSERT(!args[2].toBoolean(),
|
||||
"shouldn't be doing formatToParts without an ICU that "
|
||||
"supports it");
|
||||
#endif // defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
|
||||
return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval());
|
||||
}
|
||||
|
||||
|
||||
|
@ -4375,7 +4347,8 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
|
|||
RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
|
||||
if (!dateTimeFormatProto)
|
||||
return false;
|
||||
RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
|
||||
RootedObject numberFormatProto(cx), numberFormat(cx);
|
||||
numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat);
|
||||
if (!numberFormatProto)
|
||||
return false;
|
||||
|
||||
|
@ -4398,6 +4371,7 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
|
|||
// 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, ObjectValue(*numberFormat));
|
||||
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
|
||||
|
||||
// Also cache |Intl| to implement spec language that conditions behavior
|
||||
|
|
|
@ -1852,10 +1852,12 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
|
|||
|
||||
|
||||
/**
|
||||
* Returns an object containing the NumberFormat internal properties of |obj|,
|
||||
* or throws a TypeError if |obj| isn't NumberFormat-initialized.
|
||||
* Returns an object containing the NumberFormat internal properties of |obj|.
|
||||
*/
|
||||
function getNumberFormatInternals(obj, methodName) {
|
||||
assert(IsObject(obj), "getNumberFormatInternals called with non-object");
|
||||
assert(IsNumberFormat(obj), "getNumberFormatInternals called with non-NumberFormat");
|
||||
|
||||
var internals = getIntlObjectInternals(obj, "NumberFormat", methodName);
|
||||
assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals");
|
||||
|
||||
|
@ -1870,6 +1872,25 @@ function getNumberFormatInternals(obj, methodName) {
|
|||
return internalProps;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* UnwrapNumberFormat(nf)
|
||||
*/
|
||||
function UnwrapNumberFormat(nf, methodName) {
|
||||
// Step 1.
|
||||
if ((!IsObject(nf) || !IsNumberFormat(nf)) && nf instanceof GetNumberFormatConstructor()) {
|
||||
nf = nf[intlFallbackSymbol()];
|
||||
}
|
||||
|
||||
// Step 2.
|
||||
if (!IsObject(nf) || !IsNumberFormat(nf))
|
||||
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "NumberFormat", methodName, "NumberFormat");
|
||||
|
||||
// Step 3.
|
||||
return nf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies digit options used for number formatting onto the intl object.
|
||||
*
|
||||
|
@ -1920,12 +1941,13 @@ function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault, mxfdDefault
|
|||
*
|
||||
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
|
||||
*/
|
||||
function InitializeNumberFormat(numberFormat, locales, options) {
|
||||
assert(IsObject(numberFormat), "InitializeNumberFormat");
|
||||
function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
|
||||
assert(IsObject(numberFormat), "InitializeNumberFormat called with non-object");
|
||||
assert(IsNumberFormat(numberFormat), "InitializeNumberFormat called with non-NumberFormat");
|
||||
|
||||
// Step 1.
|
||||
if (isInitializedIntlObject(numberFormat))
|
||||
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
|
||||
// Steps 1-2 (These steps are no longer required and should be removed
|
||||
// from the spec; https://github.com/tc39/ecma402/issues/115).
|
||||
assert(!isInitializedIntlObject(numberFormat), "numberFormat mustn't be initialized");
|
||||
|
||||
// Step 2.
|
||||
var internals = initializeIntlObject(numberFormat);
|
||||
|
@ -2031,6 +2053,18 @@ function InitializeNumberFormat(numberFormat, locales, options) {
|
|||
// We've done everything that must be done now: mark the lazy data as fully
|
||||
// computed and install it.
|
||||
setLazyData(internals, "NumberFormat", lazyNumberFormatData);
|
||||
|
||||
if (numberFormat !== thisValue && thisValue instanceof GetNumberFormatConstructor()) {
|
||||
if (!IsObject(thisValue))
|
||||
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
|
||||
|
||||
_DefineDataProperty(thisValue, intlFallbackSymbol(), numberFormat,
|
||||
ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
|
||||
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
return numberFormat;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2127,29 +2161,32 @@ function numberFormatFormatToBind(value) {
|
|||
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
|
||||
*/
|
||||
function Intl_NumberFormat_format_get() {
|
||||
// Check "this NumberFormat object" per introduction of section 11.3.
|
||||
var internals = getNumberFormatInternals(this, "format");
|
||||
// Steps 1-3.
|
||||
var nf = UnwrapNumberFormat(this, "format");
|
||||
|
||||
// Step 1.
|
||||
var internals = getNumberFormatInternals(nf, "format");
|
||||
|
||||
// Step 4.
|
||||
if (internals.boundFormat === undefined) {
|
||||
// Step 1.a.
|
||||
// Step 4.a.
|
||||
var F = numberFormatFormatToBind;
|
||||
|
||||
// Step 1.b-d.
|
||||
var bf = callFunction(FunctionBind, F, this);
|
||||
// Steps 4.b-d.
|
||||
var bf = callFunction(FunctionBind, F, nf);
|
||||
internals.boundFormat = bf;
|
||||
}
|
||||
// Step 2.
|
||||
|
||||
// Step 5.
|
||||
return internals.boundFormat;
|
||||
}
|
||||
_SetCanonicalName(Intl_NumberFormat_format_get, "get format");
|
||||
|
||||
|
||||
function Intl_NumberFormat_formatToParts(value) {
|
||||
// Step 1.
|
||||
var nf = this;
|
||||
// Steps 1-3.
|
||||
var nf = UnwrapNumberFormat(this, "formatToParts");
|
||||
|
||||
// Steps 2-3.
|
||||
// Ensure the NumberFormat internals are resolved.
|
||||
getNumberFormatInternals(nf, "formatToParts");
|
||||
|
||||
// Step 4.
|
||||
|
@ -2166,8 +2203,10 @@ function Intl_NumberFormat_formatToParts(value) {
|
|||
* Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4.
|
||||
*/
|
||||
function Intl_NumberFormat_resolvedOptions() {
|
||||
// Check "this NumberFormat object" per introduction of section 11.3.
|
||||
var internals = getNumberFormatInternals(this, "resolvedOptions");
|
||||
// Invoke |UnwrapNumberFormat| per introduction of section 11.3.
|
||||
var nf = UnwrapNumberFormat(this, "resolvedOptions");
|
||||
|
||||
var internals = getNumberFormatInternals(nf, "resolvedOptions");
|
||||
|
||||
var result = {
|
||||
locale: internals.locale,
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
_(AtomicsIsLockFree) \
|
||||
\
|
||||
_(IntlIsCollator) \
|
||||
_(IntlIsNumberFormat) \
|
||||
_(IntlIsPluralRules) \
|
||||
\
|
||||
_(MathAbs) \
|
||||
|
|
|
@ -111,6 +111,8 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
|
|||
// Intl natives.
|
||||
case InlinableNative::IntlIsCollator:
|
||||
return inlineHasClass(callInfo, &CollatorObject::class_);
|
||||
case InlinableNative::IntlIsNumberFormat:
|
||||
return inlineHasClass(callInfo, &NumberFormatObject::class_);
|
||||
case InlinableNative::IntlIsPluralRules:
|
||||
return inlineHasClass(callInfo, &PluralRulesObject::class_);
|
||||
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
|
||||
|
||||
function IsConstructor(o) {
|
||||
try {
|
||||
new (new Proxy(o, {construct: () => ({})}));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function IsObject(o) {
|
||||
return Object(o) === o;
|
||||
}
|
||||
|
||||
function thisValues() {
|
||||
const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
|
||||
|
||||
return [
|
||||
// Primitive values.
|
||||
...[undefined, null, true, "abc", Symbol(), 123],
|
||||
|
||||
// Object values.
|
||||
...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
|
||||
|
||||
// Intl objects.
|
||||
...[].concat(...intlConstructors.map(ctor => [
|
||||
// Instance of an Intl constructor.
|
||||
new ctor(),
|
||||
|
||||
// Instance of a subclassed Intl constructor.
|
||||
new class extends ctor {},
|
||||
|
||||
// Object inheriting from an Intl constructor prototype.
|
||||
Object.create(ctor.prototype),
|
||||
|
||||
// Intl object not inheriting from its default prototype.
|
||||
Object.setPrototypeOf(new ctor(), Object.prototype),
|
||||
])),
|
||||
];
|
||||
}
|
||||
|
||||
const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
|
||||
|
||||
// Invoking [[Call]] for Intl.NumberFormat returns a new instance unless called
|
||||
// with an instance inheriting from Intl.NumberFormat.prototype.
|
||||
for (let thisValue of thisValues()) {
|
||||
let obj = Intl.NumberFormat.call(thisValue);
|
||||
|
||||
if (!Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) {
|
||||
assertEq(Object.is(obj, thisValue), false);
|
||||
assertEq(obj instanceof Intl.NumberFormat, true);
|
||||
if (IsObject(thisValue))
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
|
||||
} else {
|
||||
assertEq(Object.is(obj, thisValue), true);
|
||||
assertEq(obj instanceof Intl.NumberFormat, true);
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
|
||||
}
|
||||
}
|
||||
|
||||
// Intl.NumberFormat uses the legacy Intl constructor compromise semantics.
|
||||
// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns true.
|
||||
for (let thisValue of thisValues()) {
|
||||
let hasInstanceCalled = false;
|
||||
Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
|
||||
value() {
|
||||
assertEq(hasInstanceCalled, false);
|
||||
hasInstanceCalled = true;
|
||||
return true;
|
||||
}, configurable: true
|
||||
});
|
||||
if (!IsObject(thisValue)) {
|
||||
// A TypeError is thrown when Intl.NumberFormat tries to install the
|
||||
// [[FallbackSymbol]] property on |thisValue|.
|
||||
assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
|
||||
delete Intl.NumberFormat[Symbol.hasInstance];
|
||||
} else {
|
||||
let obj = Intl.NumberFormat.call(thisValue);
|
||||
delete Intl.NumberFormat[Symbol.hasInstance];
|
||||
assertEq(Object.is(obj, thisValue), true);
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
|
||||
}
|
||||
assertEq(hasInstanceCalled, true);
|
||||
}
|
||||
// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns false.
|
||||
for (let thisValue of thisValues()) {
|
||||
let hasInstanceCalled = false;
|
||||
Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
|
||||
value() {
|
||||
assertEq(hasInstanceCalled, false);
|
||||
hasInstanceCalled = true;
|
||||
return false;
|
||||
}, configurable: true
|
||||
});
|
||||
let obj = Intl.NumberFormat.call(thisValue);
|
||||
delete Intl.NumberFormat[Symbol.hasInstance];
|
||||
assertEq(Object.is(obj, thisValue), false);
|
||||
assertEq(obj instanceof Intl.NumberFormat, true);
|
||||
if (IsObject(thisValue))
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
|
||||
assertEq(hasInstanceCalled, true);
|
||||
}
|
||||
|
||||
// Throws an error when attempting to install [[FallbackSymbol]] twice.
|
||||
{
|
||||
let thisValue = Object.create(Intl.NumberFormat.prototype);
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
|
||||
|
||||
assertEq(Intl.NumberFormat.call(thisValue), thisValue);
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
|
||||
|
||||
assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
|
||||
}
|
||||
|
||||
// Throws an error when the thisValue is non-extensible.
|
||||
{
|
||||
let thisValue = Object.create(Intl.NumberFormat.prototype);
|
||||
Object.preventExtensions(thisValue);
|
||||
|
||||
assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
|
||||
}
|
||||
|
||||
// [[FallbackSymbol]] is installed as a frozen property holding an Intl.NumberFormat instance.
|
||||
{
|
||||
let thisValue = Object.create(Intl.NumberFormat.prototype);
|
||||
Intl.NumberFormat.call(thisValue);
|
||||
|
||||
let desc = Object.getOwnPropertyDescriptor(thisValue, intlFallbackSymbol);
|
||||
assertEq(desc !== undefined, true);
|
||||
assertEq(desc.writable, false);
|
||||
assertEq(desc.enumerable, false);
|
||||
assertEq(desc.configurable, false);
|
||||
assertEq(desc.value instanceof Intl.NumberFormat, true);
|
||||
}
|
||||
|
||||
// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]]
|
||||
// during initialization.
|
||||
{
|
||||
let thisValue = {};
|
||||
let options = {
|
||||
get useGrouping() {
|
||||
Object.setPrototypeOf(thisValue, Intl.NumberFormat.prototype);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let obj = Intl.NumberFormat.call(thisValue, undefined, options);
|
||||
assertEq(Object.is(obj, thisValue), true);
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
|
||||
}
|
||||
{
|
||||
let thisValue = Object.create(Intl.NumberFormat.prototype);
|
||||
let options = {
|
||||
get useGrouping() {
|
||||
Object.setPrototypeOf(thisValue, Object.prototype);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let obj = Intl.NumberFormat.call(thisValue, undefined, options);
|
||||
assertEq(Object.is(obj, thisValue), false);
|
||||
assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
|
@ -0,0 +1,226 @@
|
|||
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
|
||||
|
||||
// Test UnwrapNumberFormat operation.
|
||||
|
||||
const numberFormatFunctions = [];
|
||||
numberFormatFunctions.push(Intl.NumberFormat.prototype.resolvedOptions);
|
||||
numberFormatFunctions.push(Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get);
|
||||
// "formatToParts" isn't yet enabled by default.
|
||||
if ("formatToParts" in Intl.NumberFormat.prototype)
|
||||
numberFormatFunctions.push(Intl.NumberFormat.prototype.formatToParts);
|
||||
|
||||
function IsConstructor(o) {
|
||||
try {
|
||||
new (new Proxy(o, {construct: () => ({})}));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function IsObject(o) {
|
||||
return Object(o) === o;
|
||||
}
|
||||
|
||||
function intlObjects(ctor) {
|
||||
return [
|
||||
// Instance of an Intl constructor.
|
||||
new ctor(),
|
||||
|
||||
// Instance of a subclassed Intl constructor.
|
||||
new class extends ctor {},
|
||||
|
||||
// Intl object not inheriting from its default prototype.
|
||||
Object.setPrototypeOf(new ctor(), Object.prototype),
|
||||
];
|
||||
}
|
||||
|
||||
function thisValues(C) {
|
||||
const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
|
||||
|
||||
return [
|
||||
// Primitive values.
|
||||
...[undefined, null, true, "abc", Symbol(), 123],
|
||||
|
||||
// Object values.
|
||||
...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
|
||||
|
||||
// Intl objects.
|
||||
...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)),
|
||||
|
||||
// Object inheriting from an Intl constructor prototype.
|
||||
...intlConstructors.map(ctor => Object.create(ctor.prototype)),
|
||||
];
|
||||
}
|
||||
|
||||
const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
|
||||
|
||||
// Test Intl.NumberFormat.prototype methods.
|
||||
for (let numberFormatFunction of numberFormatFunctions) {
|
||||
// Test a TypeError is thrown when the this-value isn't an initialized
|
||||
// Intl.NumberFormat instance.
|
||||
for (let thisValue of thisValues(Intl.NumberFormat)) {
|
||||
assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
|
||||
}
|
||||
|
||||
// And test no error is thrown for initialized Intl.NumberFormat instances.
|
||||
for (let thisValue of intlObjects(Intl.NumberFormat)) {
|
||||
numberFormatFunction.call(thisValue);
|
||||
}
|
||||
|
||||
// Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
|
||||
for (let thisValue of thisValues(Intl.NumberFormat)) {
|
||||
assertThrowsInstanceOf(() => numberFormatFunction.call({
|
||||
__proto__: Intl.NumberFormat.prototype,
|
||||
[intlFallbackSymbol]: thisValue,
|
||||
}), TypeError);
|
||||
}
|
||||
|
||||
for (let thisValue of intlObjects(Intl.NumberFormat)) {
|
||||
numberFormatFunction.call({
|
||||
__proto__: Intl.NumberFormat.prototype,
|
||||
[intlFallbackSymbol]: thisValue,
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances.
|
||||
for (let thisValue of intlObjects(Intl.NumberFormat)) {
|
||||
Object.defineProperty(thisValue, intlFallbackSymbol, {
|
||||
get() { assertEq(false, true); }
|
||||
});
|
||||
numberFormatFunction.call(thisValue);
|
||||
}
|
||||
|
||||
// Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype.
|
||||
for (let thisValue of thisValues(Intl.NumberFormat)) {
|
||||
if (!IsObject(thisValue) || Intl.NumberFormat.prototype.isPrototypeOf(thisValue))
|
||||
continue;
|
||||
Object.defineProperty(thisValue, intlFallbackSymbol, {
|
||||
get() { assertEq(false, true); }
|
||||
});
|
||||
assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
|
||||
}
|
||||
|
||||
// Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance]
|
||||
// so it always returns |null|.
|
||||
for (let thisValue of thisValues(Intl.NumberFormat)) {
|
||||
let hasInstanceCalled = false, symbolGetterCalled = false;
|
||||
Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
|
||||
value() {
|
||||
assertEq(hasInstanceCalled, false);
|
||||
hasInstanceCalled = true;
|
||||
return true;
|
||||
}, configurable: true
|
||||
});
|
||||
let isUndefinedOrNull = thisValue !== undefined || thisValue !== null;
|
||||
let symbolHolder;
|
||||
if (!isUndefinedOrNull) {
|
||||
symbolHolder = IsObject(thisValue) ? thisValue : Object.getPrototypeOf(thisValue);
|
||||
Object.defineProperty(symbolHolder, intlFallbackSymbol, {
|
||||
get() {
|
||||
assertEq(symbolGetterCalled, false);
|
||||
symbolGetterCalled = true;
|
||||
return null;
|
||||
}, configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
|
||||
|
||||
delete Intl.NumberFormat[Symbol.hasInstance];
|
||||
if (!isUndefinedOrNull && !IsObject(thisValue))
|
||||
delete symbolHolder[intlFallbackSymbol];
|
||||
|
||||
assertEq(hasInstanceCalled, true);
|
||||
assertEq(symbolGetterCalled, !isUndefinedOrNull);
|
||||
}
|
||||
}
|
||||
|
||||
// Test format() returns the correct result for objects initialized as Intl.NumberFormat instances.
|
||||
{
|
||||
// An actual Intl.NumberFormat instance.
|
||||
let numberFormat = new Intl.NumberFormat();
|
||||
|
||||
// An object initialized as a NumberFormat instance.
|
||||
let thisValue = Object.create(Intl.NumberFormat.prototype);
|
||||
Intl.NumberFormat.call(thisValue);
|
||||
|
||||
// Object with [[FallbackSymbol]] set to NumberFormat instance.
|
||||
let fakeObj = {
|
||||
__proto__: Intl.NumberFormat.prototype,
|
||||
[intlFallbackSymbol]: numberFormat,
|
||||
};
|
||||
|
||||
for (let number of [0, 1, 1.5, Infinity, NaN]) {
|
||||
let expected = numberFormat.format(number);
|
||||
assertEq(thisValue.format(number), expected);
|
||||
assertEq(thisValue[intlFallbackSymbol].format(number), expected);
|
||||
assertEq(fakeObj.format(number), expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Test formatToParts() returns the correct result for objects initialized as Intl.NumberFormat instances.
|
||||
if ("formatToParts" in Intl.NumberFormat.prototype) {
|
||||
// An actual Intl.NumberFormat instance.
|
||||
let numberFormat = new Intl.NumberFormat();
|
||||
|
||||
// An object initialized as a NumberFormat instance.
|
||||
let thisValue = Object.create(Intl.NumberFormat.prototype);
|
||||
Intl.NumberFormat.call(thisValue);
|
||||
|
||||
// Object with [[FallbackSymbol]] set to NumberFormat instance.
|
||||
let fakeObj = {
|
||||
__proto__: Intl.NumberFormat.prototype,
|
||||
[intlFallbackSymbol]: numberFormat,
|
||||
};
|
||||
|
||||
function assertEqParts(actual, expected) {
|
||||
assertEq(actual.length, expected.length, "parts count mismatch");
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
assertEq(actual[i].type, expected[i].type, "type mismatch at " + i);
|
||||
assertEq(actual[i].value, expected[i].value, "value mismatch at " + i);
|
||||
}
|
||||
}
|
||||
|
||||
for (let number of [0, 1, 1.5, Infinity, NaN]) {
|
||||
let expected = numberFormat.formatToParts(number);
|
||||
assertEqParts(thisValue.formatToParts(number), expected);
|
||||
assertEqParts(thisValue[intlFallbackSymbol].formatToParts(number), expected);
|
||||
assertEqParts(fakeObj.formatToParts(number), expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Test resolvedOptions() returns the same results.
|
||||
{
|
||||
// An actual Intl.NumberFormat instance.
|
||||
let numberFormat = new Intl.NumberFormat();
|
||||
|
||||
// An object initialized as a NumberFormat instance.
|
||||
let thisValue = Object.create(Intl.NumberFormat.prototype);
|
||||
Intl.NumberFormat.call(thisValue);
|
||||
|
||||
// Object with [[FallbackSymbol]] set to NumberFormat instance.
|
||||
let fakeObj = {
|
||||
__proto__: Intl.NumberFormat.prototype,
|
||||
[intlFallbackSymbol]: numberFormat,
|
||||
};
|
||||
|
||||
function assertEqOptions(actual, expected) {
|
||||
actual = Object.entries(actual);
|
||||
expected = Object.entries(expected);
|
||||
|
||||
assertEq(actual.length, expected.length, "options count mismatch");
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
assertEq(actual[i][0], expected[i][0], "key mismatch at " + i);
|
||||
assertEq(actual[i][1], expected[i][1], "value mismatch at " + i);
|
||||
}
|
||||
}
|
||||
|
||||
let expected = numberFormat.resolvedOptions();
|
||||
assertEqOptions(thisValue.resolvedOptions(), expected);
|
||||
assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected);
|
||||
assertEqOptions(fakeObj.resolvedOptions(), expected);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
|
@ -60,6 +60,7 @@ skip script test262/ch10/10.6/10.6-13-b-2-s.js
|
|||
# (bug 1328386).
|
||||
skip script test262/intl402/ch10/10.1/10.1.1_1.js
|
||||
skip script test262/intl402/ch10/10.1/10.1.2_a.js
|
||||
skip script test262/intl402/ch11/11.1/11.1.1_1.js
|
||||
|
||||
#######################################################################
|
||||
# Tests disabled due to jstest limitations wrt imported test262 tests #
|
||||
|
|
|
@ -101,6 +101,7 @@ class GlobalObject : public NativeObject
|
|||
MAP_ITERATOR_PROTO,
|
||||
SET_ITERATOR_PROTO,
|
||||
COLLATOR_PROTO,
|
||||
NUMBER_FORMAT,
|
||||
NUMBER_FORMAT_PROTO,
|
||||
DATE_TIME_FORMAT_PROTO,
|
||||
PLURAL_RULES_PROTO,
|
||||
|
@ -483,6 +484,12 @@ class GlobalObject : public NativeObject
|
|||
return getOrCreateObject(cx, global, COLLATOR_PROTO, initIntlObject);
|
||||
}
|
||||
|
||||
static JSFunction*
|
||||
getOrCreateNumberFormatConstructor(JSContext* cx, Handle<GlobalObject*> global) {
|
||||
JSObject* obj = getOrCreateObject(cx, global, NUMBER_FORMAT, initIntlObject);
|
||||
return obj ? &obj->as<JSFunction>() : nullptr;
|
||||
}
|
||||
|
||||
static JSObject*
|
||||
getOrCreateNumberFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
|
||||
return getOrCreateObject(cx, global, NUMBER_FORMAT_PROTO, initIntlObject);
|
||||
|
|
|
@ -1856,6 +1856,23 @@ intrinsic_RuntimeDefaultLocale(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
using GetOrCreateIntlConstructor = JSFunction* (*)(JSContext*, Handle<GlobalObject*>);
|
||||
|
||||
template <GetOrCreateIntlConstructor getOrCreateIntlConstructor>
|
||||
static bool
|
||||
intrinsic_GetBuiltinIntlConstructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 0);
|
||||
|
||||
JSFunction* constructor = getOrCreateIntlConstructor(cx, cx->global());
|
||||
if (!constructor)
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*constructor);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_AddContentTelemetry(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
|
@ -2485,9 +2502,15 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
|||
JS_INLINABLE_FN("IsCollator",
|
||||
intrinsic_IsInstanceOfBuiltin<CollatorObject>, 1,0,
|
||||
IntlIsCollator),
|
||||
JS_INLINABLE_FN("IsNumberFormat",
|
||||
intrinsic_IsInstanceOfBuiltin<NumberFormatObject>, 1,0,
|
||||
IntlIsNumberFormat),
|
||||
JS_INLINABLE_FN("IsPluralRules",
|
||||
intrinsic_IsInstanceOfBuiltin<PluralRulesObject>, 1,0,
|
||||
IntlIsPluralRules),
|
||||
JS_FN("GetNumberFormatConstructor",
|
||||
intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateNumberFormatConstructor>,
|
||||
0,0),
|
||||
|
||||
JS_INLINABLE_FN("IsRegExpObject",
|
||||
intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
|
||||
|
|
Загрузка…
Ссылка в новой задаче