Issue #2046 - Introduce mozIntl.DateTimeFormat with mozExtensions

Based-on: m-c 1329904
This commit is contained in:
Martok 2023-06-29 23:02:37 +02:00 committed by roytam1
commit 85612accf2
10 changed files with 227 additions and 15 deletions

View file

@ -42,14 +42,15 @@ js::intl::InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*
bool
js::intl::LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue thisValue, HandleValue locales, HandleValue options,
MutableHandleValue result)
DateTimeFormatOptions dtfOptions, MutableHandleValue result)
{
FixedInvokeArgs<4> args(cx);
FixedInvokeArgs<5> args(cx);
args[0].setObject(*obj);
args[1].set(thisValue);
args[2].set(locales);
args[3].set(options);
args[4].setBoolean(dtfOptions == DateTimeFormatOptions::EnableMozExtensions);
RootedValue thisv(cx, NullValue());
if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result))

View file

@ -38,10 +38,17 @@ InitializeObject(JSContext* cx, HandleObject obj, Handle<PropertyName*> initiali
* self-hosted function. This is only for a few old Intl.* constructors, for
* legacy reasons -- new ones should use the function above instead.
*/
enum class DateTimeFormatOptions
{
Standard,
EnableMozExtensions,
};
extern bool
LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
HandleValue thisValue, HandleValue locales, HandleValue options,
MutableHandleValue result);
DateTimeFormatOptions dtfOptions, MutableHandleValue result);
/**
* Returns the object holding the internal properties for obj.
*/

View file

@ -33,6 +33,7 @@ using JS::ClippedTime;
using JS::TimeClip;
using js::intl::CallICU;
using js::intl::DateTimeFormatOptions;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
@ -89,7 +90,7 @@ static const JSFunctionSpec dateTimeFormat_methods[] = {
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
*/
static bool
DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, DateTimeFormatOptions dtfOptions)
{
// Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
@ -119,14 +120,28 @@ DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct)
// Step 3.
return intl::LegacyIntlInitialize(cx, dateTimeFormat, cx->names().InitializeDateTimeFormat,
thisValue, locales, options, args.rval());
thisValue, locales, options, dtfOptions, args.rval());
}
static bool
DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return DateTimeFormat(cx, args, args.isConstructing());
return DateTimeFormat(cx, args, args.isConstructing(), DateTimeFormatOptions::Standard);
}
static bool
MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Don't allow to call mozIntl.DateTimeFormat as a function. That way we
// don't need to worry how to handle the legacy initialization semantics
// when applied on mozIntl.DateTimeFormat.
if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat"))
return false;
return DateTimeFormat(cx, args, true, DateTimeFormatOptions::EnableMozExtensions);
}
bool
@ -138,7 +153,7 @@ js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp)
// intl_DateTimeFormat 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 DateTimeFormat(cx, args, true);
return DateTimeFormat(cx, args, true, DateTimeFormatOptions::Standard);
}
void
@ -153,10 +168,12 @@ js::DateTimeFormatObject::finalize(FreeOp* fop, JSObject* obj)
JSObject*
js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
MutableHandleObject constructor)
MutableHandleObject constructor, DateTimeFormatOptions dtfOptions)
{
RootedFunction ctor(cx);
ctor = GlobalObject::createConstructor(cx, &DateTimeFormat, cx->names().DateTimeFormat, 0);
ctor = dtfOptions == DateTimeFormatOptions::EnableMozExtensions
? GlobalObject::createConstructor(cx, MozDateTimeFormat, cx->names().DateTimeFormat, 0)
: GlobalObject::createConstructor(cx, DateTimeFormat, cx->names().DateTimeFormat, 0);
if (!ctor)
return nullptr;
@ -200,6 +217,17 @@ js::CreateDateTimeFormatPrototype(JSContext* cx, HandleObject Intl, Handle<Globa
return proto;
}
bool
js::AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl)
{
Handle<GlobalObject*> global = cx->global();
RootedObject mozDateTimeFormat(cx);
JSObject* mozDateTimeFormatProto =
CreateDateTimeFormatPrototype(cx, intl, global, &mozDateTimeFormat, DateTimeFormatOptions::EnableMozExtensions);
return mozDateTimeFormatProto != nullptr;
}
bool
js::intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
{
@ -445,6 +473,79 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
return true;
}
bool
js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 4);
MOZ_ASSERT(args[0].isString());
JSAutoByteString locale(cx, args[0].toString());
if (!locale)
return false;
UDateFormatStyle dateStyle = UDAT_NONE;
UDateFormatStyle timeStyle = UDAT_NONE;
if (args[1].isString()) {
JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx);
if (!dateStyleStr)
return false;
if (StringEqualsAscii(dateStyleStr, "full"))
dateStyle = UDAT_FULL;
else if (StringEqualsAscii(dateStyleStr, "long"))
dateStyle = UDAT_LONG;
else if (StringEqualsAscii(dateStyleStr, "medium"))
dateStyle = UDAT_MEDIUM;
else if (StringEqualsAscii(dateStyleStr, "short"))
dateStyle = UDAT_SHORT;
else
MOZ_ASSERT_UNREACHABLE("unexpected dateStyle");
}
if (args[2].isString()) {
JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx);
if (!timeStyleStr)
return false;
if (StringEqualsAscii(timeStyleStr, "full"))
timeStyle = UDAT_FULL;
else if (StringEqualsAscii(timeStyleStr, "long"))
timeStyle = UDAT_LONG;
else if (StringEqualsAscii(timeStyleStr, "medium"))
timeStyle = UDAT_MEDIUM;
else if (StringEqualsAscii(timeStyleStr, "short"))
timeStyle = UDAT_SHORT;
else
MOZ_ASSERT_UNREACHABLE("unexpected timeStyle");
}
AutoStableStringChars timeZone(cx);
if (!timeZone.initTwoByte(cx, args[3].toString()))
return false;
mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
UErrorCode status = U_ZERO_ERROR;
UDateFormat* df = udat_open(timeStyle, dateStyle, IcuLocale(locale.ptr()),
Char16ToUChar(timeZoneChars.begin().get()),
timeZoneChars.length(), nullptr, -1, &status);
if (U_FAILURE(status)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
return false;
}
ScopedICUObject<UDateFormat, udat_close> toClose(df);
JSString* str = CallICU(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) {
return udat_toPattern(df, false, chars, size, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
/**
* Returns a new UDateFormat with the locale and date-time formatting options
* of the given DateTimeFormat.

View file

@ -41,7 +41,8 @@ class DateTimeFormatObject : public NativeObject
extern JSObject*
CreateDateTimeFormatPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global, MutableHandleObject constructor);
JS::Handle<GlobalObject*> global, MutableHandleObject constructor,
intl::DateTimeFormatOptions dtfOptions);
/**
* Returns a new instance of the standard built-in DateTimeFormat constructor.
@ -124,6 +125,35 @@ intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
/**
* Return a pattern in the date-time format pattern language of Unicode
* Technical Standard 35, Unicode Locale Data Markup Language, for the
* best-fit date-time style for the given locale.
* The function takes four arguments:
*
* locale
* BCP47 compliant locale string
* dateStyle
* A string with values: full or long or medium or short, or `undefined`
* timeStyle
* A string with values: full or long or medium or short, or `undefined`
* timeZone
* IANA time zone name
*
* Date and time style categories map to CLDR time/date standard
* format patterns.
*
* For the definition of a pattern string, see LDML 4.8:
* http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
*
* If `undefined` is passed to `dateStyle` or `timeStyle`, the respective
* portions of the pattern will not be included in the result.
*
* Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle, timeZone)
*/
extern MOZ_MUST_USE bool
intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns a String value representing x (which must be a Number value)
* according to the effective locale and the formatting options of the

View file

@ -32,6 +32,18 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
// }
//
// formatMatcher: "basic" / "best fit",
//
// mozExtensions: true / false,
//
//
// // If mozExtensions is true:
//
// dateStyle: "full" / "long" / "medium" / "short" / undefined,
//
// timeStyle: "full" / "long" / "medium" / "short" / undefined,
//
// patternOption:
// String representing LDML Date Format pattern or undefined
// }
//
// Note that lazy data is only installed as a final step of initialization,
@ -75,7 +87,26 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
var formatOpt = lazyDateTimeFormatData.formatOpt;
// Steps 27-28, more or less - see comment after this function.
var pattern = toBestICUPattern(dataLocale, formatOpt);
var pattern;
if (lazyDateTimeFormatData.mozExtensions) {
if (lazyDateTimeFormatData.patternOption !== undefined) {
pattern = lazyDateTimeFormatData.patternOption;
internalProps.patternOption = lazyDateTimeFormatData.patternOption;
} else if (lazyDateTimeFormatData.dateStyle || lazyDateTimeFormatData.timeStyle) {
pattern = intl_patternForStyle(dataLocale,
lazyDateTimeFormatData.dateStyle, lazyDateTimeFormatData.timeStyle,
lazyDateTimeFormatData.timeZone);
internalProps.dateStyle = lazyDateTimeFormatData.dateStyle;
internalProps.timeStyle = lazyDateTimeFormatData.timeStyle;
} else {
pattern = toBestICUPattern(dataLocale, formatOpt);
}
internalProps.mozExtensions = true;
} else {
pattern = toBestICUPattern(dataLocale, formatOpt);
}
// Step 29.
internalProps.pattern = pattern;
@ -250,7 +281,7 @@ function UnwrapDateTimeFormat(dtf, methodName) {
*
* Spec: ECMAScript Internationalization API Specification, 12.1.1.
*/
function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options) {
function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, mozExtensions) {
assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat called with non-Object");
assert(IsDateTimeFormat(dateTimeFormat),
"InitializeDateTimeFormat called with non-DateTimeFormat");
@ -324,6 +355,18 @@ function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options) {
var formatOpt = new Record();
lazyDateTimeFormatData.formatOpt = formatOpt;
lazyDateTimeFormatData.mozExtensions = mozExtensions;
if (mozExtensions) {
let pattern = GetOption(options, "pattern", "string", undefined, undefined);
lazyDateTimeFormatData.patternOption = pattern;
let dateStyle = GetOption(options, "dateStyle", "string", ["full", "long", "medium", "short"], undefined);
lazyDateTimeFormatData.dateStyle = dateStyle;
let timeStyle = GetOption(options, "timeStyle", "string", ["full", "long", "medium", "short"], undefined);
lazyDateTimeFormatData.timeStyle = timeStyle;
}
// Step 19.
var i, prop;
for (i = 0; i < dateTimeComponents.length; i++) {
@ -832,8 +875,18 @@ function Intl_DateTimeFormat_resolvedOptions() {
locale: internals.locale,
calendar: internals.calendar,
numberingSystem: internals.numberingSystem,
timeZone: internals.timeZone
timeZone: internals.timeZone,
};
if (internals.mozExtensions) {
if (internals.patternOption !== undefined) {
result.pattern = internals.pattern;
} else if (internals.dateStyle || internals.timeStyle) {
result.dateStyle = internals.dateStyle;
result.timeStyle = internals.timeStyle;
}
}
resolveICUPattern(internals.pattern, result);
return result;
}

View file

@ -30,6 +30,7 @@
using namespace js;
using js::intl::CallICU;
using js::intl::DateTimeFormatOptions;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
@ -455,7 +456,7 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
if (!collatorProto)
return false;
RootedObject dateTimeFormatProto(cx), dateTimeFormat(cx);
dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat);
dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat, DateTimeFormatOptions::Standard);
if (!dateTimeFormatProto)
return false;
RootedObject numberFormatProto(cx), numberFormat(cx);

View file

@ -35,6 +35,7 @@ using mozilla::IsFinite;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using js::intl::CallICU;
using js::intl::DateTimeFormatOptions;
using js::intl::GetAvailableLocales;
using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
@ -119,7 +120,7 @@ NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
// Step 3.
return intl::LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
locales, options, args.rval());
locales, options, DateTimeFormatOptions::Standard, args.rval());
}
static bool

View file

@ -2862,6 +2862,20 @@ ToWindowProxyIfWindow(JSObject* obj);
extern JS_FRIEND_API(JSObject*)
ToWindowIfWindowProxy(JSObject* obj);
/*
* This custom date/time formatter constructor gives users the ability
* to specify a custom format pattern. This pattern is passed *directly*
* to ICU with NO SYNTAX PARSING OR VALIDATION WHATSOEVER. ICU appears to
* have a a modicum of testing of this, and it won't fall over completely
* if passed bad input. But the current behavior is entirely under-specified
* and emphatically not shippable on the web, and it *must* be fixed before
* this functionality can be exposed in the real world. (There are also some
* questions about whether the format exposed here is the *right* one to
* standardize, that will also need to be resolved to ship this.)
*/
extern bool
AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
} /* namespace js */
class NativeProfiler

View file

@ -927,6 +927,9 @@ AddIntlExtras(JSContext* cx, unsigned argc, Value* vp)
if (!JS_DefineFunctions(cx, intl, funcs))
return false;
if (!js::AddMozDateTimeFormatConstructor(cx, intl))
return false;
args.rval().setUndefined();
return true;
}

View file

@ -2480,6 +2480,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
JS_FN("intl_patternForStyle", intl_patternForStyle, 3,0),
JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),