Issue #2259 - Update Intl.* Object-ness to ECMA-402, 4th edition

- turn each Intl object into a NativeObject subclass
- create them as PlainObject
- ensure correct type is passed in self-hosted code
- implement legacy constructor semantics for DateTime&NumberFormat
- store internals on object slot instead of JS WeakMap

Based-on: m-c 1328386, 1332604
This commit is contained in:
Martok 2023-06-29 23:02:11 +02:00 committed by roytam1
commit 30157344fe
31 changed files with 633 additions and 663 deletions

View file

@ -913,7 +913,7 @@ struct JSClass {
// application.
#define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
#define JSCLASS_GLOBAL_SLOT_COUNT \
(JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 47)
(JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 49)
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
#define JSCLASS_GLOBAL_FLAGS \

View file

@ -104,7 +104,7 @@ function Date_toLocaleString() {
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x, false);
return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false);
}
@ -137,7 +137,7 @@ function Date_toLocaleDateString() {
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x, false);
return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false);
}
@ -170,5 +170,5 @@ function Date_toLocaleTimeString() {
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x, false);
return intl_FormatDateTime(dateTimeFormat, x, /* formatToParts = */ false);
}

View file

@ -50,7 +50,7 @@ const JSFunctionSpec SymbolObject::staticMethods[] = {
};
JSObject*
SymbolObject::initClass(JSContext* cx, HandleObject obj)
SymbolObject::initClass(JSContext* cx, HandleObject obj, bool defineMembers)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
@ -66,25 +66,33 @@ SymbolObject::initClass(JSContext* cx, HandleObject obj)
if (!ctor)
return nullptr;
// Define the well-known symbol properties, such as Symbol.iterator.
ImmutablePropertyNamePtr* names = cx->names().wellKnownSymbolNames();
RootedValue value(cx);
unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols;
for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
value.setSymbol(wks->get(i));
if (!NativeDefineProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs))
return nullptr;
if (defineMembers) {
// Define the well-known symbol properties, such as Symbol.iterator.
ImmutablePropertyNamePtr* names = cx->names().wellKnownSymbolNames();
RootedValue value(cx);
unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols;
for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
value.setSymbol(wks->get(i));
if (!NativeDefineProperty(cx, ctor, names[i], value, nullptr, nullptr, attrs))
return nullptr;
}
}
if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
!DefineToStringTag(cx, proto, cx->names().Symbol) ||
!DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods) ||
!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto))
{
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (defineMembers) {
if (!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
!DefineToStringTag(cx, proto, cx->names().Symbol) ||
!DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods))
{
return nullptr;
}
}
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto))
return nullptr;
return proto;
}
@ -258,5 +266,11 @@ SymbolObject::descriptionGetter(JSContext* cx, unsigned argc, Value* vp)
JSObject*
js::InitSymbolClass(JSContext* cx, HandleObject obj)
{
return SymbolObject::initClass(cx, obj);
return SymbolObject::initClass(cx, obj, true);
}
JSObject*
js::InitBareSymbolCtor(JSContext* cx, HandleObject obj)
{
return SymbolObject::initClass(cx, obj, false);
}

View file

@ -21,7 +21,7 @@ class SymbolObject : public NativeObject
static const Class class_;
static JSObject* initClass(JSContext* cx, js::HandleObject obj);
static JSObject* initClass(JSContext* cx, js::HandleObject obj, bool defineMembers);
/*
* Creates a new Symbol object boxing the given primitive Symbol. The
@ -63,6 +63,9 @@ class SymbolObject : public NativeObject
extern JSObject*
InitSymbolClass(JSContext* cx, HandleObject obj);
extern JSObject*
InitBareSymbolCtor(JSContext* cx, HandleObject obj);
} /* namespace js */
#endif /* builtin_SymbolObject_h */

View file

@ -50,6 +50,8 @@
// Do not create an alias to a self-hosted builtin, otherwise it will be cloned
// twice.
//
// Symbol is a bare constructor without properties or methods.
var std_Symbol = Symbol;
// WeakMap is a bare constructor without properties or methods.
var std_WeakMap = WeakMap;
// StopIteration is a bare constructor without properties or methods.

View file

@ -79,64 +79,36 @@ static const JSFunctionSpec collator_methods[] = {
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
static bool
Collator(JSContext* cx, const CallArgs& args, bool construct)
Collator(JSContext* cx, const CallArgs& args)
{
RootedObject obj(cx);
// Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
// We're following ECMA-402 1st Edition when Collator is called because of
// backward compatibility issues.
// See https://github.com/tc39/ecma402/issues/57
if (!construct) {
// ES Intl 1st ed., 10.1.2.1 step 3
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
if (!intl)
return false;
RootedValue self(cx, args.thisv());
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
// ES Intl 1st ed., 10.1.2.1 step 4
obj = ToObject(cx, self);
if (!obj)
return false;
// ES Intl 1st ed., 10.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., 10.1.2.1 step 3.a
construct = true;
}
}
if (construct) {
// Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
if (!proto) {
proto = GlobalObject::getOrCreateCollatorPrototype(cx, cx->global());
if (!proto)
return false;
}
obj = NewObjectWithGivenProto<CollatorObject>(cx, proto);
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(CollatorObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
// Step 6.
if (!intl::InitializeObject(cx, obj, cx->names().InitializeCollator, locales, options))
// Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
args.rval().setObject(*obj);
if (!proto) {
proto = GlobalObject::getOrCreateCollatorPrototype(cx, cx->global());
if (!proto)
return false;
}
Rooted<CollatorObject*> collator(cx, NewObjectWithGivenProto<CollatorObject>(cx, proto));
if (!collator)
return false;
collator->setReservedSlot(CollatorObject::INTERNALS_SLOT, NullValue());
collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
// Step 6.
if (!intl::InitializeObject(cx, collator, cx->names().InitializeCollator, locales, options))
return false;
args.rval().setObject(*collator);
return true;
}
@ -144,7 +116,7 @@ static bool
Collator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return Collator(cx, args, args.isConstructing());
return Collator(cx, args);
}
bool
@ -153,9 +125,8 @@ js::intl_Collator(JSContext* cx, unsigned argc, Value* vp)
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(!args.isConstructing());
// intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot
// be used with "new", but it still has to be treated as a constructor.
return Collator(cx, args, true);
return Collator(cx, args);
}
void
@ -163,15 +134,9 @@ js::CollatorObject::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(CollatorObject::UCOLLATOR_SLOT);
if (!slot.isUndefined()) {
if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate()))
ucol_close(coll);
}
const Value& slot = obj->as<CollatorObject>().getReservedSlot(CollatorObject::UCOLLATOR_SLOT);
if (UCollator* coll = static_cast<UCollator*>(slot.toPrivate()))
ucol_close(coll);
}
JSObject*
@ -182,10 +147,9 @@ js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &CollatorObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -213,14 +177,6 @@ js::CreateCollatorPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObjec
return nullptr;
}
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
// 10.2.1 and 10.3
if (!intl::InitializeObject(cx, proto, cx->names().InitializeCollator, UndefinedHandleValue, options))
return nullptr;
// 8.1
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0))
@ -313,7 +269,7 @@ js::intl_availableCollations(JSContext* cx, unsigned argc, Value* vp)
* of the given Collator.
*/
static UCollator*
NewUCollator(JSContext* cx, HandleObject collator)
NewUCollator(JSContext* cx, Handle<CollatorObject*> collator)
{
RootedValue value(cx);
@ -490,40 +446,20 @@ js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp)
MOZ_ASSERT(args[2].isString());
Rooted<CollatorObject*> collator(cx, &args[0].toObject().as<CollatorObject>());
// Obtain a UCollator object, cached if possible.
// Obtain a cached UCollator object.
// XXX Does this handle Collator instances from other globals correctly?
bool isCollatorInstance = collator->getClass() == &CollatorObject::class_;
UCollator* coll;
if (isCollatorInstance) {
void* priv = collator->getReservedSlot(CollatorObject::UCOLLATOR_SLOT).toPrivate();
coll = static_cast<UCollator*>(priv);
if (!coll) {
coll = NewUCollator(cx, collator);
if (!coll)
return false;
collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(coll));
}
} else {
// There's no good place to cache the ICU collator for an object
// that has been initialized as a Collator but is not a Collator
// instance. One possibility might be to add a Collator instance as an
// internal property to each such object.
void* priv = collator->getReservedSlot(CollatorObject::UCOLLATOR_SLOT).toPrivate();
UCollator* coll = static_cast<UCollator*>(priv);
if (!coll) {
coll = NewUCollator(cx, collator);
if (!coll)
return false;
collator->setReservedSlot(CollatorObject::UCOLLATOR_SLOT, PrivateValue(coll));
}
// Use the UCollator to actually compare the strings.
RootedString str1(cx, args[1].toString());
RootedString str2(cx, args[2].toString());
RootedValue result(cx);
bool success = intl_CompareStrings(cx, coll, str1, str2, &result);
if (!isCollatorInstance)
ucol_close(coll);
if (!success)
return false;
args.rval().set(result);
return true;
return intl_CompareStrings(cx, coll, str1, str2, args.rval());
}

View file

@ -107,11 +107,13 @@ function resolveCollatorInternals(lazyCollatorData)
/**
* Returns an object containing the Collator internal properties of |obj|, or
* throws a TypeError if |obj| isn't Collator-initialized.
* Returns an object containing the Collator internal properties of |obj|.
*/
function getCollatorInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "Collator", methodName);
function getCollatorInternals(obj) {
assert(IsObject(obj), "getCollatorInternals called with non-object");
assert(IsCollator(obj), "getCollatorInternals called with non-Collator");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "Collator", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
@ -138,14 +140,11 @@ function getCollatorInternals(obj, methodName) {
* Spec: ECMAScript Internationalization API Specification, 10.1.1.
*/
function InitializeCollator(collator, locales, options) {
assert(IsObject(collator), "InitializeCollator");
assert(IsObject(collator), "InitializeCollator called with non-object");
assert(IsCollator(collator), "InitializeCollator called with non-Collator");
// Step 1.
if (isInitializedIntlObject(collator))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
// Step 2.
var internals = initializeIntlObject(collator);
// Steps 1-2 (These steps are no longer required and should be removed
// from the spec; https://github.com/tc39/ecma402/issues/115).;
// Lazy Collator data has the following structure:
//
@ -219,7 +218,7 @@ function InitializeCollator(collator, locales, options) {
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "Collator", lazyCollatorData);
initializeIntlObject(collator, "Collator", lazyCollatorData);
}
@ -311,14 +310,17 @@ function collatorCompareToBind(x, y) {
*/
function Intl_Collator_compare_get() {
// Check "this Collator object" per introduction of section 10.3.
var internals = getCollatorInternals(this, "compare");
if (!IsObject(this) || !IsCollator(this))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "Collator", "compare", "Collator");
var internals = getCollatorInternals(this);
// Step 1.
if (internals.boundCompare === undefined) {
// Step 1.a.
var F = collatorCompareToBind;
// Step 1.b-d.
// Steps 1.b-d.
var bc = callFunction(FunctionBind, F, this);
internals.boundCompare = bc;
}
@ -335,7 +337,10 @@ function Intl_Collator_compare_get() {
*/
function Intl_Collator_resolvedOptions() {
// Check "this Collator object" per introduction of section 10.3.
var internals = getCollatorInternals(this, "resolvedOptions");
if (!IsObject(this) || !IsCollator(this))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "Collator", "resolvedOptions", "Collator");
var internals = getCollatorInternals(this);
var result = {
locale: internals.locale,

View file

@ -19,26 +19,10 @@
#include "jsobjinlines.h"
bool
js::intl::CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions)
{
RootedObject options(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
if (!options)
return false;
defaultOptions.setObject(*options);
return true;
}
bool
js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue locales, HandleValue options)
{
RootedValue initializerValue(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue))
return false;
MOZ_ASSERT(initializerValue.isObject());
MOZ_ASSERT(initializerValue.toObject().is<JSFunction>());
FixedInvokeArgs<3> args(cx);
args[0].setObject(*obj);
@ -47,7 +31,32 @@ js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*
RootedValue thisv(cx, NullValue());
RootedValue ignored(cx);
return js::Call(cx, initializerValue, 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;
}
bool
js::intl::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;
}
/**
@ -56,21 +65,12 @@ js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*
JSObject*
js::intl::GetInternalsObject(JSContext* cx, HandleObject obj)
{
RootedValue getInternalsValue(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals,
&getInternalsValue))
{
return nullptr;
}
MOZ_ASSERT(getInternalsValue.isObject());
MOZ_ASSERT(getInternalsValue.toObject().is<JSFunction>());
FixedInvokeArgs<1> args(cx);
args[0].setObject(*obj);
RootedValue v(cx, NullValue());
if (!js::Call(cx, getInternalsValue, v, args, &v))
if (!js::CallSelfHostedFunction(cx, cx->names().getInternals, v, args, &v))
return nullptr;
return &v.toObject();

View file

@ -26,12 +26,6 @@ namespace js {
namespace intl {
/**
* Setup the |options| argument of |IntlInitialize|
*/
extern bool
CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions);
/**
* Initialize a new Intl.* object using the named self-hosted function.
*/
@ -39,6 +33,15 @@ extern bool
InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue locales, HandleValue options);
/**
* Initialize an existing object as an Intl.* object using the named
* self-hosted function. This is only for a few old Intl.* constructors, for
* legacy reasons -- new ones should use the function above instead.
*/
extern bool
LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue thisValue, HandleValue locales, HandleValue options,
MutableHandleValue result);
/**
* Returns the object holding the internal properties for obj.
*/

View file

@ -1057,26 +1057,38 @@ function GetNumberOption(options, property, minimum, maximum, fallback) {
}
// Symbols in the self-hosting compartment can't be cloned, use a separate
// object to hold the actual symbol value.
// TODO: Can we add support to clone symbols?
var intlFallbackSymbolHolder = { value: undefined };
/**
* Weak map used to track the initialize-as-Intl status (and, if an object has
* been so initialized, the Intl-specific internal properties) of all objects.
* Presence of an object as a key within this map indicates that the object has
* its [[initializedIntlObject]] internal property set to true. The associated
* value is an object whose structure is documented in |initializeIntlObject|
* below.
* The [[FallbackSymbol]] symbol of the %Intl% intrinsic object.
*
* Ideally we'd be using private symbols for internal properties, but
* SpiderMonkey doesn't have those yet.
* This symbol is used to implement the legacy constructor semantics for
* Intl.DateTimeFormat and Intl.NumberFormat.
*/
var internalsMap = new WeakMap();
function intlFallbackSymbol() {
var fallbackSymbol = intlFallbackSymbolHolder.value;
if (!fallbackSymbol)
intlFallbackSymbolHolder.value = fallbackSymbol = std_Symbol();
return fallbackSymbol;
}
/**
* Set the [[initializedIntlObject]] internal property of |obj| to true.
* Initializes the INTL_INTERNALS_OBJECT_SLOT of the given object.
*/
function initializeIntlObject(obj) {
function initializeIntlObject(obj, type, lazyData) {
assert(IsObject(obj), "Non-object passed to initializeIntlObject");
assert((type === "Collator" && IsCollator(obj)) ||
(type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
(type === "NumberFormat" && IsNumberFormat(obj)) ||
(type === "PluralRules" && IsPluralRules(obj)) ||
(type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
"type must match the object's class");
assert(IsObject(lazyData), "non-object lazy data");
// Intl-initialized objects are weird. They have [[initializedIntlObject]]
// set on them, but they don't *necessarily* have any other properties.
@ -1084,51 +1096,26 @@ function initializeIntlObject(obj) {
// The meaning of an internals object for an object |obj| is as follows.
//
// If the .type is "partial", |obj| has [[initializedIntlObject]] set but
// nothing else. No other property of |internals| can be used. (This
// occurs when InitializeCollator or similar marks an object as
// [[initializedIntlObject]] but fails before marking it as the appropriate
// more-specific type ["Collator", "DateTimeFormat", "NumberFormat"].)
// The .type property indicates the type of Intl object that |obj| is:
// "Collator", "DateTimeFormat", "NumberFormat", or "PluralRules" (likely
// with more coming in future Intl specs).
//
// Otherwise, the .type indicates the type of Intl object that |obj| is:
// "Collator", "DateTimeFormat", or "NumberFormat" (likely with more coming
// in future Intl specs). In these cases |obj| *conceptually* also has
// [[initializedCollator]] or similar set, and all the other properties
// implied by that.
//
// If |internals| doesn't have a "partial" .type, two additional properties
// have meaning. The .lazyData property stores information needed to
// compute -- without observable side effects -- the actual internal Intl
// properties of |obj|. If it is non-null, then the actual internal
// properties haven't been computed, and .lazyData must be processed by
// The .lazyData property stores information needed to compute -- without
// observable side effects -- the actual internal Intl properties of
// |obj|. If it is non-null, then the actual internal properties haven't
// been computed, and .lazyData must be processed by
// |setInternalProperties| before internal Intl property values are
// available. If it is null, then the .internalProps property contains an
// object whose properties are the internal Intl properties of |obj|.
internals.type = "partial";
internals.lazyData = null;
var internals = std_Object_create(null);
internals.type = type;
internals.lazyData = lazyData;
internals.internalProps = null;
callFunction(std_WeakMap_set, internalsMap, obj, internals);
return internals;
}
/**
* 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" || type === "PluralRules" ||
type === "RelativeTimeFormat",
"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;
assert(UnsafeGetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT) === null,
"Internal slot already initialized?");
UnsafeSetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT, internals);
}
@ -1136,9 +1123,7 @@ function setLazyData(internals, type, lazyData)
* Set the internal properties object for an |internals| object previously
* associated with lazy data.
*/
function setInternalProperties(internals, internalProps)
{
assert(internals.type !== "partial", "newborn internals can't have computed internals");
function setInternalProperties(internals, internalProps) {
assert(IsObject(internals.lazyData), "lazy data must exist already");
assert(IsObject(internalProps), "internalProps argument should be an object");
@ -1152,10 +1137,8 @@ function setInternalProperties(internals, internalProps)
* Get the existing internal properties out of a non-newborn |internals|, or
* null if none have been computed.
*/
function maybeInternalProperties(internals)
{
function maybeInternalProperties(internals) {
assert(IsObject(internals), "non-object passed to maybeInternalProperties");
assert(internals.type !== "partial", "maybeInternalProperties must only be used on completely-initialized internals objects");
var lazyData = internals.lazyData;
if (lazyData)
return null;
@ -1165,48 +1148,33 @@ function maybeInternalProperties(internals)
/**
* 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(hasOwn("type", internals), "missing type");
var type = internals.type;
assert(type === "partial" || type === "Collator" ||
type === "DateTimeFormat" || type === "NumberFormat" ||
type === "PluralRules" || type === "RelativeTimeFormat",
"unexpected type");
assert(hasOwn("lazyData", internals), "missing lazyData");
assert(hasOwn("internalProps", internals), "missing internalProps");
} else {
assert(internals === undefined, "bad mapping for |obj|");
}
#endif
return callFunction(std_WeakMap_has, internalsMap, obj);
}
/**
* Check that |obj| meets the requirements for "this Collator object", "this
* NumberFormat object", or "this DateTimeFormat object" as used in the method
* with the given name. Throw a TypeError if |obj| doesn't meet these
* requirements. But if it does, return |obj|'s internals object (*not* the
* object holding its internal properties!), associated with it by
* |internalsMap|, with structure specified above.
* Return |obj|'s internals object (*not* the object holding its internal
* properties!), with structure specified above.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.
* Spec: ECMAScript Internationalization API Specification, 11.3.
* Spec: ECMAScript Internationalization API Specification, 12.3.
*/
function getIntlObjectInternals(obj, className, methodName) {
assert(typeof className === "string", "bad className for getIntlObjectInternals");
function getIntlObjectInternals(obj) {
assert(IsObject(obj), "getIntlObjectInternals called with non-Object");
assert(IsCollator(obj) || IsDateTimeFormat(obj) || IsNumberFormat(obj) ||
IsPluralRules(obj) || IsRelativeTimeFormat(obj),
"getIntlObjectInternals called with non-Intl object");
var internals = callFunction(std_WeakMap_get, internalsMap, obj);
assert(internals === undefined || isInitializedIntlObject(obj), "bad mapping in internalsMap");
var internals = UnsafeGetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT);
if (internals === undefined || internals.type !== className)
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, className, methodName, className);
assert(IsObject(internals), "internals not an object");
assert(hasOwn("type", internals), "missing type");
assert((internals.type === "Collator" && IsCollator(obj)) ||
(internals.type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
(internals.type === "NumberFormat" && IsNumberFormat(obj)) ||
(internals.type === "PluralRules" && IsPluralRules(obj)) ||
(internals.type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
"type must match the object's class");
assert(hasOwn("lazyData", internals),
"missing lazyData");
assert(hasOwn("internalProps", internals),
"missing internalProps");
return internals;
}
@ -1216,35 +1184,32 @@ function getIntlObjectInternals(obj, className, methodName) {
* Get the internal properties of known-Intl object |obj|. For use only by
* C++ code that knows what it's doing!
*/
function getInternals(obj)
{
assert(isInitializedIntlObject(obj), "for use only on guaranteed Intl objects");
function getInternals(obj) {
var internals = getIntlObjectInternals(obj);
var internals = callFunction(std_WeakMap_get, internalsMap, obj);
// If internal properties have already been computed, use them.
var internalProps = maybeInternalProperties(internals);
if (internalProps)
return internalProps;
assert(internals.type !== "partial", "must have been successfully initialized");
var lazyData = internals.lazyData;
if (!lazyData)
return internals.internalProps;
var internalProps;
// Otherwise it's time to fully create them.
var type = internals.type;
switch (type) {
case "Collator":
internalProps = resolveCollatorInternals(lazyData);
internalProps = resolveCollatorInternals(internals.lazyData);
break;
case "DateTimeFormat":
internalProps = resolveDateTimeFormatInternals(lazyData);
internalProps = resolveDateTimeFormatInternals(internals.lazyData);
break;
case "PluralRules":
internalProps = resolvePluralRulesInternals(lazyData);
internalProps = resolvePluralRulesInternals(internals.lazyData);
break;
case "RelativeTimeFormat":
internalProps = resolveRelativeTimeFormatInternals(lazyData);
case "NumberFormat":
internalProps = resolveNumberFormatInternals(internals.lazyData);
break;
default: // type === "NumberFormat"
internalProps = resolveNumberFormatInternals(lazyData);
default: // type === "RelativeTimeFormat"
internalProps = resolveRelativeTimeFormatInternals(internals.lazyData);
break;
}
setInternalProperties(internals, internalProps);

View file

@ -91,63 +91,35 @@ static const JSFunctionSpec dateTimeFormat_methods[] = {
static bool
DateTimeFormat(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 DateTimeFormat is called
// because of backward compatibility issues.
// See https://github.com/tc39/ecma402/issues/57
if (!construct) {
// ES Intl 1st ed., 12.1.2.1 step 3
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
if (!intl)
return false;
RootedValue self(cx, args.thisv());
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
// ES Intl 1st ed., 12.1.2.1 step 4
obj = ToObject(cx, self);
if (!obj)
return false;
// ES Intl 1st ed., 12.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., 12.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;
if (!proto) {
proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
obj = NewObjectWithGivenProto<DateTimeFormatObject>(cx, proto);
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(DateTimeFormatObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
// Step 3.
if (!intl::InitializeObject(cx, obj, cx->names().InitializeDateTimeFormat, locales, options))
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
args.rval().setObject(*obj);
return true;
if (!proto) {
proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
dateTimeFormat = NewObjectWithGivenProto<DateTimeFormatObject>(cx, proto);
if (!dateTimeFormat)
return false;
dateTimeFormat->setReservedSlot(DateTimeFormatObject::INTERNALS_SLOT, NullValue());
dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT,
PrivateValue(nullptr));
RootedValue thisValue(cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
// Step 3.
return intl::LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
thisValue, locales, options, args.rval());
}
static bool
@ -174,30 +146,23 @@ js::DateTimeFormatObject::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<DateTimeFormatObject>().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT);
if (!slot.isUndefined()) {
if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
udat_close(df);
}
if (UDateFormat* df = static_cast<UDateFormat*>(slot.toPrivate()))
udat_close(df);
}
JSObject*
js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
MutableHandleObject constructor)
{
RootedFunction ctor(cx);
ctor = GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global,
&DateTimeFormatObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -226,22 +191,12 @@ js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<Globa
return nullptr;
}
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
// 12.2.1 and 12.3
if (!intl::InitializeObject(cx, proto, cx->names().InitializeDateTimeFormat, UndefinedHandleValue,
options))
{
return nullptr;
}
// 8.1
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0))
return nullptr;
constructor.set(ctor);
return proto;
}
@ -495,7 +450,7 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
* of the given DateTimeFormat.
*/
static UDateFormat*
NewUDateFormat(JSContext* cx, HandleObject dateTimeFormat)
NewUDateFormat(JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat)
{
RootedValue value(cx);
@ -779,42 +734,23 @@ js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp)
MOZ_ASSERT(args[1].isNumber());
MOZ_ASSERT(args[2].isBoolean());
RootedObject dateTimeFormat(cx, &args[0].toObject());
Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
// Obtain a UDateFormat object, cached if possible.
bool isDateTimeFormatInstance = dateTimeFormat->getClass() == &DateTimeFormatObject::class_;
UDateFormat* df;
if (isDateTimeFormatInstance) {
void* priv =
dateTimeFormat->as<NativeObject>().getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate();
df = static_cast<UDateFormat*>(priv);
if (!df) {
df = NewUDateFormat(cx, dateTimeFormat);
if (!df)
return false;
dateTimeFormat->as<NativeObject>().setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df));
}
} else {
// There's no good place to cache the ICU date-time format for an object
// that has been initialized as a DateTimeFormat but is not a
// DateTimeFormat instance. One possibility might be to add a
// DateTimeFormat instance as an internal property to each such object.
// Obtain a cached UDateFormat object.
void* priv =
dateTimeFormat->getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT).toPrivate();
UDateFormat* df = static_cast<UDateFormat*>(priv);
if (!df) {
df = NewUDateFormat(cx, dateTimeFormat);
if (!df)
return false;
dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT, PrivateValue(df));
}
// Use the UDateFormat to actually format the time stamp.
RootedValue result(cx);
bool success = args[2].toBoolean()
? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), &result)
: intl_FormatDateTime(cx, df, args[1].toNumber(), &result);
if (!isDateTimeFormatInstance)
udat_close(df);
if (!success)
return false;
args.rval().set(result);
return true;
return args[2].toBoolean()
? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), args.rval())
: intl_FormatDateTime(cx, df, args[1].toNumber(), args.rval());
}

View file

@ -41,7 +41,7 @@ class DateTimeFormatObject : public NativeObject
extern JSObject*
CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
JS::Handle<GlobalObject*> global, MutableHandleObject constructor);
/**
* Returns a new instance of the standard built-in DateTimeFormat constructor.
@ -131,7 +131,7 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*
* Usage: formatted = intl_FormatDateTime(dateTimeFormat, x)
* Usage: formatted = intl_FormatDateTime(dateTimeFormat, x, formatToParts)
*/
extern MOZ_MUST_USE bool
intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);

View file

@ -90,11 +90,13 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
/**
* Returns an object containing the DateTimeFormat internal properties of |obj|,
* or throws a TypeError if |obj| isn't DateTimeFormat-initialized.
* Returns an object containing the DateTimeFormat internal properties of |obj|.
*/
function getDateTimeFormatInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName);
function getDateTimeFormatInternals(obj) {
assert(IsObject(obj), "getDateTimeFormatInternals called with non-object");
assert(IsDateTimeFormat(obj), "getDateTimeFormatInternals called with non-DateTimeFormat");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
@ -214,6 +216,29 @@ function DefaultTimeZone() {
return defaultTimeZone;
}
/**
* UnwrapDateTimeFormat(dtf)
*/
function UnwrapDateTimeFormat(dtf, methodName) {
// Step 1.
if ((!IsObject(dtf) || !IsDateTimeFormat(dtf)) &&
dtf instanceof GetDateTimeFormatConstructor())
{
dtf = dtf[intlFallbackSymbol()];
}
// Step 2.
if (!IsObject(dtf) || !IsDateTimeFormat(dtf)) {
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "DateTimeFormat", methodName,
"DateTimeFormat");
}
// Step 3.
return dtf;
}
/**
* Initializes an object as a DateTimeFormat.
*
@ -225,15 +250,13 @@ function DefaultTimeZone() {
*
* Spec: ECMAScript Internationalization API Specification, 12.1.1.
*/
function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat");
function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options) {
assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat called with non-Object");
assert(IsDateTimeFormat(dateTimeFormat),
"InitializeDateTimeFormat called with non-DateTimeFormat");
// Step 1.
if (isInitializedIntlObject(dateTimeFormat))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
// Step 2.
var internals = initializeIntlObject(dateTimeFormat);
// Steps 1-2 (These steps are no longer required and should be removed
// from the spec; https://github.com/tc39/ecma402/issues/115).
// Lazy DateTimeFormat data has the following structure:
//
@ -334,7 +357,19 @@ function InitializeDateTimeFormat(dateTimeFormat, locales, options) {
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData);
initializeIntlObject(dateTimeFormat, "DateTimeFormat", lazyDateTimeFormatData);
if (dateTimeFormat !== thisValue && thisValue instanceof GetDateTimeFormatConstructor()) {
if (!IsObject(thisValue))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
_DefineDataProperty(thisValue, intlFallbackSymbol(), dateTimeFormat,
ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
return thisValue;
}
return dateTimeFormat;
}
@ -734,7 +769,7 @@ function dateTimeFormatFormatToBind() {
var x = (date === undefined) ? std_Date_now() : ToNumber(date);
// Step 1.a.iii.
return intl_FormatDateTime(this, x, false);
return intl_FormatDateTime(this, x, /* formatToParts = */ false);
}
/**
@ -745,34 +780,40 @@ function dateTimeFormatFormatToBind() {
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*/
function Intl_DateTimeFormat_format_get() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
var internals = getDateTimeFormatInternals(this, "format");
// Steps 1-3.
var dtf = UnwrapDateTimeFormat(this, "format");
// Step 1.
var internals = getDateTimeFormatInternals(dtf);
// Step 4.
if (internals.boundFormat === undefined) {
// Step 1.a.
// Step 4.a.
var F = dateTimeFormatFormatToBind;
// Step 1.b-d.
var bf = callFunction(FunctionBind, F, this);
// Steps 4.b-d.
var bf = callFunction(FunctionBind, F, dtf);
internals.boundFormat = bf;
}
// Step 2.
// Step 5.
return internals.boundFormat;
}
_SetCanonicalName(Intl_DateTimeFormat_format_get, "get format");
function Intl_DateTimeFormat_formatToParts() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
getDateTimeFormatInternals(this, "formatToParts");
// Steps 1-3.
var dtf = UnwrapDateTimeFormat(this, "formatToParts");
// Steps 1.a.i-ii
// Ensure the DateTimeFormat internals are resolved.
getDateTimeFormatInternals(dtf);
// Steps 4-5.
var date = arguments.length > 0 ? arguments[0] : undefined;
var x = (date === undefined) ? std_Date_now() : ToNumber(date);
// Step 1.a.iii.
return intl_FormatDateTime(this, x, true);
// Step 6.
return intl_FormatDateTime(dtf, x, /* formatToParts = */ true);
}
@ -782,8 +823,10 @@ function Intl_DateTimeFormat_formatToParts() {
* Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4.
*/
function Intl_DateTimeFormat_resolvedOptions() {
// Check "this DateTimeFormat object" per introduction of section 12.3.
var internals = getDateTimeFormatInternals(this, "resolvedOptions");
// Invoke |UnwrapDateTimeFormat| per introduction of section 12.3.
var dtf = UnwrapDateTimeFormat(this, "resolvedOptions");
var internals = getDateTimeFormatInternals(dtf);
var result = {
locale: internals.locale,

View file

@ -454,10 +454,12 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
if (!collatorProto)
return false;
RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
RootedObject dateTimeFormatProto(cx), dateTimeFormat(cx);
dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat);
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;
RootedObject pluralRulesProto(cx, CreatePluralRulesPrototype(cx, intl, global));
@ -487,7 +489,9 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
// |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, ObjectValue(*dateTimeFormat));
global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
global->setReservedSlot(NUMBER_FORMAT, ObjectValue(*numberFormat));
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));
global->setReservedSlot(RELATIVE_TIME_FORMAT_PROTO, ObjectValue(*relativeTimeFmtProto));

View file

@ -3,44 +3,79 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function Intl_getCanonicalLocales(locales) {
let codes = CanonicalizeLocaleList(locales);
let result = [];
// Step 1.
var localeList = CanonicalizeLocaleList(locales);
let len = codes.length;
let k = 0;
// Step 2 (Inlined CreateArrayFromList).
var array = [];
while (k < len) {
_DefineDataProperty(result, k, codes[k]);
k++;
}
return result;
}
for (var n = 0, len = localeList.length; n < len; n++)
_DefineDataProperty(array, n, localeList[n]);
function Intl_getCalendarInfo(locales) {
const requestedLocales = CanonicalizeLocaleList(locales);
const DateTimeFormat = dateTimeFormatInternalProperties;
const localeData = DateTimeFormat.localeData;
const localeOpt = new Record();
localeOpt.localeMatcher = "best fit";
const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
const result = intl_GetCalendarInfo(r.locale);
result.calendar = r.ca;
result.locale = r.locale;
return result;
return array;
}
/**
* This function is a custom method designed after Intl API, but currently
* not part of the spec or spec proposal.
* This function is a custom function in the style of the standard Intl.*
* functions, that isn't part of any spec or proposal yet.
*
* Returns an object with the following properties:
* locale:
* The actual resolved locale.
*
* calendar:
* The default calendar of the resolved locale.
*
* firstDayOfWeek:
* The first day of the week for the resolved locale.
*
* minDays:
* The minimum number of days in a week for the resolved locale.
*
* weekendStart:
* The day considered the beginning of a weekend for the resolved locale.
*
* weekendEnd:
* The day considered the end of a weekend for the resolved locale.
*
* Days are encoded as integers in the range 1=Sunday to 7=Saturday.
*/
function Intl_getCalendarInfo(locales) {
// 1. Let requestLocales be ? CanonicalizeLocaleList(locales).
const requestedLocales = CanonicalizeLocaleList(locales);
const DateTimeFormat = dateTimeFormatInternalProperties;
// 2. Let localeData be %DateTimeFormat%.[[localeData]].
const localeData = DateTimeFormat.localeData;
// 3. Let localeOpt be a new Record.
const localeOpt = new Record();
// 4. Set localeOpt.[[localeMatcher]] to "best fit".
localeOpt.localeMatcher = "best fit";
// 5. Let r be ResolveLocale(%DateTimeFormat%.[[availableLocales]],
// requestedLocales, localeOpt,
// %DateTimeFormat%.[[relevantExtensionKeys]], localeData).
const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
requestedLocales,
localeOpt,
DateTimeFormat.relevantExtensionKeys,
localeData);
// 6. Let result be GetCalendarInfo(r.[[locale]]).
const result = intl_GetCalendarInfo(r.locale);
_DefineDataProperty(result, "calendar", r.ca);
_DefineDataProperty(result, "locale", r.locale);
// 7. Return result.
return result;
}
/**
* This function is a custom function in the style of the standard Intl.*
* functions, that isn't part of any spec or proposal yet.
* We want to use it internally to retrieve translated values from CLDR in
* order to ensure they're aligned with what Intl API returns.
*
@ -86,8 +121,9 @@ function Intl_getDisplayNames(locales, options) {
// 4. Let localeData be %DateTimeFormat%.[[localeData]].
const localeData = DateTimeFormat.localeData;
// 5. Let opt be a new Record.
// 5. Let localeOpt be a new Record.
const localeOpt = new Record();
// 6. Set localeOpt.[[localeMatcher]] to "best fit".
localeOpt.localeMatcher = "best fit";
@ -101,6 +137,7 @@ function Intl_getDisplayNames(locales, options) {
// 8. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
const style = GetOption(options, "style", "string", ["long", "short", "narrow"], "long");
// 9. Let keys be ? Get(options, "keys").
let keys = options.keys;
@ -119,8 +156,10 @@ function Intl_getDisplayNames(locales, options) {
// |intl_ComputeDisplayNames| may infallibly access the list's length via
// |ArrayObject::length|.)
let processedKeys = [];
// 13. Let len be ? ToLength(? Get(keys, "length")).
let len = ToLength(keys.length);
// 14. Let i be 0.
// 15. Repeat, while i < len
for (let i = 0; i < len; i++) {

View file

@ -92,63 +92,34 @@ static const JSFunctionSpec numberFormat_methods[] = {
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)
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;
if (!proto) {
proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
obj = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(NumberFormatObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue());
RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue());
// Step 3.
if (!intl::InitializeObject(cx, obj, cx->names().InitializeNumberFormat, locales, options))
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
args.rval().setObject(*obj);
return true;
if (!proto) {
proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
if (!proto)
return false;
}
Rooted<NumberFormatObject*> numberFormat(cx);
numberFormat = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
if (!numberFormat)
return false;
numberFormat->setReservedSlot(NumberFormatObject::INTERNALS_SLOT, NullValue());
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.
return intl::LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
locales, options, args.rval());
}
static bool
@ -175,30 +146,23 @@ js::NumberFormatObject::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(NumberFormatObject::UNUMBER_FORMAT_SLOT);
if (!slot.isUndefined()) {
if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
unum_close(nf);
}
const Value& slot = obj->as<NumberFormatObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT);
if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
unum_close(nf);
}
JSObject*
js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
MutableHandleObject constructor)
{
RootedFunction ctor(cx);
ctor = GlobalObject::createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0);
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global,
&NumberFormatObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -229,22 +193,12 @@ js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalO
return nullptr;
}
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
// 11.2.1 and 11.3
if (!intl::InitializeObject(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue,
options))
{
return nullptr;
}
// 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;
}
@ -295,7 +249,7 @@ js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
* of the given NumberFormat.
*/
static UNumberFormat*
NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
{
RootedValue value(cx);
@ -854,50 +808,23 @@ 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->getClass() == &NumberFormatObject::class_;
UNumberFormat* nf;
if (isNumberFormatInstance) {
void* priv =
numberFormat->as<NativeObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT).toPrivate();
nf = static_cast<UNumberFormat*>(priv);
if (!nf) {
nf = NewUNumberFormat(cx, numberFormat);
if (!nf)
return false;
numberFormat->as<NativeObject>().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 (args[2].toBoolean()) {
success = intl_FormatNumberToParts(cx, nf, d, &result);
} else {
MOZ_ASSERT(!args[2].toBoolean(),
"shouldn't be doing formatToParts without an ICU that "
"supports it");
success = js::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;
return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval());
}

View file

@ -37,7 +37,8 @@ class NumberFormatObject : public NativeObject
};
extern JSObject*
CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global);
CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
MutableHandleObject constructor);
/**
* Returns a new instance of the standard built-in NumberFormat constructor.
@ -76,7 +77,7 @@ intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp);
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
*
* Usage: formatted = intl_FormatNumber(numberFormat, x)
* Usage: formatted = intl_FormatNumber(numberFormat, x, formatToParts)
*/
extern MOZ_MUST_USE bool
intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp);

View file

@ -96,11 +96,13 @@ 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) {
var internals = getIntlObjectInternals(obj, "NumberFormat", methodName);
function getNumberFormatInternals(obj) {
assert(IsObject(obj), "getNumberFormatInternals called with non-object");
assert(IsNumberFormat(obj), "getNumberFormatInternals called with non-NumberFormat");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals");
// If internal properties have already been computed, use them.
@ -114,6 +116,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.
*
@ -199,15 +220,12 @@ function IsWellFormedCurrencyCode(currency) {
*
* 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);
// Step 2.
var internals = initializeIntlObject(numberFormat);
// Steps 1-2 (These steps are no longer required and should be removed
// from the spec; https://github.com/tc39/ecma402/issues/115).
// Lazy NumberFormat data has the following structure:
//
@ -312,7 +330,19 @@ 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);
initializeIntlObject(numberFormat, "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;
}
@ -446,28 +476,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);
// 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.
getNumberFormatInternals(nf, "formatToParts");
// Ensure the NumberFormat internals are resolved.
getNumberFormatInternals(nf);
// Step 4.
var x = ToNumber(value);
@ -482,8 +516,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);
var result = {
locale: internals.locale,

View file

@ -113,8 +113,8 @@ PluralRules(JSContext* cx, const CallArgs& args, bool construct)
if (!obj)
return false;
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
obj->as<PluralRulesObject>().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
obj->as<PluralRulesObject>().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
}
RootedValue locales(cx, args.get(0));
@ -147,15 +147,9 @@ 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);
}
if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
uplrules_close(pr);
}
JSObject*
@ -166,10 +160,9 @@ js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalOb
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &PluralRulesObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -180,16 +173,6 @@ js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalOb
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;
@ -222,7 +205,7 @@ js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
*
*/
static UNumberFormat*
NewUNumberFormatForPluralRules(JSContext* cx, HandleObject pluralRules)
NewUNumberFormatForPluralRules(JSContext* cx, Handle<PluralRulesObject*> pluralRules)
{
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
if (!internals)
@ -299,7 +282,7 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject pluralRules(cx, &args[0].toObject());
Rooted<PluralRulesObject*> pluralRules(cx, &args[0].toObject().as<PluralRulesObject>());
UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
if (!nf)

View file

@ -63,11 +63,13 @@ function resolvePluralRulesInternals(lazyPluralRulesData) {
}
/**
* Returns an object containing the PluralRules internal properties of |obj|,
* or throws a TypeError if |obj| isn't PluralRules-initialized.
* Returns an object containing the PluralRules internal properties of |obj|.
*/
function getPluralRulesInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "PluralRules", methodName);
function getPluralRulesInternals(obj) {
assert(IsObject(obj), "getPluralRulesInternals called with non-object");
assert(IsPluralRules(obj), "getPluralRulesInternals called with non-PluralRules");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals");
var internalProps = maybeInternalProperties(internals);
@ -91,13 +93,11 @@ function getPluralRulesInternals(obj, methodName) {
* Spec: ECMAScript 402 API, PluralRules, 1.1.1.
*/
function InitializePluralRules(pluralRules, locales, options) {
assert(IsObject(pluralRules), "InitializePluralRules");
assert(IsObject(pluralRules), "InitializePluralRules called with non-object");
assert(IsPluralRules(pluralRules), "InitializePluralRules called with non-PluralRules");
// Step 1.
if (isInitializedIntlObject(pluralRules))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
let internals = initializeIntlObject(pluralRules);
// Steps 1-2 (These steps are no longer required and should be removed
// from the spec; https://github.com/tc39/ecma402/issues/115).
// Lazy PluralRules data has the following structure:
//
@ -156,7 +156,7 @@ function InitializePluralRules(pluralRules, locales, options) {
std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
}
setLazyData(internals, "PluralRules", lazyPluralRulesData)
initializeIntlObject(pluralRules, "PluralRules", lazyPluralRulesData)
}
/**
@ -189,8 +189,13 @@ function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
function Intl_PluralRules_select(value) {
// Step 1.
let pluralRules = this;
// Step 2.
let internals = getPluralRulesInternals(pluralRules, "select");
if (!IsObject(pluralRules) || !IsPluralRules(pluralRules))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "select", "PluralRules");
// Ensure the PluralRules internals are resolved.
getPluralRulesInternals(pluralRules);
// Steps 3-4.
let n = ToNumber(value);
@ -205,7 +210,13 @@ function Intl_PluralRules_select(value) {
* Spec: ECMAScript 402 API, PluralRules, 1.4.4.
*/
function Intl_PluralRules_resolvedOptions() {
var internals = getPluralRulesInternals(this, "resolvedOptions");
// Check "this PluralRules object" per introduction of section 1.4.
if (!IsObject(this) || !IsPluralRules(this)) {
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "resolvedOptions",
"PluralRules");
}
var internals = getPluralRulesInternals(this);
var result = {
locale: internals.locale,

View file

@ -108,8 +108,8 @@ RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp)
if (!relativeTimeFormat)
return false;
relativeTimeFormat->as<NativeObject>().setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue());
relativeTimeFormat->as<NativeObject>().setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
relativeTimeFormat->as<RelativeTimeFormatObject>().setReservedSlot(RelativeTimeFormatObject::INTERNALS_SLOT, NullValue());
relativeTimeFormat->as<RelativeTimeFormatObject>().setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
RootedValue locales(cx, args.get(0));
RootedValue options(cx, args.get(1));
@ -127,15 +127,9 @@ js::RelativeTimeFormatObject::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<RelativeTimeFormatObject>().getReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT);
if (!slot.isUndefined()) {
if (URelativeDateTimeFormatter* rtf = static_cast<URelativeDateTimeFormatter*>(slot.toPrivate()))
ureldatefmt_close(rtf);
}
if (URelativeDateTimeFormatter* rtf = static_cast<URelativeDateTimeFormatter*>(slot.toPrivate()))
ureldatefmt_close(rtf);
}
JSObject*
@ -146,10 +140,9 @@ js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<G
if (!ctor)
return nullptr;
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &RelativeTimeFormatObject::class_));
RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
if (!proto)
return nullptr;
proto->setReservedSlot(RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT, PrivateValue(nullptr));
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
@ -163,16 +156,6 @@ js::CreateRelativeTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<G
if (!JS_DefineProperties(cx, proto, relativeTimeFormat_properties))
return nullptr;
RootedValue options(cx);
if (!intl::CreateDefaultOptions(cx, &options))
return nullptr;
if (!intl::InitializeObject(cx, proto, cx->names().InitializeRelativeTimeFormat, UndefinedHandleValue,
options))
{
return nullptr;
}
RootedValue ctorValue(cx, ObjectValue(*ctor));
if (!DefineProperty(cx, Intl, cx->names().RelativeTimeFormat, ctorValue, nullptr, nullptr, 0)) {
return nullptr;

View file

@ -69,8 +69,11 @@ function resolveRelativeTimeFormatInternals(lazyRelativeTimeFormatData) {
* Returns an object containing the RelativeTimeFormat internal properties of |obj|,
* or throws a TypeError if |obj| isn't RelativeTimeFormat-initialized.
*/
function getRelativeTimeFormatInternals(obj, methodName) {
var internals = getIntlObjectInternals(obj, "RelativeTimeFormat", methodName);
function getRelativeTimeFormatInternals(obj) {
assert(IsObject(obj), "getRelativeTimeFormatInternals called with non-object");
assert(IsRelativeTimeFormat(obj), "getRelativeTimeFormatInternals called with non-RelativeTimeFormat");
var internals = getIntlObjectInternals(obj);
assert(internals.type === "RelativeTimeFormat", "bad type escaped getIntlObjectInternals");
var internalProps = maybeInternalProperties(internals);
@ -94,12 +97,8 @@ function getRelativeTimeFormatInternals(obj, methodName) {
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.1.1.
*/
function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) {
assert(IsObject(relativeTimeFormat), "InitializeRelativeTimeFormat");
if (isInitializedIntlObject(relativeTimeFormat))
ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
let internals = initializeIntlObject(relativeTimeFormat);
assert(IsObject(relativeTimeFormat), "InitializeRelativeTimeFormat called with non-object");
assert(IsRelativeTimeFormat(relativeTimeFormat), "InitializeRelativeTimeFormat called with non-RelativeTimeFormat");
// Lazy RelativeTimeFormat data has the following structure:
//
@ -146,7 +145,7 @@ function InitializeRelativeTimeFormat(relativeTimeFormat, locales, options) {
const numeric = GetOption(options, "numeric", "string", ["always", "auto"], "always");
lazyRelativeTimeFormatData.numeric = numeric;
setLazyData(internals, "RelativeTimeFormat", lazyRelativeTimeFormatData)
initializeIntlObject(relativeTimeFormat, "RelativeTimeFormat", lazyRelativeTimeFormatData)
}
/**
@ -181,7 +180,11 @@ function Intl_RelativeTimeFormat_format(value, unit) {
let relativeTimeFormat = this;
// Step 2.
let internals = getRelativeTimeFormatInternals(relativeTimeFormat, "format");
if (!IsObject(relativeTimeFormat) || !IsRelativeTimeFormat(relativeTimeFormat))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "RelativeTimeFormat", "format", "RelativeTimeFormat");
// Ensure the RelativeTimeFormat internals are resolved.
let internals = getRelativeTimeFormatInternals(relativeTimeFormat);
// Step 3.
let t = ToNumber(value);
@ -227,7 +230,13 @@ function Intl_RelativeTimeFormat_format(value, unit) {
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.4.4.
*/
function Intl_RelativeTimeFormat_resolvedOptions() {
var internals = getRelativeTimeFormatInternals(this, "resolvedOptions");
// Check "this RelativeTimeFormat object" per introduction of section 1.4.
if (!IsObject(this) || !IsRelativeTimeFormat(this)) {
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "RelativeTimeFormat", "resolvedOptions",
"RelativeTimeFormat");
}
var internals = getRelativeTimeFormatInternals(this);
// Steps 4-5.
var result = {

View file

@ -13,5 +13,5 @@ evalcx(`
');
oomTest(() => eval('Array(..."")'));
if ('Intl' in this)
Intl.NumberFormat.prototype.format(0);
new Intl.NumberFormat().format(0);
`, newGlobal());

View file

@ -4,15 +4,8 @@ load(libdir + "asserts.js");
// any value except "best fit" or "lookup" is okay.
Object.prototype.localeMatcher = "invalid matcher option";
// The Intl API may not be available in the testing environment. Note that |hasOwnProperty("Intl")|
// initializes the Intl API if present, so this if-statement needs to appear after "localeMatcher"
// was added to Object.prototype.
// The Intl API may not be available in the testing environment.
if (this.hasOwnProperty("Intl")) {
// Intl prototypes are properly initialized despite changed Object.prototype.
Intl.Collator.prototype.compare("a", "b");
Intl.NumberFormat.prototype.format(10);
Intl.DateTimeFormat.prototype.format(new Date);
// Intl constructors no longer work properly, because "localeMatcher" defaults to the invalid
// value from Object.prototype. Except for Intl.DateTimeFormat, cf. ECMA-402 ToDateTimeOptions.
assertThrowsInstanceOf(() => new Intl.Collator(), RangeError);

View file

@ -15,6 +15,7 @@ for (var i = 0; i < 3; i++) {
x.toString();
assertEq(0, 1);
} catch (e) {
assertEq(e.message, "y is undefined");
assertEq(e.message === "y is undefined" ||
e.message === "undefined has no properties", true);
}
}

View file

@ -26,6 +26,12 @@
_(AtomicsXor) \
_(AtomicsIsLockFree) \
\
_(IntlIsCollator) \
_(IntlIsDateTimeFormat) \
_(IntlIsNumberFormat) \
_(IntlIsPluralRules) \
_(IntlIsRelativeTimeFormat) \
\
_(MathAbs) \
_(MathFloor) \
_(MathCeil) \

View file

@ -13,6 +13,11 @@
#include "builtin/SIMD.h"
#include "builtin/TestingFunctions.h"
#include "builtin/TypedObject.h"
#include "builtin/intl/Collator.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/PluralRules.h"
#include "builtin/intl/RelativeTimeFormat.h"
#include "jit/BaselineInspector.h"
#include "jit/InlinableNatives.h"
#include "jit/IonBuilder.h"
@ -104,6 +109,18 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
case InlinableNative::AtomicsIsLockFree:
return inlineAtomicsIsLockFree(callInfo);
// Intl natives.
case InlinableNative::IntlIsCollator:
return inlineHasClass(callInfo, &CollatorObject::class_);
case InlinableNative::IntlIsDateTimeFormat:
return inlineHasClass(callInfo, &DateTimeFormatObject::class_);
case InlinableNative::IntlIsNumberFormat:
return inlineHasClass(callInfo, &NumberFormatObject::class_);
case InlinableNative::IntlIsPluralRules:
return inlineHasClass(callInfo, &PluralRulesObject::class_);
case InlinableNative::IntlIsRelativeTimeFormat:
return inlineHasClass(callInfo, &RelativeTimeFormatObject::class_);
// Math natives.
case InlinableNative::MathAbs:
return inlineMathAbs(callInfo);

View file

@ -487,7 +487,6 @@ MSG_DEF(JSMSG_TRACELOGGER_ENABLE_FAIL, 1, JSEXN_ERR, "enabling tracelogger faile
MSG_DEF(JSMSG_DATE_NOT_FINITE, 2, JSEXN_RANGEERR, "date value is not finite in {0}.{1}()")
MSG_DEF(JSMSG_INTERNAL_INTL_ERROR, 0, JSEXN_ERR, "internal error while computing Intl data")
MSG_DEF(JSMSG_INTL_OBJECT_NOT_INITED, 3, JSEXN_TYPEERR, "Intl.{0}.prototype.{1} called on value that's not an object initialized as a {2}")
MSG_DEF(JSMSG_INTL_OBJECT_REINITED, 0, JSEXN_TYPEERR, "can't initialize object twice as an object of an Intl constructor")
MSG_DEF(JSMSG_INVALID_CURRENCY_CODE, 1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
MSG_DEF(JSMSG_INVALID_DIGITS_VALUE, 1, JSEXN_RANGEERR, "invalid digits value: {0}")
MSG_DEF(JSMSG_INVALID_KEYS_TYPE, 0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined")

View file

@ -524,6 +524,7 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle<GlobalObject*> globa
InitBareBuiltinCtor(cx, global, JSProto_TypedArray) &&
InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
InitBareSymbolCtor(cx, global) &&
InitBareWeakMapCtor(cx, global) &&
InitStopIterationClass(cx, global) &&
DefineFunctions(cx, global, builtins, AsIntrinsic);

View file

@ -107,7 +107,9 @@ class GlobalObject : public NativeObject
MAP_ITERATOR_PROTO,
SET_ITERATOR_PROTO,
COLLATOR_PROTO,
NUMBER_FORMAT,
NUMBER_FORMAT_PROTO,
DATE_TIME_FORMAT,
DATE_TIME_FORMAT_PROTO,
PLURAL_RULES_PROTO,
RELATIVE_TIME_FORMAT_PROTO,
@ -499,11 +501,23 @@ 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);
}
static JSFunction*
getOrCreateDateTimeFormatConstructor(JSContext* cx, Handle<GlobalObject*> global) {
JSObject* obj = getOrCreateObject(cx, global, DATE_TIME_FORMAT, initIntlObject);
return obj ? &obj->as<JSFunction>() : nullptr;
}
static JSObject*
getOrCreateDateTimeFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
return getOrCreateObject(cx, global, DATE_TIME_FORMAT_PROTO, initIntlObject);

View file

@ -1905,6 +1905,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_ConstructFunction(JSContext* cx, unsigned argc, Value* vp)
{
@ -2469,6 +2486,28 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_RelativeTimeFormat_availableLocales", intl_RelativeTimeFormat_availableLocales, 0,0),
JS_FN("intl_FormatRelativeTime", intl_FormatRelativeTime, 3,0),
JS_INLINABLE_FN("IsCollator",
intrinsic_IsInstanceOfBuiltin<CollatorObject>, 1,0,
IntlIsCollator),
JS_INLINABLE_FN("IsDateTimeFormat",
intrinsic_IsInstanceOfBuiltin<DateTimeFormatObject>, 1,0,
IntlIsDateTimeFormat),
JS_INLINABLE_FN("IsNumberFormat",
intrinsic_IsInstanceOfBuiltin<NumberFormatObject>, 1,0,
IntlIsNumberFormat),
JS_INLINABLE_FN("IsPluralRules",
intrinsic_IsInstanceOfBuiltin<PluralRulesObject>, 1,0,
IntlIsPluralRules),
JS_INLINABLE_FN("IsRelativeTimeFormat",
intrinsic_IsInstanceOfBuiltin<RelativeTimeFormatObject>, 1,0,
IntlIsRelativeTimeFormat),
JS_FN("GetDateTimeFormatConstructor",
intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateDateTimeFormatConstructor>,
0,0),
JS_FN("GetNumberFormatConstructor",
intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateNumberFormatConstructor>,
0,0),
JS_INLINABLE_FN("IsRegExpObject",
intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
IsRegExpObject),