зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1846224 - Add a JavaScript per-realm locale override. r=jandem
Differential Revision: https://phabricator.services.mozilla.com/D184944
This commit is contained in:
Родитель
0e5d30db53
Коммит
847fe59347
|
@ -18,6 +18,7 @@
|
|||
#include "jstypes.h" // JS_PUBLIC_API
|
||||
|
||||
#include "js/Class.h" // JSTraceOp
|
||||
#include "js/RefCounted.h"
|
||||
|
||||
struct JS_PUBLIC_API JSContext;
|
||||
class JS_PUBLIC_API JSObject;
|
||||
|
@ -60,6 +61,14 @@ enum class WeakRefSpecifier {
|
|||
EnabledWithoutCleanupSome
|
||||
};
|
||||
|
||||
struct LocaleString : js::RefCounted<LocaleString> {
|
||||
const char* chars_;
|
||||
|
||||
explicit LocaleString(const char* chars) : chars_(chars) {}
|
||||
|
||||
auto chars() const { return chars_; }
|
||||
};
|
||||
|
||||
/**
|
||||
* RealmCreationOptions specifies options relevant to creating a new realm, that
|
||||
* are either immutable characteristics of that realm or that are discarded
|
||||
|
@ -259,6 +268,9 @@ class JS_PUBLIC_API RealmCreationOptions {
|
|||
return *this;
|
||||
}
|
||||
|
||||
RefPtr<LocaleString> locale() const { return locale_; }
|
||||
RealmCreationOptions& setLocaleCopyZ(const char* locale);
|
||||
|
||||
// Always use the fdlibm implementation of math functions instead of the
|
||||
// platform native libc implementations. Useful for fingerprinting protection
|
||||
// and cross-platform consistency.
|
||||
|
@ -282,6 +294,7 @@ class JS_PUBLIC_API RealmCreationOptions {
|
|||
Zone* zone_;
|
||||
};
|
||||
uint64_t profilerRealmID_ = 0;
|
||||
RefPtr<LocaleString> locale_;
|
||||
WeakRefSpecifier weakRefs_ = WeakRefSpecifier::Disabled;
|
||||
bool invisibleToDebugger_ = false;
|
||||
bool preserveJitCode_ = false;
|
||||
|
|
|
@ -7813,34 +7813,12 @@ static bool SetDefaultLocale(JSContext* cx, unsigned argc, Value* vp) {
|
|||
}
|
||||
|
||||
if (args[0].isString() && !args[0].toString()->empty()) {
|
||||
Rooted<JSLinearString*> str(cx, args[0].toString()->ensureLinear(cx));
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StringIsAscii(str)) {
|
||||
ReportUsageErrorASCII(cx, callee,
|
||||
"First argument contains non-ASCII characters");
|
||||
return false;
|
||||
}
|
||||
|
||||
UniqueChars locale = JS_EncodeStringToASCII(cx, str);
|
||||
RootedString str(cx, args[0].toString());
|
||||
UniqueChars locale = StringToLocale(cx, callee, str);
|
||||
if (!locale) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool containsOnlyValidBCP47Characters =
|
||||
mozilla::IsAsciiAlpha(locale[0]) &&
|
||||
std::all_of(locale.get(), locale.get() + str->length(), [](auto c) {
|
||||
return mozilla::IsAsciiAlphanumeric(c) || c == '-';
|
||||
});
|
||||
|
||||
if (!containsOnlyValidBCP47Characters) {
|
||||
ReportUsageErrorASCII(cx, callee,
|
||||
"First argument should be a BCP47 language tag");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS_SetDefaultLocale(cx->runtime(), locale.get())) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
|
|
|
@ -255,3 +255,36 @@ bool js::ParseDebugMetadata(JSContext* cx, JS::Handle<JSObject*> opts,
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
JS::UniqueChars js::StringToLocale(JSContext* cx, JS::Handle<JSObject*> callee,
|
||||
JS::Handle<JSString*> str_) {
|
||||
Rooted<JSLinearString*> str(cx, str_->ensureLinear(cx));
|
||||
if (!str) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!StringIsAscii(str)) {
|
||||
ReportUsageErrorASCII(cx, callee,
|
||||
"First argument contains non-ASCII characters");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UniqueChars locale = JS_EncodeStringToASCII(cx, str);
|
||||
if (!locale) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool containsOnlyValidBCP47Characters =
|
||||
mozilla::IsAsciiAlpha(locale[0]) &&
|
||||
std::all_of(locale.get(), locale.get() + str->length(), [](auto c) {
|
||||
return mozilla::IsAsciiAlphanumeric(c) || c == '-';
|
||||
});
|
||||
|
||||
if (!containsOnlyValidBCP47Characters) {
|
||||
ReportUsageErrorASCII(cx, callee,
|
||||
"First argument should be a BCP47 language tag");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return locale;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,10 @@ JSObject* CreateScriptPrivate(JSContext* cx,
|
|||
JS::MutableHandle<JS::Value> privateValue,
|
||||
JS::MutableHandle<JSString*> elementAttributeName);
|
||||
|
||||
[[nodiscard]] JS::UniqueChars StringToLocale(JSContext* cx,
|
||||
JS::Handle<JSObject*> callee,
|
||||
JS::Handle<JSString*> str_);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* builtin_TestingUtility_h */
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// |jit-test| skip-if: typeof Intl === 'undefined'
|
||||
|
||||
function test(locale, timeZone) {
|
||||
let global = newGlobal({locale, forceUTC: true});
|
||||
|
||||
const constructors = ["Collator", "DateTimeFormat", "ListFormat",
|
||||
"NumberFormat", "PluralRules", "RelativeTimeFormat"];
|
||||
for (const constructor of constructors) {
|
||||
let intl = new global.Intl[constructor];
|
||||
assertEq(intl.resolvedOptions().locale, locale);
|
||||
}
|
||||
|
||||
const date = new global.Date(2012, 0, 10);
|
||||
let tzRE = /\(([^\)]+)\)/;
|
||||
assertEq(tzRE.exec(date)[1], timeZone)
|
||||
}
|
||||
|
||||
test("de-CH", "Koordinierte Weltzeit");
|
||||
test("en", "Coordinated Universal Time");
|
|
@ -1722,6 +1722,24 @@ JS::RealmCreationOptions& JS::RealmCreationOptions::setCoopAndCoepEnabled(
|
|||
return *this;
|
||||
}
|
||||
|
||||
JS::RealmCreationOptions& JS::RealmCreationOptions::setLocaleCopyZ(
|
||||
const char* locale) {
|
||||
const size_t size = strlen(locale) + 1;
|
||||
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
char* memoryPtr = js_pod_malloc<char>(sizeof(LocaleString) + size);
|
||||
if (!memoryPtr) {
|
||||
oomUnsafe.crash("RealmCreationOptions::setLocaleCopyZ");
|
||||
}
|
||||
|
||||
char* localePtr = memoryPtr + sizeof(LocaleString);
|
||||
memcpy(localePtr, locale, size);
|
||||
|
||||
locale_ = new (memoryPtr) LocaleString(localePtr);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const JS::RealmBehaviors& JS::RealmBehaviorsRef(JS::Realm* realm) {
|
||||
return realm->behaviors();
|
||||
}
|
||||
|
|
|
@ -156,7 +156,8 @@ class DateTimeHelper {
|
|||
static double UTC(DateTimeInfo::ForceUTC forceUTC, double t);
|
||||
static JSString* timeZoneComment(JSContext* cx,
|
||||
DateTimeInfo::ForceUTC forceUTC,
|
||||
double utcTime, double localTime);
|
||||
const char* locale, double utcTime,
|
||||
double localTime);
|
||||
#if !JS_HAS_INTL_API
|
||||
static size_t formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
|
||||
size_t buflen, const char* fmt, double utcTime,
|
||||
|
@ -2944,8 +2945,8 @@ static bool date_toJSON(JSContext* cx, unsigned argc, Value* vp) {
|
|||
#if JS_HAS_INTL_API
|
||||
JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
|
||||
DateTimeInfo::ForceUTC forceUTC,
|
||||
double utcTime, double localTime) {
|
||||
const char* locale = cx->runtime()->getDefaultLocale();
|
||||
const char* locale, double utcTime,
|
||||
double localTime) {
|
||||
if (!locale) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEFAULT_LOCALE_ERROR);
|
||||
|
@ -3018,7 +3019,8 @@ size_t DateTimeHelper::formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
|
|||
|
||||
JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
|
||||
DateTimeInfo::ForceUTC forceUTC,
|
||||
double utcTime, double localTime) {
|
||||
const char* locale, double utcTime,
|
||||
double localTime) {
|
||||
char tzbuf[100];
|
||||
|
||||
size_t tzlen =
|
||||
|
@ -3055,7 +3057,7 @@ JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
|
|||
enum class FormatSpec { DateTime, Date, Time };
|
||||
|
||||
static bool FormatDate(JSContext* cx, DateTimeInfo::ForceUTC forceUTC,
|
||||
double utcTime, FormatSpec format,
|
||||
const char* locale, double utcTime, FormatSpec format,
|
||||
MutableHandleValue rval) {
|
||||
if (!std::isfinite(utcTime)) {
|
||||
rval.setString(cx->names().Invalid_Date_);
|
||||
|
@ -3091,8 +3093,8 @@ static bool FormatDate(JSContext* cx, DateTimeInfo::ForceUTC forceUTC,
|
|||
// also means the time zone string may not fit into Latin-1.
|
||||
|
||||
// Get a time zone string from the OS or ICU to include as a comment.
|
||||
timeZoneComment =
|
||||
DateTimeHelper::timeZoneComment(cx, forceUTC, utcTime, localTime);
|
||||
timeZoneComment = DateTimeHelper::timeZoneComment(cx, forceUTC, locale,
|
||||
utcTime, localTime);
|
||||
if (!timeZoneComment) {
|
||||
return false;
|
||||
}
|
||||
|
@ -3142,9 +3144,12 @@ static bool FormatDate(JSContext* cx, DateTimeInfo::ForceUTC forceUTC,
|
|||
}
|
||||
|
||||
#if !JS_HAS_INTL_API
|
||||
static bool ToLocaleFormatHelper(JSContext* cx, DateTimeInfo::ForceUTC forceUTC,
|
||||
double utcTime, const char* format,
|
||||
MutableHandleValue rval) {
|
||||
static bool ToLocaleFormatHelper(JSContext* cx, DateObject* unwrapped,
|
||||
const char* format, MutableHandleValue rval) {
|
||||
DateTimeInfo::ForceUTC forceUTC = unwrapped->forceUTC();
|
||||
const char* locale = unwrapped->realm()->getLocale();
|
||||
double utcTime = unwrapped->UTCTime().toNumber();
|
||||
|
||||
char buf[100];
|
||||
if (!std::isfinite(utcTime)) {
|
||||
strcpy(buf, "InvalidDate");
|
||||
|
@ -3157,7 +3162,8 @@ static bool ToLocaleFormatHelper(JSContext* cx, DateTimeInfo::ForceUTC forceUTC,
|
|||
|
||||
/* If it failed, default to toString. */
|
||||
if (result_len == 0) {
|
||||
return FormatDate(cx, forceUTC, utcTime, FormatSpec::DateTime, rval);
|
||||
return FormatDate(cx, forceUTC, locale, utcTime, FormatSpec::DateTime,
|
||||
rval);
|
||||
}
|
||||
|
||||
/* Hacked check against undesired 2-digit year 00/00/00 form. */
|
||||
|
@ -3212,9 +3218,7 @@ static bool date_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
|
|||
# endif
|
||||
;
|
||||
|
||||
return ToLocaleFormatHelper(cx, unwrapped->forceUTC(),
|
||||
unwrapped->UTCTime().toNumber(), format,
|
||||
args.rval());
|
||||
return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
|
||||
}
|
||||
|
||||
static bool date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp) {
|
||||
|
@ -3240,9 +3244,7 @@ static bool date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp) {
|
|||
# endif
|
||||
;
|
||||
|
||||
return ToLocaleFormatHelper(cx, unwrapped->forceUTC(),
|
||||
unwrapped->UTCTime().toNumber(), format,
|
||||
args.rval());
|
||||
return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
|
||||
}
|
||||
|
||||
static bool date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp) {
|
||||
|
@ -3256,9 +3258,7 @@ static bool date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
return ToLocaleFormatHelper(cx, unwrapped->forceUTC(),
|
||||
unwrapped->UTCTime().toNumber(), "%X",
|
||||
args.rval());
|
||||
return ToLocaleFormatHelper(cx, unwrapped, "%X", args.rval());
|
||||
}
|
||||
#endif /* !JS_HAS_INTL_API */
|
||||
|
||||
|
@ -3272,8 +3272,9 @@ static bool date_toTimeString(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
return FormatDate(cx, unwrapped->forceUTC(), unwrapped->UTCTime().toNumber(),
|
||||
FormatSpec::Time, args.rval());
|
||||
return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
|
||||
unwrapped->UTCTime().toNumber(), FormatSpec::Time,
|
||||
args.rval());
|
||||
}
|
||||
|
||||
static bool date_toDateString(JSContext* cx, unsigned argc, Value* vp) {
|
||||
|
@ -3286,8 +3287,9 @@ static bool date_toDateString(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
return FormatDate(cx, unwrapped->forceUTC(), unwrapped->UTCTime().toNumber(),
|
||||
FormatSpec::Date, args.rval());
|
||||
return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
|
||||
unwrapped->UTCTime().toNumber(), FormatSpec::Date,
|
||||
args.rval());
|
||||
}
|
||||
|
||||
static bool date_toSource(JSContext* cx, unsigned argc, Value* vp) {
|
||||
|
@ -3323,8 +3325,9 @@ bool date_toString(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
return FormatDate(cx, unwrapped->forceUTC(), unwrapped->UTCTime().toNumber(),
|
||||
FormatSpec::DateTime, args.rval());
|
||||
return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
|
||||
unwrapped->UTCTime().toNumber(), FormatSpec::DateTime,
|
||||
args.rval());
|
||||
}
|
||||
|
||||
bool js::date_valueOf(JSContext* cx, unsigned argc, Value* vp) {
|
||||
|
@ -3480,8 +3483,8 @@ static bool NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t) {
|
|||
}
|
||||
|
||||
static bool ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t) {
|
||||
return FormatDate(cx, ForceUTC(cx->realm()), t.toDouble(),
|
||||
FormatSpec::DateTime, args.rval());
|
||||
return FormatDate(cx, ForceUTC(cx->realm()), cx->realm()->getLocale(),
|
||||
t.toDouble(), FormatSpec::DateTime, args.rval());
|
||||
}
|
||||
|
||||
static bool DateNoArguments(JSContext* cx, const CallArgs& args) {
|
||||
|
|
|
@ -6776,6 +6776,9 @@ static bool WrapWithProto(JSContext* cx, unsigned argc, Value* vp) {
|
|||
}
|
||||
|
||||
static bool NewGlobal(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
RootedObject callee(cx, &args.callee());
|
||||
|
||||
JS::RealmOptions options;
|
||||
JS::RealmCreationOptions& creationOptions = options.creationOptions();
|
||||
JS::RealmBehaviors& behaviors = options.behaviors();
|
||||
|
@ -6794,7 +6797,6 @@ static bool NewGlobal(JSContext* cx, unsigned argc, Value* vp) {
|
|||
|
||||
JS::AutoHoldPrincipals principals(cx);
|
||||
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
if (args.length() == 1 && args[0].isObject()) {
|
||||
RootedObject opts(cx, &args[0].toObject());
|
||||
RootedValue v(cx);
|
||||
|
@ -6914,6 +6916,18 @@ static bool NewGlobal(JSContext* cx, unsigned argc, Value* vp) {
|
|||
if (v.isBoolean()) {
|
||||
creationOptions.setAlwaysUseFdlibm(v.toBoolean());
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(cx, opts, "locale", &v)) {
|
||||
return false;
|
||||
}
|
||||
if (v.isString()) {
|
||||
RootedString str(cx, v.toString());
|
||||
UniqueChars locale = StringToLocale(cx, callee, str);
|
||||
if (!locale) {
|
||||
return false;
|
||||
}
|
||||
creationOptions.setLocaleCopyZ(locale.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (!CheckRealmOptions(cx, options, principals.get())) {
|
||||
|
|
|
@ -488,6 +488,14 @@ void Realm::clearScriptCounts() { zone()->clearScriptCounts(this); }
|
|||
|
||||
void Realm::clearScriptLCov() { zone()->clearScriptLCov(this); }
|
||||
|
||||
const char* Realm::getLocale() const {
|
||||
if (RefPtr<LocaleString> locale = creationOptions_.locale()) {
|
||||
return locale->chars();
|
||||
}
|
||||
|
||||
return runtime_->getDefaultLocale();
|
||||
}
|
||||
|
||||
void ObjectRealm::addSizeOfExcludingThis(
|
||||
mozilla::MallocSizeOf mallocSizeOf, size_t* innerViewsArg,
|
||||
size_t* objectMetadataTablesArg,
|
||||
|
|
|
@ -673,6 +673,9 @@ class JS::Realm : public JS::shadow::Realm {
|
|||
|
||||
bool shouldCaptureStackForThrow();
|
||||
|
||||
// Returns the locale for this realm. (Pointer must NOT be freed!)
|
||||
const char* getLocale() const;
|
||||
|
||||
// Initializes randomNumberGenerator if needed.
|
||||
mozilla::non_crypto::XorShift128PlusRNG& getOrCreateRandomNumberGenerator();
|
||||
|
||||
|
|
|
@ -1593,7 +1593,7 @@ static bool intrinsic_RuntimeDefaultLocale(JSContext* cx, unsigned argc,
|
|||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 0);
|
||||
|
||||
const char* locale = cx->runtime()->getDefaultLocale();
|
||||
const char* locale = cx->realm()->getLocale();
|
||||
if (!locale) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEFAULT_LOCALE_ERROR);
|
||||
|
@ -1622,7 +1622,7 @@ static bool intrinsic_IsRuntimeDefaultLocale(JSContext* cx, unsigned argc,
|
|||
return true;
|
||||
}
|
||||
|
||||
const char* locale = cx->runtime()->getDefaultLocale();
|
||||
const char* locale = cx->realm()->getLocale();
|
||||
if (!locale) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_DEFAULT_LOCALE_ERROR);
|
||||
|
|
Загрузка…
Ссылка в новой задаче