| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Copyright (c) ChakraCore Project Contributors. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "RuntimeLibraryPch.h" |
| #include "EngineInterfaceObject.h" |
| #include "IntlEngineInterfaceExtensionObject.h" |
| #include "Types/DeferredTypeHandler.h" |
| #include "Base/WindowsGlobalizationAdapter.h" |
| |
| #ifdef ENABLE_INTL_OBJECT |
| #include "ByteCode/ByteCodeSerializer.h" |
| #include "errstr.h" |
| #include "ByteCode/ByteCodeDumper.h" |
| #include "Codex/Utf8Helper.h" |
| |
| #ifdef INTL_WINGLOB |
| using namespace Windows::Globalization; |
| #endif |
| |
| #ifdef INTL_ICU |
| #include <CommonPal.h> |
| #include "PlatformAgnostic/ChakraICU.h" |
| using namespace PlatformAgnostic::ICUHelpers; |
| |
| #if defined(DBG) || defined(ENABLE_DEBUG_CONFIG_OPTIONS) |
| #define INTL_TRACE(fmt, ...) Output::Trace(Js::IntlPhase, _u("%S(): " fmt "\n"), __func__, __VA_ARGS__) |
| #else |
| #define INTL_TRACE(fmt, ...) |
| #endif |
| |
| #define ICU_ASSERT(e, expr) \ |
| do \ |
| { \ |
| if (e == U_MEMORY_ALLOCATION_ERROR) \ |
| { \ |
| Js::Throw::OutOfMemory(); \ |
| } \ |
| else if (ICU_FAILURE(e)) \ |
| { \ |
| AssertOrFailFastMsg(false, ICU_ERRORMESSAGE(e)); \ |
| } \ |
| else if (!(expr)) \ |
| { \ |
| AssertOrFailFast(expr); \ |
| } \ |
| } while (false) |
| |
| #endif // INTL_ICU |
| |
| // The following macros allow the key-value pairs to be C++ enums as well as JS objects |
| // in Intl.js. When adding a new macro, follow the same format as the _VALUES macros below, |
| // and add your new _VALUES macro to PROJECTED_ENUMS along with the name of the enum. |
| // NOTE: make sure the last VALUE macro has the highest integer value, since the C++ enum's ::Max |
| // value is added to the end of the C++ enum definition as an increment of the previous value. |
| // The ::Max value is used in a defensive assert, and we want to make sure its always 1 greater |
| // than the highest valid value. |
| |
| #define NUMBERFORMATSTYLE_VALUES(VALUE) \ |
| VALUE(Default, default_, 0) \ |
| VALUE(Decimal, decimal, 0) \ |
| VALUE(Percent, percent, 1) \ |
| VALUE(Currency, currency, 2) |
| |
| #define NUMBERFORMATCURRENCYDISPLAY_VALUES(VALUE) \ |
| VALUE(Default, default_, 0) \ |
| VALUE(Symbol, symbol, 0) \ |
| VALUE(Code, code, 1) \ |
| VALUE(Name, name, 2) |
| |
| #define COLLATORSENSITIVITY_VALUES(VALUE) \ |
| VALUE(Default, default_, 3) \ |
| VALUE(Base, base, 0) \ |
| VALUE(Accent, accent, 1) \ |
| VALUE(Case, case_, 2) \ |
| VALUE(Variant, variant, 3) |
| |
| #define COLLATORCASEFIRST_VALUES(VALUE) \ |
| VALUE(Default, default_, 2) \ |
| VALUE(Upper, upper, 0) \ |
| VALUE(Lower, lower, 1) \ |
| VALUE(False, false_, 2) |
| |
| // LocaleDataKind intentionally has no Default value |
| #define LOCALEDATAKIND_VALUES(VALUE) \ |
| VALUE(Collation, co, 0) \ |
| VALUE(CaseFirst, kf, 1) \ |
| VALUE(Numeric, kn, 2) \ |
| VALUE(Calendar, ca, 3) \ |
| VALUE(NumberingSystem, nu, 4) \ |
| VALUE(HourCycle, hc, 5) |
| |
| //BuiltInFunctionID intentionally has no Default value |
| #define BUILTINFUNCTIONID_VALUES(VALUE) \ |
| VALUE(DateToLocaleString, DateToLocaleString, 0) \ |
| VALUE(DateToLocaleDateString, DateToLocaleDateString, 1) \ |
| VALUE(DateToLocaleTimeString, DateToLocaleTimeString, 2) \ |
| VALUE(NumberToLocaleString, NumberToLocaleString, 3) \ |
| VALUE(StringLocaleCompare, StringLocaleCompare, 4) |
| |
| #define ENUM_VALUE(enumName, propId, value) enumName = value, |
| #define PROJECTED_ENUM(ClassName, VALUES) \ |
| enum class ClassName \ |
| { \ |
| VALUES(ENUM_VALUE) \ |
| Max \ |
| }; |
| |
| #define PROJECTED_ENUMS(PROJECT) \ |
| PROJECT(LocaleDataKind, LOCALEDATAKIND_VALUES) \ |
| PROJECT(CollatorCaseFirst, COLLATORCASEFIRST_VALUES) \ |
| PROJECT(CollatorSensitivity, COLLATORSENSITIVITY_VALUES) \ |
| PROJECT(NumberFormatCurrencyDisplay, NUMBERFORMATCURRENCYDISPLAY_VALUES) \ |
| PROJECT(NumberFormatStyle, NUMBERFORMATSTYLE_VALUES) \ |
| PROJECT(BuiltInFunctionID, BUILTINFUNCTIONID_VALUES) |
| |
| PROJECTED_ENUMS(PROJECTED_ENUM) |
| |
| #undef PROJECTED_ENUM |
| #undef ENUM_VALUE |
| |
| #define IfFailAssertAndThrowHr(op) \ |
| if (FAILED(hr=(op))) \ |
| { \ |
| AssertMsg(false, "HRESULT was a failure."); \ |
| JavascriptError::MapAndThrowError(scriptContext, hr); \ |
| } \ |
| |
| #define IfFailAssertMsgAndThrowHr(op, msg) \ |
| if (FAILED(hr=(op))) \ |
| { \ |
| AssertMsg(false, msg); \ |
| JavascriptError::MapAndThrowError(scriptContext, hr); \ |
| } \ |
| |
| #ifdef INTL_WINGLOB |
| |
| #define TO_JSBOOL(sc, b) ((b) ? (sc)->GetLibrary()->GetTrue() : (sc)->GetLibrary()->GetFalse()) |
| |
| #define IfCOMFailIgnoreSilentlyAndReturn(op) \ |
| if(FAILED(hr=(op))) \ |
| { \ |
| return; \ |
| } \ |
| |
| #define HandleOOMSOEHR(hr) \ |
| if (hr == E_OUTOFMEMORY) \ |
| { \ |
| JavascriptError::ThrowOutOfMemoryError(scriptContext); \ |
| } \ |
| else if(hr == VBSERR_OutOfStack) \ |
| { \ |
| JavascriptError::ThrowStackOverflowError(scriptContext); \ |
| } \ |
| |
| #define IfFailThrowHr(op) \ |
| if (FAILED(hr=(op))) \ |
| { \ |
| JavascriptError::MapAndThrowError(scriptContext, hr); \ |
| } \ |
| |
| #define SetPropertyOn(obj, propID, value) \ |
| obj->SetProperty(propID, value, Js::PropertyOperationFlags::PropertyOperation_None, nullptr) \ |
| |
| #define SetStringPropertyOn(obj, propID, propValue) \ |
| SetPropertyOn(obj, propID, Js::JavascriptString::NewCopySz(propValue, scriptContext)) \ |
| |
| #define SetPropertyLOn(obj, literalProperty, value) \ |
| obj->SetProperty(Js::JavascriptString::NewCopySz(literalProperty, scriptContext), value, Js::PropertyOperationFlags::PropertyOperation_None, nullptr) \ |
| |
| #define SetStringPropertyLOn(obj, literalProperty, propValue) \ |
| SetPropertyLOn(obj, literalProperty, Js::JavascriptString::NewCopySz(propValue, scriptContext)) \ |
| |
| #define SetPropertyBuiltInOn(obj, builtInPropID, value) \ |
| SetPropertyOn(obj, Js::PropertyIds::builtInPropID, value) \ |
| |
| #define SetStringPropertyBuiltInOn(obj, builtInPropID, propValue) \ |
| SetPropertyBuiltInOn(obj, builtInPropID, Js::JavascriptString::NewCopySz(propValue, scriptContext)) |
| |
| #define GetPropertyFrom(obj, propertyID) \ |
| Js::JavascriptOperators::GetProperty(obj, propertyID, &propertyValue, scriptContext) \ |
| |
| #define GetPropertyLFrom(obj, propertyName) \ |
| GetPropertyFrom(obj, scriptContext->GetOrAddPropertyIdTracked(propertyName, wcslen(propertyName))) |
| |
| #define GetPropertyBuiltInFrom(obj, builtInPropID) \ |
| GetPropertyFrom(obj, Js::PropertyIds::builtInPropID) \ |
| |
| #define GetTypedPropertyBuiltInFrom(obj, builtInPropID, Type) \ |
| (GetPropertyFrom(obj, Js::PropertyIds::builtInPropID) && VarIs<Type>(propertyValue)) \ |
| |
| #define HasPropertyOn(obj, propID) \ |
| Js::JavascriptOperators::HasProperty(obj, propID) \ |
| |
| #define HasPropertyBuiltInOn(obj, builtInPropID) \ |
| HasPropertyOn(obj, Js::PropertyIds::builtInPropID) \ |
| |
| #define HasPropertyLOn(obj, propertyName) \ |
| HasPropertyOn(obj, scriptContext->GetOrAddPropertyIdTracked(propertyName, wcslen(propertyName))) |
| |
| #define SetHSTRINGPropertyOn(obj, propID, hstringValue) \ |
| SetStringPropertyOn(obj, propID, wgl->WindowsGetStringRawBuffer(hstringValue, &length)) \ |
| |
| #define SetHSTRINGPropertyLOn(obj, literalProperty, hstringValue) \ |
| SetStringPropertyLOn(obj, literalProperty, wgl->WindowsGetStringRawBuffer(hstringValue, &length)) \ |
| |
| #define SetHSTRINGPropertyBuiltInOn(obj, builtInPropID, hstringValue) \ |
| SetStringPropertyBuiltInOn(obj, builtInPropID, wgl->WindowsGetStringRawBuffer(hstringValue, &length)) \ |
| |
| #endif |
| |
| #define INTL_CHECK_ARGS(argcheck) AssertOrFailFastMsg((argcheck), "Intl platform function given bad arguments") |
| |
| namespace Js |
| { |
| #ifdef ENABLE_INTL_OBJECT |
| #ifdef INTL_WINGLOB |
| class AutoHSTRING |
| { |
| PREVENT_COPY(AutoHSTRING) |
| |
| private: |
| HSTRING value; |
| public: |
| HSTRING *operator&() { Assert(value == nullptr); return &value; } |
| HSTRING operator*() const { Assert(value != nullptr); return value; } |
| |
| AutoHSTRING() |
| : value(nullptr) |
| { } |
| |
| ~AutoHSTRING() |
| { |
| Clear(); |
| } |
| |
| void Clear() |
| { |
| if (value != nullptr) |
| { |
| WindowsDeleteString(value); |
| value = nullptr; |
| } |
| } |
| }; |
| #endif |
| |
| // Defining Finalizable wrappers for Intl data |
| #if defined(INTL_WINGLOB) |
| class AutoCOMJSObject : public FinalizableObject |
| { |
| IInspectable *instance; |
| |
| public: |
| DEFINE_VTABLE_CTOR_NOBASE(AutoCOMJSObject); |
| |
| AutoCOMJSObject(IInspectable *object) |
| : instance(object) |
| { } |
| |
| static AutoCOMJSObject * New(Recycler * recycler, IInspectable *object) |
| { |
| return RecyclerNewFinalized(recycler, AutoCOMJSObject, object); |
| } |
| |
| void Finalize(bool isShutdown) override |
| { |
| |
| } |
| |
| void Dispose(bool isShutdown) override |
| { |
| if (!isShutdown) |
| { |
| instance->Release(); |
| } |
| } |
| void Mark(Recycler * recycler) override |
| { |
| |
| } |
| |
| IInspectable *GetInstance() |
| { |
| return instance; |
| } |
| }; |
| |
| #elif defined(INTL_ICU) |
| |
| template<typename TResource, void(__cdecl * CloseFunction)(TResource)> |
| class FinalizableICUObject : public FinalizableObject |
| { |
| private: |
| FieldNoBarrier(TResource) resource; |
| public: |
| FinalizableICUObject(TResource resource) : resource(resource) |
| { |
| |
| } |
| static FinalizableICUObject<TResource, CloseFunction> *New(Recycler *recycler, TResource resource) |
| { |
| return RecyclerNewFinalized(recycler, FinalizableICUObject, resource); |
| } |
| |
| TResource GetInstance() |
| { |
| return resource; |
| } |
| |
| operator TResource() |
| { |
| return resource; |
| } |
| |
| void Finalize(bool isShutdown) override |
| { |
| |
| } |
| |
| void Dispose(bool isShutdown) override |
| { |
| if (!isShutdown) |
| { |
| CloseFunction(resource); |
| } |
| } |
| |
| void Mark(Recycler *recycler) override |
| { |
| |
| } |
| }; |
| typedef FinalizableICUObject<UNumberFormat *, unum_close> FinalizableUNumberFormat; |
| typedef FinalizableICUObject<UDateFormat *, udat_close> FinalizableUDateFormat; |
| typedef FinalizableICUObject<UFieldPositionIterator *, ufieldpositer_close> FinalizableUFieldPositionIterator; |
| typedef FinalizableICUObject<UCollator *, ucol_close> FinalizableUCollator; |
| typedef FinalizableICUObject<UPluralRules *, uplrules_close> FinalizableUPluralRules; |
| |
| template<typename TExecutor> |
| static void EnsureBuffer(_In_ TExecutor executor, _In_ Recycler *recycler, _Outptr_result_buffer_(*returnLength) char16 **ret, _Out_ int *returnLength, _In_ bool allowZeroLengthStrings = false, _In_ int firstTryLength = 8) |
| { |
| UErrorCode status = U_ZERO_ERROR; |
| *ret = RecyclerNewArrayLeaf(recycler, char16, firstTryLength); |
| *returnLength = executor(reinterpret_cast<UChar *>(*ret), firstTryLength, &status); |
| AssertOrFailFast(allowZeroLengthStrings ? *returnLength >= 0 : *returnLength > 0); |
| if (ICU_BUFFER_FAILURE(status)) |
| { |
| AssertOrFailFastMsg(*returnLength >= firstTryLength, "Executor reported buffer failure but did not require additional space"); |
| int secondTryLength = *returnLength + 1; |
| INTL_TRACE("Buffer of length %d was too short, retrying with buffer of length %d", firstTryLength, secondTryLength); |
| status = U_ZERO_ERROR; |
| *ret = RecyclerNewArrayLeaf(recycler, char16, secondTryLength); |
| *returnLength = executor(reinterpret_cast<UChar *>(*ret), secondTryLength, &status); |
| AssertOrFailFastMsg(*returnLength == secondTryLength - 1, "Second try of executor returned unexpected length"); |
| } |
| else |
| { |
| AssertOrFailFastMsg(*returnLength < firstTryLength, "Executor required additional length but reported successful status"); |
| } |
| |
| AssertOrFailFastMsg(!ICU_FAILURE(status), ICU_ERRORMESSAGE(status)); |
| } |
| |
| template <typename T> |
| static T *AssertProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId) |
| { |
| Var propertyValue = nullptr; |
| JavascriptOperators::GetProperty(state, propertyId, &propertyValue, state->GetScriptContext()); |
| |
| AssertOrFailFast(propertyValue && VarIs<T>(propertyValue)); |
| |
| return UnsafeVarTo<T>(propertyValue); |
| } |
| |
| static JavascriptString *AssertStringProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId) |
| { |
| return AssertProperty<JavascriptString>(state, propertyId); |
| } |
| |
| static int AssertIntegerProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId) |
| { |
| Var propertyValue = nullptr; |
| JavascriptOperators::GetProperty(state, propertyId, &propertyValue, state->GetScriptContext()); |
| |
| AssertOrFailFast(propertyValue); |
| |
| if (TaggedInt::Is(propertyValue)) |
| { |
| return TaggedInt::ToInt32(propertyValue); |
| } |
| else |
| { |
| AssertOrFailFast(JavascriptNumber::Is(propertyValue)); |
| int ret; |
| AssertOrFailFast(JavascriptNumber::TryGetInt32Value(JavascriptNumber::GetValue(propertyValue), &ret)); |
| |
| return ret; |
| } |
| } |
| |
| static bool AssertBooleanProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId) |
| { |
| return AssertProperty<JavascriptBoolean>(state, propertyId)->GetValue(); |
| } |
| |
| template <typename T> |
| static T AssertEnumProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId) |
| { |
| int p = AssertIntegerProperty(state, propertyId); |
| T ret = static_cast<T>(p); |
| AssertMsg(p >= 0 && ret < T::Max, "Invalid value for enum property"); |
| return ret; |
| } |
| |
| template <typename T> |
| static _Ret_notnull_ T ThrowOOMIfNull(_In_ T value) |
| { |
| if (value == nullptr) |
| { |
| Throw::OutOfMemory(); |
| } |
| |
| return value; |
| } |
| |
| template <size_t N> |
| static void LangtagToLocaleID(_In_count_(langtagLength) const char16 *langtag, _In_ charcount_t langtagLength, _Out_ char(&localeID)[N]) |
| { |
| static_assert(N >= ULOC_FULLNAME_CAPACITY, "LocaleID must be large enough to fit the largest possible ICU localeID"); |
| |
| UErrorCode status = U_ZERO_ERROR; |
| utf8::WideToNarrow langtag8(langtag, langtagLength); |
| int32_t localeIDLength = 0; |
| uloc_forLanguageTag(langtag8, localeID, N, &localeIDLength, &status); |
| ICU_ASSERT(status, localeIDLength > 0 && static_cast<size_t>(localeIDLength) < N); |
| } |
| |
| template <size_t N> |
| static void LangtagToLocaleID(_In_ JavascriptString *langtag, _Out_ char(&localeID)[N]) |
| { |
| LangtagToLocaleID(langtag->GetString(), langtag->GetLength(), localeID); |
| } |
| |
| template <typename Callback> |
| static void ForEachUEnumeration(UEnumeration *enumeration, Callback callback) |
| { |
| int valueLength = 0; |
| UErrorCode status = U_ZERO_ERROR; |
| for (int index = 0, const char *value = uenum_next(enumeration, &valueLength, &status); value != nullptr; index++, value = uenum_next(enumeration, &valueLength, &status)) |
| { |
| ICU_ASSERT(status, valueLength > 0); |
| |
| // cast valueLength now since we have verified its greater than 0 |
| callback(index, value, static_cast<charcount_t>(valueLength)); |
| } |
| } |
| |
| template <typename Callback> |
| static void ForEachUEnumeration16(UEnumeration *enumeration, Callback callback) |
| { |
| int valueLength = 0; |
| UErrorCode status = U_ZERO_ERROR; |
| int index = 0; |
| for (const UChar *value = uenum_unext(enumeration, &valueLength, &status); value != nullptr; index++, value = uenum_unext(enumeration, &valueLength, &status)) |
| { |
| ICU_ASSERT(status, valueLength > 0); |
| |
| // cast valueLength now since we have verified its greater than 0 |
| callback(index, reinterpret_cast<const char16 *>(value), static_cast<charcount_t>(valueLength)); |
| } |
| } |
| #endif |
| |
| IntlEngineInterfaceExtensionObject::IntlEngineInterfaceExtensionObject(Js::ScriptContext* scriptContext) : |
| EngineExtensionObjectBase(EngineInterfaceExtensionKind_Intl, scriptContext), |
| dateToLocaleString(nullptr), |
| dateToLocaleTimeString(nullptr), |
| dateToLocaleDateString(nullptr), |
| numberToLocaleString(nullptr), |
| stringLocaleCompare(nullptr), |
| intlNativeInterfaces(nullptr), |
| intlByteCode(nullptr), |
| wasInitialized(false) |
| { |
| } |
| |
| // Initializes the IntlEngineInterfaceExtensionObject::EntryInfo struct |
| #ifdef INTL_ENTRY |
| #undef INTL_ENTRY |
| #endif |
| #define INTL_ENTRY(id, func) \ |
| NoProfileFunctionInfo IntlEngineInterfaceExtensionObject::EntryInfo::Intl_##func##(FORCE_NO_WRITE_BARRIER_TAG(IntlEngineInterfaceExtensionObject::EntryIntl_##func##)); |
| #include "IntlExtensionObjectBuiltIns.h" |
| #undef INTL_ENTRY |
| |
| #ifdef INTL_WINGLOB |
| WindowsGlobalizationAdapter* IntlEngineInterfaceExtensionObject::GetWindowsGlobalizationAdapter(_In_ ScriptContext * scriptContext) |
| { |
| return scriptContext->GetThreadContext()->GetWindowsGlobalizationAdapter(); |
| } |
| #endif |
| |
| void IntlEngineInterfaceExtensionObject::Initialize() |
| { |
| if (wasInitialized) |
| { |
| return; |
| } |
| JavascriptLibrary* library = scriptContext->GetLibrary(); |
| |
| DynamicObject* commonObject = library->GetEngineInterfaceObject()->GetCommonNativeInterfaces(); |
| if (scriptContext->IsIntlEnabled()) |
| { |
| Assert(library->GetEngineInterfaceObject() != nullptr); |
| this->intlNativeInterfaces = DynamicObject::New(library->GetRecycler(), |
| DynamicType::New(scriptContext, TypeIds_Object, commonObject, nullptr, |
| DeferredTypeHandler<InitializeIntlNativeInterfaces>::GetDefaultInstance())); |
| library->AddMember(library->GetEngineInterfaceObject(), Js::PropertyIds::Intl, this->intlNativeInterfaces); |
| |
| // Only show the platform object publicly if -IntlPlatform is passed |
| if (CONFIG_FLAG(IntlPlatform)) |
| { |
| library->AddMember(library->GetIntlObject(), PropertyIds::platform, this->intlNativeInterfaces); |
| } |
| } |
| wasInitialized = true; |
| } |
| |
| #if DBG |
| void IntlEngineInterfaceExtensionObject::DumpByteCode() |
| { |
| Output::Print(_u("Dumping Intl Byte Code:")); |
| Assert(this->intlByteCode); |
| Js::ByteCodeDumper::DumpRecursively(intlByteCode); |
| } |
| #endif |
| |
| bool IntlEngineInterfaceExtensionObject::InitializeIntlNativeInterfaces(DynamicObject* intlNativeInterfaces, DeferredTypeHandlerBase * typeHandler, DeferredInitializeMode mode) |
| { |
| int initSlotCapacity = 0; |
| |
| // automatically get the initSlotCapacity from everything we are about to add to intlNativeInterfaces |
| #define INTL_ENTRY(id, func) initSlotCapacity++; |
| #include "IntlExtensionObjectBuiltIns.h" |
| #undef INTL_ENTRY |
| |
| #define PROJECTED_ENUM(ClassName, VALUES) initSlotCapacity++; |
| PROJECTED_ENUMS(PROJECTED_ENUM) |
| #undef PROJECTED_ENUM |
| |
| // add capacity for platform.winglob and platform.FallbackSymbol |
| initSlotCapacity += 2; |
| |
| typeHandler->Convert(intlNativeInterfaces, mode, initSlotCapacity); |
| |
| ScriptContext* scriptContext = intlNativeInterfaces->GetScriptContext(); |
| JavascriptLibrary* library = scriptContext->GetLibrary(); |
| |
| // gives each entrypoint a property ID on the intlNativeInterfaces library object |
| #define INTL_ENTRY(id, func) library->AddFunctionToLibraryObject(intlNativeInterfaces, Js::PropertyIds::##id, &IntlEngineInterfaceExtensionObject::EntryInfo::Intl_##func, 1); |
| #include "IntlExtensionObjectBuiltIns.h" |
| #undef INTL_ENTRY |
| |
| library->AddMember(intlNativeInterfaces, PropertyIds::FallbackSymbol, library->CreateSymbol(BuiltInPropertyRecords::_intlFallbackSymbol)); |
| |
| DynamicObject * enumObj = nullptr; |
| |
| // Projects the exact layout of our C++ enums into Intl.js so that we dont have to remember to keep them in sync |
| #define ENUM_VALUE(enumName, propId, value) library->AddMember(enumObj, PropertyIds::##propId, JavascriptNumber::ToVar(value, scriptContext)); |
| #define PROJECTED_ENUM(ClassName, VALUES) \ |
| enumObj = library->CreateObject(); \ |
| VALUES(ENUM_VALUE) \ |
| library->AddMember(intlNativeInterfaces, PropertyIds::##ClassName, enumObj); \ |
| |
| PROJECTED_ENUMS(PROJECTED_ENUM) |
| |
| #undef PROJECTED_ENUM |
| #undef ENUM_VALUE |
| |
| #if INTL_WINGLOB |
| library->AddMember(intlNativeInterfaces, Js::PropertyIds::winglob, library->GetTrue()); |
| #else |
| library->AddMember(intlNativeInterfaces, Js::PropertyIds::winglob, library->GetFalse()); |
| |
| #if defined(NTBUILD) |
| // when using ICU, we can call ulocdata_getCLDRVersion to ensure that ICU is functioning properly before allowing Intl to continue. |
| // ulocdata_getCLDRVersion will cause the data file to be loaded, and if we don't have enough memory to do so, we can throw OutOfMemory here. |
| // This is to protect against spurious U_MISSING_RESOURCE_ERRORs and U_FILE_ACCESS_ERRORs coming from early-lifecycle |
| // functions that require ICU data. |
| // See OS#16897150, OS#16896933, and others relating to bad statuses returned by GetLocaleData and IsLocaleAvailable |
| // This was initially attempted using u_init, however u_init does not work with Node's default small-icu data file |
| // because it contains no converters. |
| UErrorCode status = U_ZERO_ERROR; |
| UVersionInfo cldrVersion; |
| ulocdata_getCLDRVersion(cldrVersion, &status); |
| if (status == U_MEMORY_ALLOCATION_ERROR || status == U_FILE_ACCESS_ERROR || status == U_MISSING_RESOURCE_ERROR) |
| { |
| // Trace that this happens in case there are build system changes that actually cause the data file to be not found |
| INTL_TRACE("Could not initialize ICU - ulocdata_getCLDRVersion returned status %S", u_errorName(status)); |
| Throw::OutOfMemory(); |
| } |
| else |
| { |
| INTL_TRACE("Using CLDR version %d.%d.%d.%d", cldrVersion[0], cldrVersion[1], cldrVersion[2], cldrVersion[3]); |
| } |
| |
| AssertOrFailFastMsg(U_SUCCESS(status), "ulocdata_getCLDRVersion returned non-OOM failure"); |
| #endif // defined(NTBUILD) |
| #endif // else !INTL_WINGLOB |
| |
| // Add a reference to the built-in Intl object that can be referenced from the init code in Intl.js. |
| library->AddMember(intlNativeInterfaces, Js::PropertyIds::Intl, library->GetIntlObject()); |
| |
| intlNativeInterfaces->SetHasNoEnumerableProperties(true); |
| |
| return true; |
| } |
| |
| void IntlEngineInterfaceExtensionObject::deletePrototypePropertyHelper(ScriptContext* scriptContext, DynamicObject* intlObject, Js::PropertyId objectPropertyId, Js::PropertyId getterFunctionId) |
| { |
| DynamicObject *prototypeObject = nullptr; |
| DynamicObject *functionObj = nullptr; |
| Var propertyValue = nullptr; |
| Var prototypeValue = nullptr; |
| Var resolvedOptionsValue = nullptr; |
| Var getter = nullptr; |
| Var setter = nullptr; |
| |
| if (!JavascriptOperators::GetProperty(intlObject, objectPropertyId, &propertyValue, scriptContext) || |
| !JavascriptOperators::IsObject(propertyValue)) |
| { |
| return; |
| } |
| |
| if (!JavascriptOperators::GetProperty(VarTo<DynamicObject>(propertyValue), Js::PropertyIds::prototype, &prototypeValue, scriptContext) || |
| !JavascriptOperators::IsObject(prototypeValue)) |
| { |
| return; |
| } |
| |
| prototypeObject = VarTo<DynamicObject>(prototypeValue); |
| |
| if (!JavascriptOperators::GetProperty(prototypeObject, Js::PropertyIds::resolvedOptions, &resolvedOptionsValue, scriptContext) || |
| !JavascriptOperators::IsObject(resolvedOptionsValue)) |
| { |
| return; |
| } |
| |
| functionObj = VarTo<DynamicObject>(resolvedOptionsValue); |
| functionObj->SetConfigurable(Js::PropertyIds::prototype, true); |
| functionObj->DeleteProperty(Js::PropertyIds::prototype, Js::PropertyOperationFlags::PropertyOperation_None); |
| |
| if (!JavascriptOperators::GetOwnAccessors(prototypeObject, getterFunctionId, &getter, &setter, scriptContext) || |
| !JavascriptOperators::IsObject(getter)) |
| { |
| return; |
| } |
| |
| functionObj = VarTo<DynamicObject>(getter); |
| functionObj->SetConfigurable(Js::PropertyIds::prototype, true); |
| functionObj->DeleteProperty(Js::PropertyIds::prototype, Js::PropertyOperationFlags::PropertyOperation_None); |
| } |
| |
| void IntlEngineInterfaceExtensionObject::cleanUpIntl(ScriptContext *scriptContext, DynamicObject* intlObject) |
| { |
| this->dateToLocaleString = nullptr; |
| this->dateToLocaleTimeString = nullptr; |
| this->dateToLocaleDateString = nullptr; |
| this->numberToLocaleString = nullptr; |
| this->stringLocaleCompare = nullptr; |
| |
| //Failed to setup Intl; Windows.Globalization.dll is most likely missing. |
| if (Js::JavascriptOperators::HasProperty(intlObject, Js::PropertyIds::Collator)) |
| { |
| intlObject->DeleteProperty(Js::PropertyIds::Collator, Js::PropertyOperationFlags::PropertyOperation_None); |
| } |
| if (Js::JavascriptOperators::HasProperty(intlObject, Js::PropertyIds::NumberFormat)) |
| { |
| intlObject->DeleteProperty(Js::PropertyIds::NumberFormat, Js::PropertyOperationFlags::PropertyOperation_None); |
| } |
| if (Js::JavascriptOperators::HasProperty(intlObject, Js::PropertyIds::DateTimeFormat)) |
| { |
| intlObject->DeleteProperty(Js::PropertyIds::DateTimeFormat, Js::PropertyOperationFlags::PropertyOperation_None); |
| } |
| } |
| |
| void IntlEngineInterfaceExtensionObject::EnsureIntlByteCode(_In_ ScriptContext * scriptContext) |
| { |
| if (this->intlByteCode == nullptr) |
| { |
| SourceContextInfo * sourceContextInfo = scriptContext->GetSourceContextInfo(Js::Constants::NoHostSourceContext, NULL); |
| |
| Assert(sourceContextInfo != nullptr); |
| |
| SRCINFO si; |
| memset((void*)&si, 0, sizeof(si)); |
| si.sourceContextInfo = sourceContextInfo; |
| SRCINFO *hsi = scriptContext->AddHostSrcInfo(&si); |
| uint32 flags = fscrIsLibraryCode | (CONFIG_FLAG(CreateFunctionProxy) && !scriptContext->IsProfiling() ? fscrAllowFunctionProxy : 0); |
| |
| HRESULT hr = Js::ByteCodeSerializer::DeserializeFromBuffer(scriptContext, flags, (LPCUTF8)nullptr, hsi, (byte*)js::Library_Bytecode_Intl, nullptr, &this->intlByteCode); |
| |
| IfFailAssertMsgAndThrowHr(hr, "Failed to deserialize Intl.js bytecode - very probably the bytecode needs to be rebuilt."); |
| |
| this->SetHasBytecode(); |
| } |
| } |
| |
| void IntlEngineInterfaceExtensionObject::InjectIntlLibraryCode(_In_ ScriptContext * scriptContext, DynamicObject* intlObject, IntlInitializationType intlInitializationType) |
| { |
| |
| // Ensure JsBuiltIns are initialized before initializing Intl which uses some of them. |
| scriptContext->GetLibrary()->EnsureArrayBuiltInsAreReady(); |
| scriptContext->GetLibrary()->EnsureMathBuiltInsAreReady(); |
| |
| JavascriptExceptionObject *pExceptionObject = nullptr; |
| #ifdef INTL_WINGLOB |
| WindowsGlobalizationAdapter* globAdapter = GetWindowsGlobalizationAdapter(scriptContext); |
| #endif |
| |
| try { |
| this->EnsureIntlByteCode(scriptContext); |
| Assert(intlByteCode != nullptr); |
| |
| #ifdef INTL_WINGLOB |
| DelayLoadWindowsGlobalization *library = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| #endif |
| |
| JavascriptString* initType = nullptr; |
| |
| #ifdef INTL_WINGLOB |
| HRESULT hr; |
| //Ensure we have initialized all appropriate COM objects for the adapter (we will be using them now) |
| IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureCommonObjectsInitialized(library)); |
| #endif |
| switch (intlInitializationType) |
| { |
| default: |
| AssertMsg(false, "Not a valid intlInitializationType."); |
| // fall thru |
| case IntlInitializationType::Intl: |
| #ifdef INTL_WINGLOB |
| IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureNumberFormatObjectsInitialized(library)); |
| IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureDateTimeFormatObjectsInitialized(library)); |
| #endif |
| initType = scriptContext->GetPropertyString(PropertyIds::Intl); |
| break; |
| case IntlInitializationType::StringPrototype: |
| // No other windows globalization adapter needed. Common adapter should suffice |
| initType = scriptContext->GetPropertyString(PropertyIds::String); |
| break; |
| case IntlInitializationType::DatePrototype: |
| #ifdef INTL_WINGLOB |
| IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureDateTimeFormatObjectsInitialized(library)); |
| #endif |
| initType = scriptContext->GetPropertyString(PropertyIds::Date); |
| break; |
| case IntlInitializationType::NumberPrototype: |
| #ifdef INTL_WINGLOB |
| IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureNumberFormatObjectsInitialized(library)); |
| #endif |
| initType = scriptContext->GetPropertyString(PropertyIds::Number); |
| break; |
| } |
| |
| Js::ScriptFunction *function = scriptContext->GetLibrary()->CreateScriptFunction(intlByteCode->GetNestedFunctionForExecution(0)); |
| |
| #ifdef ENABLE_SCRIPT_PROFILING |
| // If we are profiling, we need to register the script to the profiler callback, so the script compiled event will be sent. |
| if (scriptContext->IsProfiling()) |
| { |
| scriptContext->RegisterScript(function->GetFunctionProxy()); |
| } |
| #endif |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| // Mark we are profiling library code already, so that any initialization library code called here won't be reported to profiler. |
| // Also tell the debugger not to record events during intialization so that we don't leak information about initialization. |
| AutoInitLibraryCodeScope autoInitLibraryCodeScope(scriptContext); |
| #endif |
| |
| Js::Var args[] = { scriptContext->GetLibrary()->GetUndefined(), scriptContext->GetLibrary()->GetEngineInterfaceObject(), initType }; |
| Js::CallInfo callInfo(Js::CallFlags_Value, _countof(args)); |
| |
| Js::Arguments arguments(callInfo, args); |
| scriptContext->GetThreadContext()->ExecuteImplicitCall(function, Js::ImplicitCall_Accessor, [=]()->Js::Var |
| { |
| return JavascriptFunction::CallRootFunctionInScript(function, arguments); |
| }); |
| |
| // Delete prototypes on functions if initialized Intl object |
| if (intlInitializationType == IntlInitializationType::Intl) |
| { |
| deletePrototypePropertyHelper(scriptContext, intlObject, Js::PropertyIds::Collator, Js::PropertyIds::compare); |
| deletePrototypePropertyHelper(scriptContext, intlObject, Js::PropertyIds::NumberFormat, Js::PropertyIds::format); |
| deletePrototypePropertyHelper(scriptContext, intlObject, Js::PropertyIds::DateTimeFormat, Js::PropertyIds::format); |
| } |
| |
| #if DBG_DUMP |
| if (PHASE_DUMP(Js::ByteCodePhase, function->GetFunctionProxy()) && Js::Configuration::Global.flags.Verbose) |
| { |
| DumpByteCode(); |
| } |
| #endif |
| |
| } |
| catch (const JavascriptException& err) |
| { |
| pExceptionObject = err.GetAndClear(); |
| } |
| |
| if (pExceptionObject) |
| { |
| if (intlInitializationType == IntlInitializationType::Intl) |
| { |
| cleanUpIntl(scriptContext, intlObject); |
| } |
| |
| if (pExceptionObject == ThreadContext::GetContextForCurrentThread()->GetPendingOOMErrorObject() || |
| pExceptionObject == ThreadContext::GetContextForCurrentThread()->GetPendingSOErrorObject()) |
| { |
| // Reset factory objects that are might not have fully initialized |
| #ifdef INTL_WINGLOB |
| globAdapter->ResetCommonFactoryObjects(); |
| #endif |
| switch (intlInitializationType) { |
| default: |
| AssertMsg(false, "Not a valid intlInitializationType."); |
| // fall thru |
| case IntlInitializationType::Intl: |
| #ifdef INTL_WINGLOB |
| globAdapter->ResetNumberFormatFactoryObjects(); |
| globAdapter->ResetDateTimeFormatFactoryObjects(); |
| #endif |
| scriptContext->GetLibrary()->ResetIntlObject(); |
| break; |
| case IntlInitializationType::StringPrototype: |
| // No other windows globalization adapter is created. Resetting common adapter should suffice |
| break; |
| case IntlInitializationType::DatePrototype: |
| #ifdef INTL_WINGLOB |
| globAdapter->ResetDateTimeFormatFactoryObjects(); |
| #endif |
| break; |
| case IntlInitializationType::NumberPrototype: |
| #ifdef INTL_WINGLOB |
| globAdapter->ResetNumberFormatFactoryObjects(); |
| #endif |
| break; |
| } |
| |
| JavascriptExceptionOperators::DoThrowCheckClone(pExceptionObject, scriptContext); |
| } |
| |
| #if DEBUG |
| JavascriptExceptionOperators::DoThrowCheckClone(pExceptionObject, scriptContext); |
| #else |
| JavascriptError::ThrowTypeError(scriptContext, JSERR_IntlNotAvailable); |
| #endif |
| } |
| } |
| |
| // First parameter is boolean. |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_RaiseAssert(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| if (args.Info.Count < 2 || !VarIs<JavascriptError>(args.Values[1])) |
| { |
| AssertMsg(false, "Intl's Assert platform API was called incorrectly."); |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| #if DEBUG |
| #ifdef INTL_ICU_DEBUG |
| Output::Print(_u("EntryIntl_RaiseAssert\n")); |
| #endif |
| JavascriptExceptionOperators::Throw(VarTo<JavascriptError>(args.Values[1]), scriptContext); |
| #else |
| return scriptContext->GetLibrary()->GetUndefined(); |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_IsWellFormedLanguageTag(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| #if defined(INTL_ICU) |
| AssertOrFailFastMsg(false, "IsWellFormedLanguageTag is not implemented using ICU"); |
| return nullptr; |
| #else |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| if (args.Info.Count < 2 || !VarIs<JavascriptString>(args.Values[1])) |
| { |
| // IsWellFormedLanguageTag of undefined or non-string is false |
| return scriptContext->GetLibrary()->GetFalse(); |
| } |
| |
| JavascriptString *argString = VarTo<JavascriptString>(args.Values[1]); |
| |
| return TO_JSBOOL(scriptContext, GetWindowsGlobalizationAdapter(scriptContext)->IsWellFormedLanguageTag(scriptContext, argString->GetSz())); |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_NormalizeLanguageTag(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| #if defined(INTL_ICU) |
| INTL_CHECK_ARGS(args.Info.Count == 2 && VarIs<JavascriptString>(args[1])); |
| |
| UErrorCode status = U_ZERO_ERROR; |
| JavascriptString *langtag = UnsafeVarTo<JavascriptString>(args[1]); |
| utf8::WideToNarrow langtag8(langtag->GetSz(), langtag->GetLength()); |
| |
| // ICU doesn't have a full-fledged canonicalization implementation that correctly replaces all preferred values |
| // and grandfathered tags, as required by #sec-canonicalizelanguagetag. |
| // However, passing the locale through uloc_forLanguageTag -> uloc_toLanguageTag gets us most of the way there |
| // by replacing some(?) values, correctly capitalizing the tag, and re-ordering extensions |
| int parsedLength = 0; |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| int forLangTagResultLength = uloc_forLanguageTag(langtag8, localeID, ULOC_FULLNAME_CAPACITY, &parsedLength, &status); |
| AssertOrFailFast(parsedLength >= 0); |
| if (status == U_ILLEGAL_ARGUMENT_ERROR || ((charcount_t) parsedLength) < langtag->GetLength()) |
| { |
| // The string passed in to NormalizeLanguageTag has already passed IsStructurallyValidLanguageTag. |
| // However, duplicate unicode extension keys, such as "de-u-co-phonebk-co-phonebk", are structurally |
| // valid according to RFC5646 yet still trigger U_ILLEGAL_ARGUMENT_ERROR |
| // V8 ~6.2 says that the above language tag is invalid, while SpiderMonkey ~58 handles it. |
| // Until we have a more spec-compliant implementation of CanonicalizeLanguageTag, err on the side |
| // of caution and say it is invalid. |
| // We also check for parsedLength < langtag->GetLength() because there are cases when status == U_ZERO_ERROR |
| // but the langtag was not valid, such as "en-tesTER-TESter" (OSS-Fuzz #6657). |
| // NOTE: make sure we check for `undefined` at the platform.normalizeLanguageTag callsite. |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| // forLangTagResultLength can be 0 if langtag is "und". |
| // uloc_toLanguageTag("") returns "und", so this works out (forLanguageTag can return >= 0 but toLanguageTag must return > 0) |
| ICU_ASSERT(status, forLangTagResultLength >= 0 && ((charcount_t) parsedLength) == langtag->GetLength()); |
| |
| char canonicalized[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| int toLangTagResultLength = uloc_toLanguageTag(localeID, canonicalized, ULOC_FULLNAME_CAPACITY, true, &status); |
| ICU_ASSERT(status, toLangTagResultLength > 0); |
| |
| // allocate toLangTagResultLength + 1 to leave room for null terminator |
| char16 *canonicalized16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, toLangTagResultLength + 1); |
| charcount_t canonicalized16Len = 0; |
| HRESULT hr = utf8::NarrowStringToWideNoAlloc( |
| canonicalized, |
| toLangTagResultLength, |
| canonicalized16, |
| toLangTagResultLength + 1, |
| &canonicalized16Len |
| ); |
| AssertOrFailFast(hr == S_OK && ((int) canonicalized16Len) == toLangTagResultLength); |
| |
| return JavascriptString::NewWithBuffer(canonicalized16, toLangTagResultLength, scriptContext); |
| #else |
| if (args.Info.Count < 2 || !VarIs<JavascriptString>(args.Values[1])) |
| { |
| // NormalizeLanguageTag of undefined or non-string is undefined |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| JavascriptString *argString = VarTo<JavascriptString>(args.Values[1]); |
| JavascriptString *retVal; |
| HRESULT hr; |
| AutoHSTRING str; |
| hr = GetWindowsGlobalizationAdapter(scriptContext)->NormalizeLanguageTag(scriptContext, argString->GetSz(), &str); |
| DelayLoadWindowsGlobalization *wsl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| PCWSTR strBuf = wsl->WindowsGetStringRawBuffer(*str, NULL); |
| retVal = Js::JavascriptString::NewCopySz(strBuf, scriptContext); |
| if (FAILED(hr)) |
| { |
| HandleOOMSOEHR(hr); |
| //If we can't normalize the tag; return undefined. |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| return retVal; |
| #endif |
| } |
| |
| #ifdef INTL_ICU |
| template <const char *(__cdecl *GetAvailableLocalesFunc)(int), int(__cdecl *CountAvailableLocalesFunc)(void)> |
| static bool BinarySearchForLocale(const char *localeID) |
| { |
| const int count = CountAvailableLocalesFunc(); |
| int left = 0; |
| int right = count - 1; |
| int iterations = 0; |
| while (true) |
| { |
| iterations += 1; |
| if (left > right) |
| { |
| INTL_TRACE("Could not find localeID %S in %d iterations", localeID, iterations); |
| return false; |
| } |
| |
| int i = (left + right) / 2; |
| Assert(i >= 0 && i < count); |
| |
| const char *cur = GetAvailableLocalesFunc(i); |
| |
| // Ensure that this list is actually binary searchable |
| Assert(i > 0 ? strcmp(GetAvailableLocalesFunc(i - 1), cur) < 0 : true); |
| Assert(i < count - 1 ? strcmp(GetAvailableLocalesFunc(i + 1), cur) > 0 : true); |
| |
| int res = strcmp(localeID, cur); |
| if (res == 0) |
| { |
| INTL_TRACE("Found localeID %S in %d iterations", localeID, iterations); |
| return true; |
| } |
| else if (res < 0) |
| { |
| right = i - 1; |
| } |
| else |
| { |
| left = i + 1; |
| } |
| } |
| } |
| |
| template <const char *(__cdecl *GetAvailableLocalesFunc)(int), int(__cdecl *CountAvailableLocalesFunc)(void)> |
| static bool IsLocaleAvailable(JavascriptString *langtag) |
| { |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| LangtagToLocaleID(langtag, localeID); |
| |
| if (!BinarySearchForLocale<GetAvailableLocalesFunc, CountAvailableLocalesFunc>(localeID)) |
| { |
| // ICU's "available locales" do not include (all? most?) aliases. |
| // For example, searching for "zh_TW" will return false, even though |
| // zh_TW is equivalent to zh_Hant_TW, which would return true. |
| // We can work around this by searching for both the locale as requested and |
| // the locale in addition to all of its "likely subtags." |
| // This works in practice because, for instance, unum_open("zh_TW") will actually |
| // use "zh_Hant_TW" (confirmed with unum_getLocaleByType(..., ULOC_VALID_LOCALE)) |
| // The code below performs, for example, the following mappings: |
| // pa_PK -> pa_Arab_PK |
| // sr_RS -> sr_Cyrl_RS |
| // zh_CN -> zh_Hans_CN |
| // zh_TW -> zh_Hant_TW |
| // TODO(jahorto): Determine if there is any scenario where a language tag + likely subtags is |
| // not exactly functionally equivalent to the language tag on its own -- basically, where |
| // constructor_open(language_tag) behaves differently to constructor_open(language_tag_and_likely_subtags) |
| // for all supported constructors. |
| UErrorCode status = U_ZERO_ERROR; |
| char localeIDWithLikelySubtags[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| int likelySubtagLen = uloc_addLikelySubtags(localeID, localeIDWithLikelySubtags, ULOC_FULLNAME_CAPACITY, &status); |
| ICU_ASSERT(status, likelySubtagLen > 0 && likelySubtagLen < ULOC_FULLNAME_CAPACITY); |
| |
| return BinarySearchForLocale<GetAvailableLocalesFunc, CountAvailableLocalesFunc>(localeIDWithLikelySubtags); |
| } |
| |
| return true; |
| } |
| #endif |
| |
| #ifdef INTL_ICU |
| #define DEFINE_ISXLOCALEAVAILABLE(ctorShortName, icuNamespace) \ |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_Is##ctorShortName##LocaleAvailable(RecyclableObject* function, CallInfo callInfo, ...) \ |
| { \ |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); \ |
| INTL_CHECK_ARGS(args.Info.Count == 2 && VarIs<JavascriptString>(args.Values[1])); \ |
| return scriptContext->GetLibrary()->GetTrueOrFalse( \ |
| IsLocaleAvailable<##icuNamespace##_getAvailable, ##icuNamespace##_countAvailable>(UnsafeVarTo<JavascriptString>(args.Values[1])) \ |
| ); \ |
| } |
| #else |
| #define DEFINE_ISXLOCALEAVAILABLE(ctorShortName, icuNamespace) \ |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_Is##ctorShortName##LocaleAvailable(RecyclableObject* function, CallInfo callInfo, ...) \ |
| { \ |
| AssertOrFailFastMsg(false, "Intl with Windows Globalization should never call Is" #ctorShortName "LocaleAvailable"); \ |
| return nullptr; \ |
| } |
| #endif |
| |
| DEFINE_ISXLOCALEAVAILABLE(Collator, ucol) |
| DEFINE_ISXLOCALEAVAILABLE(NF, unum) |
| DEFINE_ISXLOCALEAVAILABLE(DTF, udat) |
| // uplrules namespace doesn't have its own getAvailable/countAvailable |
| // assume it supports whatever is supported in the base data |
| DEFINE_ISXLOCALEAVAILABLE(PR, uloc) |
| |
| #ifdef INTL_ICU |
| |
| |
| #endif |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_GetLocaleData(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| #ifdef INTL_ICU |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| INTL_CHECK_ARGS( |
| args.Info.Count == 3 && |
| (JavascriptNumber::Is(args.Values[1]) || TaggedInt::Is(args.Values[1])) && |
| VarIs<JavascriptString>(args.Values[2]) |
| ); |
| |
| LocaleDataKind kind = (LocaleDataKind) (TaggedInt::Is(args.Values[1]) |
| ? TaggedInt::ToInt32(args.Values[1]) |
| : (int) JavascriptNumber::GetValue(args.Values[1])); |
| |
| JavascriptArray *ret = nullptr; |
| |
| UErrorCode status = U_ZERO_ERROR; |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| JavascriptString *langtag = UnsafeVarTo<JavascriptString>(args.Values[2]); |
| LangtagToLocaleID(langtag, localeID); |
| |
| JavascriptLibrary *library = scriptContext->GetLibrary(); |
| PropertyOperationFlags flag = PropertyOperationFlags::PropertyOperation_None; |
| |
| if (kind == LocaleDataKind::Collation) |
| { |
| ScopedUEnumeration collations(ucol_getKeywordValuesForLocale("collation", localeID, false, &status)); |
| int collationsCount = uenum_count(collations, &status); |
| |
| // we expect collationsCount to have at least "standard" and "search" in it |
| ICU_ASSERT(status, collationsCount > 2); |
| |
| // the return array can't include "standard" and "search", but must have its first element be null (count - 2 + 1) [#sec-intl-collator-internal-slots] |
| ret = library->CreateArray(collationsCount - 1); |
| ret->SetItem(0, library->GetNull(), flag); |
| |
| int collationLen = 0; |
| const char *collation = nullptr; |
| int i = 0; |
| for (collation = uenum_next(collations, &collationLen, &status); collation != nullptr; collation = uenum_next(collations, &collationLen, &status)) |
| { |
| ICU_ASSERT(status, collation != nullptr && collationLen > 0); |
| if (strcmp(collation, "standard") == 0 || strcmp(collation, "search") == 0) |
| { |
| // continue does not create holes in ret because i is set outside the loop |
| continue; |
| } |
| |
| // OS#17172584: OOM during uloc_toUnicodeLocaleType can make this return nullptr even for known collations |
| const char *unicodeCollation = ThrowOOMIfNull(uloc_toUnicodeLocaleType("collation", collation)); |
| const size_t unicodeCollationLen = strlen(unicodeCollation); |
| |
| // we only need strlen(unicodeCollation) + 1 char16s because unicodeCollation will always be ASCII (funnily enough) |
| char16 *unicodeCollation16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, unicodeCollationLen + 1); |
| charcount_t unicodeCollation16Len = 0; |
| HRESULT hr = utf8::NarrowStringToWideNoAlloc( |
| unicodeCollation, |
| unicodeCollationLen, |
| unicodeCollation16, |
| unicodeCollationLen + 1, |
| &unicodeCollation16Len |
| ); |
| AssertOrFailFastMsg( |
| hr == S_OK && unicodeCollation16Len == unicodeCollationLen && unicodeCollation16Len < MaxCharCount, |
| "Unicode collation char16 conversion was unsuccessful" |
| ); |
| // i + 1 to not overwrite leading null element |
| ret->SetItem(i + 1, JavascriptString::NewWithBuffer( |
| unicodeCollation16, |
| unicodeCollation16Len, |
| scriptContext |
| ), PropertyOperationFlags::PropertyOperation_None); |
| i++; |
| } |
| } |
| else if (kind == LocaleDataKind::CaseFirst) |
| { |
| ScopedUCollator collator(ucol_open(localeID, &status)); |
| UColAttributeValue kf = ucol_getAttribute(collator, UCOL_CASE_FIRST, &status); |
| ICU_ASSERT(status, true); |
| ret = library->CreateArray(3); |
| |
| JavascriptString *falseStr = library->GetFalseDisplayString(); |
| JavascriptString *upperStr = library->GetIntlCaseFirstUpperString(); |
| JavascriptString *lowerStr = library->GetIntlCaseFirstLowerString(); |
| |
| if (kf == UCOL_OFF) |
| { |
| ret->SetItem(0, falseStr, flag); |
| ret->SetItem(1, upperStr, flag); |
| ret->SetItem(2, lowerStr, flag); |
| } |
| else if (kf == UCOL_UPPER_FIRST) |
| { |
| ret->SetItem(0, upperStr, flag); |
| ret->SetItem(1, lowerStr, flag); |
| ret->SetItem(2, falseStr, flag); |
| } |
| else if (kf == UCOL_LOWER_FIRST) |
| { |
| ret->SetItem(0, lowerStr, flag); |
| ret->SetItem(1, upperStr, flag); |
| ret->SetItem(2, falseStr, flag); |
| } |
| } |
| else if (kind == LocaleDataKind::Numeric) |
| { |
| ScopedUCollator collator(ucol_open(localeID, &status)); |
| UColAttributeValue kn = ucol_getAttribute(collator, UCOL_NUMERIC_COLLATION, &status); |
| ICU_ASSERT(status, true); |
| ret = library->CreateArray(2); |
| |
| JavascriptString *falseStr = library->GetFalseDisplayString(); |
| JavascriptString *trueStr = library->GetTrueDisplayString(); |
| |
| if (kn == UCOL_OFF) |
| { |
| ret->SetItem(0, falseStr, flag); |
| ret->SetItem(1, trueStr, flag); |
| } |
| else if (kn == UCOL_ON) |
| { |
| ret->SetItem(0, trueStr, flag); |
| ret->SetItem(1, falseStr, flag); |
| } |
| } |
| else if (kind == LocaleDataKind::Calendar) |
| { |
| ScopedUEnumeration calendars(ucal_getKeywordValuesForLocale("calendar", localeID, false, &status)); |
| ret = library->CreateArray(uenum_count(calendars, &status)); |
| ICU_ASSERT(status, true); |
| |
| int calendarLen = 0; |
| const char *calendar = nullptr; |
| int i = 0; |
| for (calendar = uenum_next(calendars, &calendarLen, &status); calendar != nullptr; calendar = uenum_next(calendars, &calendarLen, &status)) |
| { |
| ICU_ASSERT(status, calendar != nullptr && calendarLen > 0); |
| |
| // OS#17172584: OOM during uloc_toUnicodeLocaleType can make this return nullptr even for known calendars |
| const char *unicodeCalendar = ThrowOOMIfNull(uloc_toUnicodeLocaleType("calendar", calendar)); |
| const size_t unicodeCalendarLen = strlen(unicodeCalendar); |
| |
| // we only need strlen(unicodeCalendar) + 1 char16s because unicodeCalendar will always be ASCII (funnily enough) |
| char16 *unicodeCalendar16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, strlen(unicodeCalendar) + 1); |
| charcount_t unicodeCalendar16Len = 0; |
| HRESULT hr = utf8::NarrowStringToWideNoAlloc( |
| unicodeCalendar, |
| unicodeCalendarLen, |
| unicodeCalendar16, |
| unicodeCalendarLen + 1, |
| &unicodeCalendar16Len |
| ); |
| AssertOrFailFastMsg( |
| hr == S_OK && unicodeCalendar16Len == unicodeCalendarLen && unicodeCalendar16Len < MaxCharCount, |
| "Unicode calendar char16 conversion was unsuccessful" |
| ); |
| ret->SetItem(i, JavascriptString::NewWithBuffer( |
| unicodeCalendar16, |
| static_cast<charcount_t>(unicodeCalendar16Len), |
| scriptContext |
| ), flag); |
| i++; |
| } |
| } |
| else if (kind == LocaleDataKind::NumberingSystem) |
| { |
| // unumsys_openAvailableNames has multiple bugs (http://bugs.icu-project.org/trac/ticket/11908) and also |
| // does not provide a locale-specific set of numbering systems |
| // the Intl spec provides a list of required numbering systems to support in #table-numbering-system-digits |
| // For now, assume that all of those numbering systems are supported, and just get the default using unumsys_open |
| // unumsys_open will also ensure that "native", "traditio", and "finance" are not returned, as per #sec-intl.datetimeformat-internal-slots |
| ScopedUNumberingSystem numsys(unumsys_open(localeID, &status)); |
| ICU_ASSERT(status, true); |
| utf8::NarrowToWide numsysName(unumsys_getName(numsys)); |
| |
| // NOTE: update the initial array length if the list of available numbering systems changes in the future! |
| ret = library->CreateArray(22); |
| int i = 0; |
| ret->SetItem(i++, JavascriptString::NewCopySz(numsysName, scriptContext), flag); |
| |
| // It doesn't matter that item 0 will be in the array twice (aside for size), because item 0 is the |
| // preferred numbering system for the given locale, so it has precedence over everything else |
| ret->SetItem(i++, library->GetIntlNumsysArabString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysArabextString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysBaliString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysBengString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysDevaString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysFullwideString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysGujrString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysGuruString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysHanidecString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysKhmrString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysKndaString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysLaooString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysLatnString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysLimbString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysMlymString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysMongString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysMymrString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysOryaString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysTamldecString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysTeluString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysThaiString(), flag); |
| ret->SetItem(i++, library->GetIntlNumsysTibtString(), flag); |
| } |
| else if (kind == LocaleDataKind::HourCycle) |
| { |
| // #sec-intl.datetimeformat-internal-slots: "[[LocaleData]][locale].hc must be < null, h11, h12, h23, h24 > for all locale values" |
| ret = library->CreateArray(5); |
| int i = 0; |
| ret->SetItem(i++, library->GetNull(), flag); |
| ret->SetItem(i++, library->GetIntlHourCycle11String(), flag); |
| ret->SetItem(i++, library->GetIntlHourCycle12String(), flag); |
| ret->SetItem(i++, library->GetIntlHourCycle23String(), flag); |
| ret->SetItem(i++, library->GetIntlHourCycle24String(), flag); |
| } |
| else |
| { |
| AssertOrFailFastMsg(false, "GetLocaleData called with unknown kind parameter"); |
| } |
| |
| return ret; |
| #else |
| AssertOrFailFastMsg(false, "Intl with Windows Globalization should never call GetLocaleData"); |
| return nullptr; |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_ResolveLocaleLookup(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| if (args.Info.Count < 2 || !VarIs<JavascriptString>(args.Values[1])) |
| { |
| // ResolveLocaleLookup of undefined or non-string is undefined |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| #if defined(INTL_ICU) |
| #if defined(INTL_ICU_DEBUG) |
| Output::Print(_u("Intl::ResolveLocaleLookup returned false: EntryIntl_ResolveLocaleLookup returning null to fallback to JS\n")); |
| #endif |
| return scriptContext->GetLibrary()->GetNull(); |
| #else |
| JavascriptString *argString = VarTo<JavascriptString>(args.Values[1]); |
| PCWSTR passedLocale = argString->GetSz(); |
| // REVIEW should we zero the whole array for safety? |
| WCHAR resolvedLocaleName[LOCALE_NAME_MAX_LENGTH]; |
| resolvedLocaleName[0] = '\0'; |
| |
| ResolveLocaleName(passedLocale, resolvedLocaleName, _countof(resolvedLocaleName)); |
| if (resolvedLocaleName[0] == '\0') |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| return JavascriptString::NewCopySz(resolvedLocaleName, scriptContext); |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_ResolveLocaleBestFit(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| if (args.Info.Count < 2 || !VarIs<JavascriptString>(args.Values[1])) |
| { |
| // NormalizeLanguageTag of undefined or non-string is undefined |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| #if defined(INTL_ICU) |
| AssertOrFailFastMsg(false, "Intl-ICU does not implement ResolveLocaleBestFit"); |
| return nullptr; |
| #else // !INTL_ICU |
| JavascriptString *localeStrings = VarTo<JavascriptString>(args.Values[1]); |
| PCWSTR passedLocale = localeStrings->GetSz(); |
| DelayLoadWindowsGlobalization* wgl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext); |
| |
| AutoCOMPtr<DateTimeFormatting::IDateTimeFormatter> formatter; |
| HRESULT hr; |
| if (FAILED(hr = wga->CreateDateTimeFormatter(scriptContext, _u("longdate"), &passedLocale, 1, nullptr, nullptr, &formatter))) |
| { |
| HandleOOMSOEHR(hr); |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| AutoHSTRING locale; |
| if (FAILED(hr = wga->GetResolvedLanguage(formatter, &locale))) |
| { |
| HandleOOMSOEHR(hr); |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| return JavascriptString::NewCopySz(wgl->WindowsGetStringRawBuffer(*locale, NULL), scriptContext); |
| |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_GetDefaultLocale(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| #ifdef INTL_WINGLOB |
| char16 defaultLocale[LOCALE_NAME_MAX_LENGTH]; |
| defaultLocale[0] = '\0'; |
| |
| if (GetUserDefaultLocaleName(defaultLocale, _countof(defaultLocale)) == 0) |
| { |
| JavascriptError::MapAndThrowError(scriptContext, HRESULT_FROM_WIN32(GetLastError())); |
| } |
| |
| return JavascriptString::NewCopySz(defaultLocale, scriptContext); |
| #else |
| UErrorCode status = U_ZERO_ERROR; |
| char defaultLangtag[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| char defaultLocaleID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| |
| int localeIDActual = uloc_getName(nullptr, defaultLocaleID, _countof(defaultLocaleID), &status); |
| ICU_ASSERT(status, localeIDActual > 0 && localeIDActual < _countof(defaultLocaleID)); |
| |
| int langtagActual = uloc_toLanguageTag(defaultLocaleID, defaultLangtag, _countof(defaultLangtag), true, &status); |
| ICU_ASSERT(status, langtagActual > 0 && langtagActual < _countof(defaultLangtag)); |
| |
| char16 *defaultLangtag16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, langtagActual + 1); |
| charcount_t defaultLangtag16Actual = 0; |
| utf8::NarrowStringToWideNoAlloc(defaultLangtag, static_cast<size_t>(langtagActual), defaultLangtag16, langtagActual + 1, &defaultLangtag16Actual); |
| AssertOrFailFastMsg(defaultLangtag16Actual == static_cast<size_t>(langtagActual), "Language tags should always be ASCII"); |
| return JavascriptString::NewWithBuffer(defaultLangtag16, defaultLangtag16Actual, scriptContext); |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_GetExtensions(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| if (args.Info.Count < 2 || !VarIs<JavascriptString>(args.Values[1])) |
| { |
| // NormalizeLanguageTag of undefined or non-string is undefined |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| #ifdef INTL_WINGLOB |
| DelayLoadWindowsGlobalization* wgl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext); |
| |
| AutoCOMPtr<ILanguage> language; |
| AutoCOMPtr<ILanguageExtensionSubtags> extensionSubtags; |
| HRESULT hr; |
| if (FAILED(hr = wga->CreateLanguage(scriptContext, VarTo<JavascriptString>(args.Values[1])->GetSz(), &language))) |
| { |
| HandleOOMSOEHR(hr); |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| if (FAILED(hr = language->QueryInterface(__uuidof(ILanguageExtensionSubtags), reinterpret_cast<void**>(&extensionSubtags)))) |
| { |
| HandleOOMSOEHR(hr); |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| Assert(extensionSubtags); |
| |
| AutoHSTRING singletonString; |
| AutoCOMPtr<Windows::Foundation::Collections::IVectorView<HSTRING>> subtags; |
| uint32 length; |
| |
| if (FAILED(hr = wgl->WindowsCreateString(_u("u"), 1, &singletonString)) || FAILED(hr = extensionSubtags->GetExtensionSubtags(*singletonString, &subtags)) || FAILED(subtags->get_Size(&length))) |
| { |
| HandleOOMSOEHR(hr); |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| JavascriptArray *toReturn = scriptContext->GetLibrary()->CreateArray(length); |
| |
| for (uint32 i = 0; i < length; i++) |
| { |
| AutoHSTRING str; |
| if (!FAILED(hr = wga->GetItemAt(subtags, i, &str))) |
| { |
| toReturn->SetItem(i, JavascriptString::NewCopySz(wgl->WindowsGetStringRawBuffer(*str, NULL), scriptContext), Js::PropertyOperationFlags::PropertyOperation_None); |
| } |
| else |
| { |
| HandleOOMSOEHR(hr); |
| } |
| } |
| |
| return toReturn; |
| #else |
| AssertOrFailFastMsg(false, "ICU should not be calling platform.getExtensions"); |
| return nullptr; |
| #endif |
| } |
| |
| #ifdef INTL_ICU |
| // This is used by both NumberFormat and PluralRules |
| static void SetUNumberFormatDigitOptions(UNumberFormat *fmt, DynamicObject *state) |
| { |
| if (JavascriptOperators::HasProperty(state, PropertyIds::minimumSignificantDigits)) |
| { |
| unum_setAttribute(fmt, UNUM_SIGNIFICANT_DIGITS_USED, true); |
| unum_setAttribute(fmt, UNUM_MIN_SIGNIFICANT_DIGITS, AssertIntegerProperty(state, PropertyIds::minimumSignificantDigits)); |
| unum_setAttribute(fmt, UNUM_MAX_SIGNIFICANT_DIGITS, AssertIntegerProperty(state, PropertyIds::maximumSignificantDigits)); |
| } |
| else |
| { |
| unum_setAttribute(fmt, UNUM_MIN_INTEGER_DIGITS, AssertIntegerProperty(state, PropertyIds::minimumIntegerDigits)); |
| unum_setAttribute(fmt, UNUM_MIN_FRACTION_DIGITS, AssertIntegerProperty(state, PropertyIds::minimumFractionDigits)); |
| unum_setAttribute(fmt, UNUM_MAX_FRACTION_DIGITS, AssertIntegerProperty(state, PropertyIds::maximumFractionDigits)); |
| } |
| } |
| #endif |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_CacheNumberFormat(RecyclableObject * function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| INTL_CHECK_ARGS(args.Info.Count == 2 && DynamicObject::IsBaseDynamicObject(args.Values[1])); |
| |
| #if defined(INTL_ICU) |
| DynamicObject *state = UnsafeVarTo<DynamicObject>(args.Values[1]); |
| |
| // always AssertOrFailFast that the properties we need are there, because if they aren't, Intl.js isn't functioning correctly |
| NumberFormatStyle style = AssertEnumProperty<NumberFormatStyle>(state, PropertyIds::formatterToUse); |
| |
| UNumberFormatStyle unumStyle = UNUM_IGNORE; |
| UErrorCode status = U_ZERO_ERROR; |
| JavascriptString *currency = nullptr; |
| |
| JavascriptString *langtag = AssertStringProperty(state, PropertyIds::locale); |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| LangtagToLocaleID(langtag, localeID); |
| |
| if (style == NumberFormatStyle::Decimal) |
| { |
| unumStyle = UNUM_DECIMAL; |
| } |
| else if (style == NumberFormatStyle::Percent) |
| { |
| unumStyle = UNUM_PERCENT; |
| } |
| else if (style == NumberFormatStyle::Currency) |
| { |
| NumberFormatCurrencyDisplay nfcd = AssertEnumProperty<NumberFormatCurrencyDisplay>(state, PropertyIds::currencyDisplayToUse); |
| |
| // TODO(jahorto): Investigate making our enum values equal to the corresponding UNumberFormatStyle values |
| if (nfcd == NumberFormatCurrencyDisplay::Symbol) |
| { |
| unumStyle = UNUM_CURRENCY; |
| } |
| else if (nfcd == NumberFormatCurrencyDisplay::Code) |
| { |
| unumStyle = UNUM_CURRENCY_ISO; |
| } |
| else if (nfcd == NumberFormatCurrencyDisplay::Name) |
| { |
| unumStyle = UNUM_CURRENCY_PLURAL; |
| } |
| |
| currency = AssertStringProperty(state, PropertyIds::currency); |
| } |
| |
| AssertOrFailFast(unumStyle != UNUM_IGNORE); |
| |
| auto fmt = FinalizableUNumberFormat::New(scriptContext->GetRecycler(), unum_open(unumStyle, nullptr, 0, localeID, nullptr, &status)); |
| ICU_ASSERT(status, true); |
| |
| bool groupingUsed = AssertBooleanProperty(state, PropertyIds::useGrouping); |
| unum_setAttribute(*fmt, UNUM_GROUPING_USED, groupingUsed); |
| |
| unum_setAttribute(*fmt, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); |
| |
| SetUNumberFormatDigitOptions(*fmt, state); |
| |
| if (currency != nullptr) |
| { |
| unum_setTextAttribute(*fmt, UNUM_CURRENCY_CODE, reinterpret_cast<const UChar *>(currency->GetSz()), currency->GetLength(), &status); |
| ICU_ASSERT(status, true); |
| } |
| |
| state->SetInternalProperty( |
| InternalPropertyIds::CachedUNumberFormat, |
| fmt, |
| PropertyOperationFlags::PropertyOperation_None, |
| nullptr |
| ); |
| |
| return scriptContext->GetLibrary()->GetUndefined(); |
| #else |
| HRESULT hr = S_OK; |
| JavascriptString *localeJSstr = nullptr; |
| DynamicObject *options = VarTo<DynamicObject>(args.Values[1]); |
| DelayLoadWindowsGlobalization* wgl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext); |
| Var propertyValue; |
| |
| // Verify locale is present |
| // REVIEW (doilij): Fix comparison of the unsigned value <= 0 |
| if (!GetTypedPropertyBuiltInFrom(options, __locale, JavascriptString) || (localeJSstr = VarTo<JavascriptString>(propertyValue))->GetLength() <= 0) |
| { |
| // REVIEW (doilij): Should we throw? Or otherwise, from Intl.js, should detect something didn't work right here... |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| //First we have to determine which formatter(number, percent, or currency) we will be using. |
| //Note some options might not be present. |
| AutoCOMPtr<NumberFormatting::INumberFormatter> numberFormatter(nullptr); |
| PCWSTR locale = localeJSstr->GetSz(); |
| |
| uint16 formatterToUseVal = 0; // 0 (default) is number, 1 is percent, 2 is currency |
| if (GetTypedPropertyBuiltInFrom(options, __formatterToUse, TaggedInt) && (formatterToUseVal = TaggedInt::ToUInt16(propertyValue)) == 1) |
| { |
| //Use the percent formatter |
| IfFailThrowHr(wga->CreatePercentFormatter(scriptContext, &locale, 1, &numberFormatter)); |
| } |
| else if (formatterToUseVal == 2) |
| { |
| //Use the currency formatter |
| AutoCOMPtr<NumberFormatting::ICurrencyFormatter> currencyFormatter(nullptr); |
| if (!GetTypedPropertyBuiltInFrom(options, __currency, JavascriptString)) |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| //API call retrieves a currency formatter, have to query its interface for numberFormatter |
| IfFailThrowHr(GetWindowsGlobalizationAdapter(scriptContext)->CreateCurrencyFormatter(scriptContext, &locale, 1, VarTo<JavascriptString>(propertyValue)->GetSz(), ¤cyFormatter)); |
| |
| if (GetTypedPropertyBuiltInFrom(options, __currencyDisplayToUse, TaggedInt)) // 0 is for symbol, 1 is for code, 2 is for name. |
| //Currently name isn't supported; so it will default to code in that case. |
| { |
| AutoCOMPtr<NumberFormatting::ICurrencyFormatter2> currencyFormatter2(nullptr); |
| IfFailThrowHr(currencyFormatter->QueryInterface(__uuidof(NumberFormatting::ICurrencyFormatter2), reinterpret_cast<void**>(¤cyFormatter2))); |
| |
| if (TaggedInt::ToUInt16(propertyValue) == 0) |
| { |
| IfFailThrowHr(currencyFormatter2->put_Mode(NumberFormatting::CurrencyFormatterMode::CurrencyFormatterMode_UseSymbol)); |
| } |
| else |
| { |
| IfFailThrowHr(currencyFormatter2->put_Mode(NumberFormatting::CurrencyFormatterMode::CurrencyFormatterMode_UseCurrencyCode)); |
| } |
| } |
| |
| IfFailThrowHr(currencyFormatter->QueryInterface(__uuidof(NumberFormatting::INumberFormatter), reinterpret_cast<void**>(&numberFormatter))); |
| } |
| else |
| { |
| //Use the number formatter (default) |
| IfFailThrowHr(wga->CreateNumberFormatter(scriptContext, &locale, 1, &numberFormatter)); |
| } |
| Assert(numberFormatter); |
| |
| AutoCOMPtr<NumberFormatting::ISignedZeroOption> signedZeroOption(nullptr); |
| IfFailThrowHr(numberFormatter->QueryInterface(__uuidof(NumberFormatting::ISignedZeroOption), reinterpret_cast<void**>(&signedZeroOption))); |
| IfFailThrowHr(signedZeroOption->put_IsZeroSigned(true)); |
| |
| //Configure non-digit related options |
| AutoCOMPtr<NumberFormatting::INumberFormatterOptions> numberFormatterOptions(nullptr); |
| IfFailThrowHr(numberFormatter->QueryInterface(__uuidof(NumberFormatting::INumberFormatterOptions), reinterpret_cast<void**>(&numberFormatterOptions))); |
| Assert(numberFormatterOptions); |
| |
| if (GetTypedPropertyBuiltInFrom(options, __useGrouping, JavascriptBoolean)) |
| { |
| IfFailThrowHr(numberFormatterOptions->put_IsGrouped((boolean)(VarTo<JavascriptBoolean>(propertyValue)->GetValue()))); |
| } |
| |
| //Get the numeral system and add it to the object since it will be located in the locale |
| AutoHSTRING hNumeralSystem; |
| AutoHSTRING hResolvedLanguage; |
| uint32 length; |
| IfFailThrowHr(wga->GetNumeralSystem(numberFormatterOptions, &hNumeralSystem)); |
| SetHSTRINGPropertyBuiltInOn(options, __numberingSystem, *hNumeralSystem); |
| |
| IfFailThrowHr(wga->GetResolvedLanguage(numberFormatterOptions, &hResolvedLanguage)); |
| SetHSTRINGPropertyBuiltInOn(options, __locale, *hResolvedLanguage); |
| |
| AutoCOMPtr<NumberFormatting::INumberRounderOption> rounderOptions(nullptr); |
| IfFailThrowHr(numberFormatter->QueryInterface(__uuidof(NumberFormatting::INumberRounderOption), reinterpret_cast<void**>(&rounderOptions))); |
| Assert(rounderOptions); |
| |
| if (HasPropertyBuiltInOn(options, __minimumSignificantDigits) || HasPropertyBuiltInOn(options, __maximumSignificantDigits)) |
| { |
| uint16 minSignificantDigits = 1, maxSignificantDigits = 21; |
| //Do significant digit rounding |
| if (GetTypedPropertyBuiltInFrom(options, __minimumSignificantDigits, TaggedInt)) |
| { |
| minSignificantDigits = max<uint16>(min<uint16>(TaggedInt::ToUInt16(propertyValue), 21), 1); |
| } |
| if (GetTypedPropertyBuiltInFrom(options, __maximumSignificantDigits, TaggedInt)) |
| { |
| maxSignificantDigits = max<uint16>(min<uint16>(TaggedInt::ToUInt16(propertyValue), 21), minSignificantDigits); |
| } |
| prepareWithSignificantDigits(scriptContext, rounderOptions, numberFormatter, numberFormatterOptions, minSignificantDigits, maxSignificantDigits); |
| } |
| else |
| { |
| uint16 minFractionDigits = 0, maxFractionDigits = 3, minIntegerDigits = 1; |
| //Do fraction/integer digit rounding |
| if (GetTypedPropertyBuiltInFrom(options, __minimumIntegerDigits, TaggedInt)) |
| { |
| minIntegerDigits = max<uint16>(min<uint16>(TaggedInt::ToUInt16(propertyValue), 21), 1); |
| } |
| if (GetTypedPropertyBuiltInFrom(options, __minimumFractionDigits, TaggedInt)) |
| { |
| minFractionDigits = min<uint16>(TaggedInt::ToUInt16(propertyValue), 20);//ToUInt16 will get rid of negatives by making them high |
| } |
| if (GetTypedPropertyBuiltInFrom(options, __maximumFractionDigits, TaggedInt)) |
| { |
| maxFractionDigits = max(min<uint16>(TaggedInt::ToUInt16(propertyValue), 20), minFractionDigits);//ToUInt16 will get rid of negatives by making them high |
| } |
| prepareWithFractionIntegerDigits(scriptContext, rounderOptions, numberFormatterOptions, minFractionDigits, maxFractionDigits + (formatterToUseVal == 1 ? 2 : 0), minIntegerDigits);//extend max fractions for percent |
| } |
| |
| //Set the object as a cache |
| numberFormatter->AddRef(); |
| options->SetInternalProperty(Js::InternalPropertyIds::HiddenObject, AutoCOMJSObject::New(scriptContext->GetRecycler(), numberFormatter), Js::PropertyOperationFlags::PropertyOperation_None, NULL); |
| |
| return scriptContext->GetLibrary()->GetUndefined(); |
| #endif |
| } |
| |
| // Unlike CacheNumberFormat; this call takes an additional parameter to specify whether we are going to cache it. |
| // We have to create this formatter twice; first time get the date/time patterns; and second time cache with correct format string. |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_CreateDateTimeFormat(RecyclableObject * function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| if (args.Info.Count < 3 || !DynamicObject::IsBaseDynamicObject(args.Values[1]) || !VarIs<JavascriptBoolean>(args.Values[2])) |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| #ifdef INTL_WINGLOB |
| DynamicObject* obj = VarTo<DynamicObject>(args.Values[1]); |
| |
| DelayLoadWindowsGlobalization* wgl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext); |
| |
| HRESULT hr; |
| Var propertyValue = nullptr; |
| uint32 length; |
| |
| PCWSTR locale = GetTypedPropertyBuiltInFrom(obj, __locale, JavascriptString) ? VarTo<JavascriptString>(propertyValue)->GetSz() : nullptr; |
| PCWSTR templateString = GetTypedPropertyBuiltInFrom(obj, __templateString, JavascriptString) ? VarTo<JavascriptString>(propertyValue)->GetSz() : nullptr; |
| |
| if (locale == nullptr || templateString == nullptr) |
| { |
| AssertMsg(false, "For some reason, locale and templateString aren't defined or aren't a JavascriptString."); |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| PCWSTR clock = GetTypedPropertyBuiltInFrom(obj, __windowsClock, JavascriptString) ? VarTo<JavascriptString>(propertyValue)->GetSz() : nullptr; |
| |
| AutoHSTRING hDummyCalendar; |
| if (clock != nullptr) |
| { |
| //Because both calendar and clock are needed to pass into the datetimeformatter constructor (or neither); create a dummy one to get the value of calendar out so clock can be passed in with it. |
| AutoCOMPtr<DateTimeFormatting::IDateTimeFormatter> dummyFormatter; |
| IfFailThrowHr(wga->CreateDateTimeFormatter(scriptContext, templateString, &locale, 1, nullptr, nullptr, &dummyFormatter)); |
| |
| IfFailThrowHr(wga->GetCalendar(dummyFormatter, &hDummyCalendar)); |
| } |
| |
| //Now create the real formatter. |
| AutoCOMPtr<DateTimeFormatting::IDateTimeFormatter> cachedFormatter; |
| IfFailThrowHr(wga->CreateDateTimeFormatter(scriptContext, templateString, &locale, 1, |
| clock == nullptr ? nullptr : wgl->WindowsGetStringRawBuffer(*hDummyCalendar, &length), clock, &cachedFormatter)); |
| |
| AutoHSTRING hCalendar; |
| AutoHSTRING hClock; |
| AutoHSTRING hLocale; |
| AutoHSTRING hNumberingSystem; |
| //In case the upper code path wasn't hit; extract the calendar string again so it can be set. |
| IfFailThrowHr(wga->GetCalendar(cachedFormatter, &hCalendar)); |
| SetHSTRINGPropertyBuiltInOn(obj, __windowsCalendar, *hCalendar); |
| |
| IfFailThrowHr(wga->GetClock(cachedFormatter, &hClock)); |
| SetHSTRINGPropertyBuiltInOn(obj, __windowsClock, *hClock); |
| |
| IfFailThrowHr(wga->GetResolvedLanguage(cachedFormatter, &hLocale)); |
| SetHSTRINGPropertyBuiltInOn(obj, __locale, *hLocale); |
| |
| //Get the numbering system |
| IfFailThrowHr(wga->GetNumeralSystem(cachedFormatter, &hNumberingSystem)); |
| SetHSTRINGPropertyBuiltInOn(obj, __numberingSystem, *hNumberingSystem); |
| |
| //Extract the pattern strings |
| AutoCOMPtr<Windows::Foundation::Collections::IVectorView<HSTRING>> dateResult; |
| IfFailThrowHr(cachedFormatter->get_Patterns(&dateResult)); |
| |
| IfFailThrowHr(dateResult->get_Size(&length)); |
| |
| JavascriptArray *patternStrings = scriptContext->GetLibrary()->CreateArray(length); |
| |
| for (uint32 i = 0; i < length; i++) |
| { |
| AutoHSTRING item; |
| IfFailThrowHr(wga->GetItemAt(dateResult, i, &item)); |
| patternStrings->SetItem(i, Js::JavascriptString::NewCopySz(wgl->WindowsGetStringRawBuffer(*item, NULL), scriptContext), PropertyOperation_None); |
| } |
| SetPropertyBuiltInOn(obj, __patternStrings, patternStrings); |
| |
| //This parameter tells us whether we are caching it this time around; or just validating pattern strings |
| if ((boolean)(VarTo<JavascriptBoolean>(args.Values[2])->GetValue())) |
| { |
| //If timeZone is undefined; then use the standard dateTimeFormatter to format in local time; otherwise use the IDateTimeFormatter2 to format using specified timezone (UTC) |
| if (!GetPropertyBuiltInFrom(obj, __timeZone) || JavascriptOperators::IsUndefinedObject(propertyValue)) |
| { |
| cachedFormatter->AddRef(); |
| obj->SetInternalProperty(Js::InternalPropertyIds::HiddenObject, AutoCOMJSObject::New(scriptContext->GetRecycler(), cachedFormatter), Js::PropertyOperationFlags::PropertyOperation_None, NULL); |
| } |
| else |
| { |
| AutoCOMPtr<DateTimeFormatting::IDateTimeFormatter2> tzCachedFormatter; |
| IfFailThrowHr(cachedFormatter->QueryInterface(__uuidof(DateTimeFormatting::IDateTimeFormatter2), reinterpret_cast<void**>(&tzCachedFormatter))); |
| tzCachedFormatter->AddRef(); |
| |
| //Set the object as a cache |
| obj->SetInternalProperty(Js::InternalPropertyIds::HiddenObject, AutoCOMJSObject::New(scriptContext->GetRecycler(), tzCachedFormatter), Js::PropertyOperationFlags::PropertyOperation_None, NULL); |
| } |
| } |
| |
| return scriptContext->GetLibrary()->GetUndefined(); |
| #else |
| // TODO (doilij): implement INTL_ICU version |
| #ifdef INTL_ICU_DEBUG |
| Output::Print(_u("EntryIntl_CreateDateTimeFormat > returning null, fallback to JS\n")); |
| #endif |
| return scriptContext->GetLibrary()->GetNull(); |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_LocaleCompare(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| #ifdef INTL_WINGLOB |
| AssertOrFailFastMsg(false, "platform.localeCompare should not be called in Intl-WinGlob"); |
| return nullptr; |
| #else |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| INTL_CHECK_ARGS( |
| args.Info.Count == 5 && |
| VarIs<JavascriptString>(args[1]) && |
| VarIs<JavascriptString>(args[2]) && |
| DynamicObject::IsBaseDynamicObject(args[3]) && |
| VarIs<JavascriptBoolean>(args[4]) |
| ); |
| |
| JavascriptString *left = UnsafeVarTo<JavascriptString>(args[1]); |
| JavascriptString *right = UnsafeVarTo<JavascriptString>(args[2]); |
| DynamicObject *state = UnsafeVarTo<DynamicObject>(args[3]); |
| bool forStringPrototypeLocaleCompare = UnsafeVarTo<JavascriptBoolean>(args[4])->GetValue(); |
| if (forStringPrototypeLocaleCompare) |
| { |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_localeCompare); |
| INTL_TRACE("Calling '%s'.localeCompare('%s', ...)", left->GetSz(), right->GetSz()); |
| } |
| else |
| { |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Collator_Prototype_compare); |
| INTL_TRACE("Calling Collator.prototype.compare('%s', '%s')", left->GetSz(), right->GetSz()); |
| } |
| |
| // Below, we lazy-initialize the backing UCollator on the first call to localeCompare |
| // On subsequent calls, the UCollator will be cached in state.CachedUCollator |
| Var cachedUCollator = nullptr; |
| FinalizableUCollator *coll = nullptr; |
| UErrorCode status = U_ZERO_ERROR; |
| if (state->GetInternalProperty(state, InternalPropertyIds::CachedUCollator, &cachedUCollator, nullptr, scriptContext)) |
| { |
| coll = reinterpret_cast<FinalizableUCollator *>(cachedUCollator); |
| INTL_TRACE("Using previously cached UCollator (0x%x)", coll); |
| } |
| else |
| { |
| // the object key is locale according to Intl spec, but its more accurately a BCP47 Language Tag, not an ICU LocaleID |
| JavascriptString *langtag = AssertStringProperty(state, PropertyIds::locale); |
| CollatorSensitivity sensitivity = AssertEnumProperty<CollatorSensitivity>(state, PropertyIds::sensitivityEnum); |
| bool ignorePunctuation = AssertBooleanProperty(state, PropertyIds::ignorePunctuation); |
| bool numeric = AssertBooleanProperty(state, PropertyIds::numeric); |
| CollatorCaseFirst caseFirst = AssertEnumProperty<CollatorCaseFirst>(state, PropertyIds::caseFirstEnum); |
| JavascriptString *usage = AssertStringProperty(state, PropertyIds::usage); |
| |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| LangtagToLocaleID(langtag, localeID); |
| |
| const char16 searchString[] = _u("search"); |
| if (usage->BufferEquals(searchString, _countof(searchString) - 1)) // minus the null terminator |
| { |
| uloc_setKeywordValue("collation", "search", localeID, _countof(localeID), &status); |
| ICU_ASSERT(status, true); |
| } |
| |
| coll = FinalizableUCollator::New(scriptContext->GetRecycler(), ucol_open(localeID, &status)); |
| ICU_ASSERT(status, true); |
| |
| // REVIEW(jahorto): Anything that requires a status will no-op if its in a failure state |
| // Thus, we can ICU_ASSERT the status once after all of the properties are set for simplicity |
| if (sensitivity == CollatorSensitivity::Base) |
| { |
| ucol_setStrength(*coll, UCOL_PRIMARY); |
| } |
| else if (sensitivity == CollatorSensitivity::Accent) |
| { |
| ucol_setStrength(*coll, UCOL_SECONDARY); |
| } |
| else if (sensitivity == CollatorSensitivity::Case) |
| { |
| // see "description" for the caseLevel default option: http://userguide.icu-project.org/collation/customization |
| ucol_setStrength(*coll, UCOL_PRIMARY); |
| ucol_setAttribute(*coll, UCOL_CASE_LEVEL, UCOL_ON, &status); |
| } |
| else if (sensitivity == CollatorSensitivity::Variant) |
| { |
| ucol_setStrength(*coll, UCOL_TERTIARY); |
| } |
| |
| if (ignorePunctuation) |
| { |
| // see http://userguide.icu-project.org/collation/customization/ignorepunct |
| ucol_setAttribute(*coll, UCOL_ALTERNATE_HANDLING, UCOL_SHIFTED, &status); |
| } |
| |
| if (numeric) |
| { |
| ucol_setAttribute(*coll, UCOL_NUMERIC_COLLATION, UCOL_ON, &status); |
| } |
| |
| if (caseFirst == CollatorCaseFirst::Upper) |
| { |
| ucol_setAttribute(*coll, UCOL_CASE_FIRST, UCOL_UPPER_FIRST, &status); |
| } |
| else if (caseFirst == CollatorCaseFirst::Lower) |
| { |
| ucol_setAttribute(*coll, UCOL_CASE_FIRST, UCOL_LOWER_FIRST, &status); |
| } |
| |
| // Ensure that collator configuration was successfull |
| ICU_ASSERT(status, true); |
| |
| INTL_TRACE( |
| "Caching UCollator (0x%x) with langtag = %s, sensitivity = %d, caseFirst = %d, ignorePunctuation = %d, and numeric = %d", |
| coll, |
| langtag->GetSz(), |
| sensitivity, |
| caseFirst, |
| ignorePunctuation, |
| numeric |
| ); |
| |
| // cache coll for later use (so that the condition that brought us here returns true for future calls) |
| state->SetInternalProperty( |
| InternalPropertyIds::CachedUCollator, |
| coll, |
| PropertyOperationFlags::PropertyOperation_None, |
| nullptr |
| ); |
| } |
| |
| // As of ES2015, String.prototype.localeCompare must compare canonically equivalent strings as equal |
| BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("EntryIntl_LocaleCompare")); |
| |
| const char16 *leftNormalized = nullptr; |
| charcount_t leftNormalizedLength = 0; |
| if (UnicodeText::IsNormalizedString(UnicodeText::NormalizationForm::C, left->GetSz(), left->GetLength())) |
| { |
| leftNormalized = left->GetSz(); |
| leftNormalizedLength = left ->GetLength(); |
| } |
| else |
| { |
| leftNormalized = left->GetNormalizedString(UnicodeText::NormalizationForm::C, tempAllocator, leftNormalizedLength); |
| } |
| |
| const char16 *rightNormalized = nullptr; |
| charcount_t rightNormalizedLength = 0; |
| if (UnicodeText::IsNormalizedString(UnicodeText::NormalizationForm::C, right->GetSz(), right->GetLength())) |
| { |
| rightNormalized = right->GetSz(); |
| rightNormalizedLength = right->GetLength(); |
| } |
| else |
| { |
| rightNormalized = right->GetNormalizedString(UnicodeText::NormalizationForm::C, tempAllocator, rightNormalizedLength); |
| } |
| |
| static_assert(UCOL_LESS == -1 && UCOL_EQUAL == 0 && UCOL_GREATER == 1, "ucol_strcoll should return values compatible with localeCompare"); |
| Var ret = JavascriptNumber::ToVar(ucol_strcoll( |
| *coll, |
| reinterpret_cast<const UChar *>(leftNormalized), |
| leftNormalizedLength, |
| reinterpret_cast<const UChar *>(rightNormalized), |
| rightNormalizedLength |
| ), scriptContext); |
| |
| END_TEMP_ALLOCATOR(tempAllocator, scriptContext); |
| |
| return ret; |
| #endif |
| } |
| |
| #ifdef INTL_WINGLOB |
| static DWORD GetCompareStringComparisonFlags(CollatorSensitivity sensitivity, bool ignorePunctuation, bool numeric) |
| { |
| DWORD flags = 0; |
| |
| if (sensitivity == CollatorSensitivity::Base) |
| { |
| flags |= LINGUISTIC_IGNOREDIACRITIC | LINGUISTIC_IGNORECASE | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH; |
| } |
| else if (sensitivity == CollatorSensitivity::Accent) |
| { |
| flags |= LINGUISTIC_IGNORECASE | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH; |
| } |
| else if (sensitivity == CollatorSensitivity::Case) |
| { |
| flags |= NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH | LINGUISTIC_IGNOREDIACRITIC; |
| } |
| else if (sensitivity == CollatorSensitivity::Variant) |
| { |
| flags |= NORM_LINGUISTIC_CASING; |
| } |
| |
| if (ignorePunctuation) |
| { |
| flags |= NORM_IGNORESYMBOLS; |
| } |
| |
| if (numeric) |
| { |
| flags |= SORT_DIGITSASNUMBERS; |
| } |
| |
| return flags; |
| } |
| #endif |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_CompareString(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| #ifdef INTL_ICU |
| AssertOrFailFastMsg(false, "EntryIntl_CompareString should not be called in Intl-ICU"); |
| return nullptr; |
| #else |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| INTL_CHECK_ARGS(args.Info.Count >= 3 && VarIs<JavascriptString>(args[1]) && VarIs<JavascriptString>(args[2])); |
| |
| const char16 *locale = nullptr; // args[3] |
| char16 defaultLocale[LOCALE_NAME_MAX_LENGTH] = { 0 }; |
| |
| CollatorSensitivity sensitivity = CollatorSensitivity::Default; // args[4] |
| bool ignorePunctuation = false; // args[5] |
| bool numeric = false; // args[6] |
| |
| JavascriptString *str1 = VarTo<JavascriptString>(args.Values[1]); |
| JavascriptString *str2 = VarTo<JavascriptString>(args.Values[2]); |
| CollatorCaseFirst caseFirst = CollatorCaseFirst::Default; // args[7] |
| |
| // we only need to parse arguments 3 through 7 if locale and options are provided |
| // see fast path in JavascriptString::EntryLocaleCompare |
| if (args.Info.Count > 3) |
| { |
| if (args.Info.Count < 8) |
| { |
| JavascriptError::MapAndThrowError(scriptContext, E_INVALIDARG); |
| } |
| |
| if (!JavascriptOperators::IsUndefinedObject(args.Values[3]) && VarIs<JavascriptString>(args.Values[3])) |
| { |
| locale = VarTo<JavascriptString>(args.Values[3])->GetSz(); |
| } |
| else |
| { |
| JavascriptError::MapAndThrowError(scriptContext, E_INVALIDARG); |
| } |
| |
| if (!JavascriptOperators::IsUndefinedObject(args.Values[4]) && TaggedInt::Is(args.Values[4])) |
| { |
| sensitivity = static_cast<CollatorSensitivity>(TaggedInt::ToUInt16(args.Values[4])); |
| } |
| |
| if (!JavascriptOperators::IsUndefinedObject(args.Values[5]) && VarIs<JavascriptBoolean>(args.Values[5])) |
| { |
| ignorePunctuation = (VarTo<JavascriptBoolean>(args.Values[5])->GetValue() != 0); |
| } |
| |
| if (!JavascriptOperators::IsUndefinedObject(args.Values[6]) && VarIs<JavascriptBoolean>(args.Values[6])) |
| { |
| numeric = (VarTo<JavascriptBoolean>(args.Values[6])->GetValue() != 0); |
| } |
| |
| if (!JavascriptOperators::IsUndefinedObject(args.Values[7]) && TaggedInt::Is(args.Values[7])) |
| { |
| caseFirst = static_cast<CollatorCaseFirst>(TaggedInt::ToUInt16(args.Values[7])); |
| } |
| } |
| else |
| { |
| if (GetUserDefaultLocaleName(defaultLocale, _countof(defaultLocale)) != 0) |
| { |
| locale = defaultLocale; |
| } |
| else |
| { |
| JavascriptError::MapAndThrowError(scriptContext, HRESULT_FROM_WIN32(GetLastError())); |
| } |
| } |
| |
| Assert(locale != nullptr); |
| Assert((int)sensitivity >= 0 && sensitivity < CollatorSensitivity::Max); |
| Assert((int)caseFirst >= 0 && caseFirst < CollatorCaseFirst::Max); |
| |
| BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("EntryIntl_CompareString")); |
| |
| const char16 *left = nullptr; |
| charcount_t leftLen = 0; |
| if (UnicodeText::IsNormalizedString(UnicodeText::NormalizationForm::C, str1->GetSz(), str1->GetLength())) |
| { |
| left = str1->GetSz(); |
| leftLen = str1->GetLength(); |
| } |
| else |
| { |
| left = str1->GetNormalizedString(UnicodeText::NormalizationForm::C, tempAllocator, leftLen); |
| } |
| |
| const char16 *right = nullptr; |
| charcount_t rightLen = 0; |
| if (UnicodeText::IsNormalizedString(UnicodeText::NormalizationForm::C, str2->GetSz(), str2->GetLength())) |
| { |
| right = str2->GetSz(); |
| rightLen = str2->GetLength(); |
| } |
| else |
| { |
| right = str2->GetNormalizedString(UnicodeText::NormalizationForm::C, tempAllocator, rightLen); |
| } |
| |
| // CompareStringEx on Windows returns 0 for error, 1 if less, 2 if equal, 3 if greater |
| // Default to the strings being equal, because sorting with == causes no change in the order but converges, whereas < would cause an infinite loop. |
| int compareResult = 2; |
| HRESULT error = S_OK; |
| DWORD comparisonFlags = GetCompareStringComparisonFlags(sensitivity, ignorePunctuation, numeric); |
| compareResult = CompareStringEx(locale, comparisonFlags, left, leftLen, right, rightLen, NULL, NULL, 0); |
| error = HRESULT_FROM_WIN32(GetLastError()); |
| |
| END_TEMP_ALLOCATOR(tempAllocator, scriptContext); |
| |
| if (compareResult == 0) |
| { |
| JavascriptError::MapAndThrowError(scriptContext, error); |
| } |
| |
| return JavascriptNumber::ToVar(compareResult - 2, scriptContext); |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_CurrencyDigits(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| INTL_CHECK_ARGS( |
| args.Info.Count == 2 && |
| VarIs<JavascriptString>(args.Values[1]) |
| ); |
| |
| const char16 *currencyCode = UnsafeVarTo<JavascriptString>(args.Values[1])->GetSz(); |
| |
| #if defined(INTL_ICU) |
| UErrorCode status = U_ZERO_ERROR; |
| ScopedUNumberFormat fmt(unum_open(UNUM_CURRENCY, nullptr, 0, nullptr, nullptr, &status)); |
| unum_setTextAttribute(fmt, UNUM_CURRENCY_CODE, reinterpret_cast<const UChar *>(currencyCode), -1, &status); |
| ICU_ASSERT(status, true); |
| |
| int currencyDigits = unum_getAttribute(fmt, UNUM_FRACTION_DIGITS); |
| return JavascriptNumber::ToVar(currencyDigits, scriptContext); |
| #else |
| HRESULT hr; |
| AutoCOMPtr<NumberFormatting::ICurrencyFormatter> currencyFormatter(nullptr); |
| IfFailThrowHr(GetWindowsGlobalizationAdapter(scriptContext)->CreateCurrencyFormatterCode(scriptContext, currencyCode, ¤cyFormatter)); |
| AutoCOMPtr<NumberFormatting::INumberFormatterOptions> numberFormatterOptions; |
| IfFailThrowHr(currencyFormatter->QueryInterface(__uuidof(NumberFormatting::INumberFormatterOptions), reinterpret_cast<void**>(&numberFormatterOptions))); |
| Assert(numberFormatterOptions); |
| INT32 fractionDigits; |
| IfFailThrowHr(numberFormatterOptions->get_FractionDigits(&fractionDigits)); |
| return JavascriptNumber::ToVar(fractionDigits, scriptContext); |
| #endif |
| } |
| |
| #ifdef INTL_WINGLOB |
| //Helper, this just prepares based on fraction and integer format options |
| void IntlEngineInterfaceExtensionObject::prepareWithFractionIntegerDigits(ScriptContext* scriptContext, NumberFormatting::INumberRounderOption* rounderOptions, |
| NumberFormatting::INumberFormatterOptions* formatterOptions, uint16 minFractionDigits, uint16 maxFractionDigits, uint16 minIntegerDigits) |
| { |
| HRESULT hr; |
| WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext); |
| AutoCOMPtr<NumberFormatting::INumberRounder> numberRounder(nullptr); |
| AutoCOMPtr<NumberFormatting::IIncrementNumberRounder> incrementNumberRounder(nullptr); |
| |
| IfFailThrowHr(wga->CreateIncrementNumberRounder(scriptContext, &numberRounder)); |
| IfFailThrowHr(numberRounder->QueryInterface(__uuidof(NumberFormatting::IIncrementNumberRounder), reinterpret_cast<void**>(&incrementNumberRounder))); |
| Assert(incrementNumberRounder); |
| IfFailThrowHr(incrementNumberRounder->put_RoundingAlgorithm(Windows::Globalization::NumberFormatting::RoundingAlgorithm::RoundingAlgorithm_RoundHalfAwayFromZero)); |
| |
| IfFailThrowHr(incrementNumberRounder->put_Increment(pow(10.0, -maxFractionDigits))); |
| IfFailThrowHr(rounderOptions->put_NumberRounder(numberRounder)); |
| |
| IfFailThrowHr(formatterOptions->put_FractionDigits(minFractionDigits)); |
| IfFailThrowHr(formatterOptions->put_IntegerDigits(minIntegerDigits)); |
| } |
| |
| //Helper, this just prepares based on significant digits format options |
| void IntlEngineInterfaceExtensionObject::prepareWithSignificantDigits(ScriptContext* scriptContext, NumberFormatting::INumberRounderOption* rounderOptions, NumberFormatting::INumberFormatter *numberFormatter, |
| NumberFormatting::INumberFormatterOptions* formatterOptions, uint16 minSignificantDigits, uint16 maxSignificantDigits) |
| { |
| HRESULT hr; |
| WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext); |
| AutoCOMPtr<NumberFormatting::INumberRounder> numberRounder(nullptr); |
| AutoCOMPtr<NumberFormatting::ISignificantDigitsNumberRounder> incrementNumberRounder(nullptr); |
| AutoCOMPtr<NumberFormatting::ISignificantDigitsOption> significantDigitsOptions(nullptr); |
| |
| IfFailThrowHr(wga->CreateSignificantDigitsRounder(scriptContext, &numberRounder)); |
| IfFailThrowHr(numberRounder->QueryInterface(__uuidof(NumberFormatting::ISignificantDigitsNumberRounder), reinterpret_cast<void**>(&incrementNumberRounder))); |
| Assert(incrementNumberRounder); |
| IfFailThrowHr(incrementNumberRounder->put_RoundingAlgorithm(Windows::Globalization::NumberFormatting::RoundingAlgorithm::RoundingAlgorithm_RoundHalfAwayFromZero)); |
| |
| IfFailThrowHr(incrementNumberRounder->put_SignificantDigits(maxSignificantDigits)); |
| IfFailThrowHr(rounderOptions->put_NumberRounder(numberRounder)); |
| |
| IfFailThrowHr(numberFormatter->QueryInterface(__uuidof(NumberFormatting::ISignificantDigitsOption), reinterpret_cast<void**>(&significantDigitsOptions))); |
| IfFailThrowHr(significantDigitsOptions->put_SignificantDigits(minSignificantDigits)); |
| Assert(significantDigitsOptions); |
| |
| //Clear minimum fraction digits as in the case of percent 2 is supplied |
| IfFailThrowHr(formatterOptions->put_FractionDigits(0)); |
| } |
| #endif |
| |
| #ifdef INTL_ICU |
| // Rationale for this data structure: ICU reports back a tree of parts where each node in the tree |
| // has a type and a width, and there must be at least one node corresponding to each character in the tree |
| // (nodes can be wider than one character). Nodes can have children, and the parent-child relationship is |
| // that child node represents a more specific type for a given character range than the parent. Since ICU |
| // doesn't ever report "literal" parts of strings (like spaces or other extra characters), the root node in |
| // the tree will always be the entire width of the string with the type UnsetField, and we later map UnsetField |
| // to the "literal" part. Then, for a string like "US$ 1,000", there will be two child nodes, one of type |
| // currency with width [0, 3) and one of type integer with width [4, 9). The integer node will have a child |
| // of type group and width [6, 7). So the most specific type for characters 0 to 3 is currency, 3 to 4 is unset |
| // (literal), 4 to 5 is integer, 5 to 6 is group, and 6 to 9 is integer. This linear scan across the string, |
| // determining the part of consecutive spans of characters, is what the NumberFormat.prototype.formatToParts |
| // API needs to return to the user. |
| // |
| // I thought about this a bunch and I didn't like the idea of traversing an actual tree structure to get that |
| // information because it felt awkward to encode the "width" of nodes with specific meaning during traveral. |
| // So, I came up with an array structure where basically when we are told a part exists from position x to y, |
| // we can figure out what type used to apply to that span and update that section of the array with the new type. |
| // We skip over sections of the span [x, y) that have a type that doesn't match the start and end because that |
| // means we have already gotten a more specific part for that sub-span (for instance if we got a grouping |
| // separator before it's parent integer) |
| class NumberFormatPartsBuilder |
| { |
| private: |
| double num; |
| Field(const char16 *) formatted; |
| const charcount_t formattedLength; |
| Field(ScriptContext *) sc; |
| Field(UNumberFormatFields *) fields; |
| |
| static const uint8_t UnsetField = 0xFF; |
| |
| JavascriptString *GetPartTypeString(UNumberFormatFields field) |
| { |
| JavascriptLibrary *library = sc->GetLibrary(); |
| |
| // this is outside the switch because MSVC doesn't like that UnsetField is not a valid enum value |
| if ((static_cast<uint32_t>(field) & 0xFF) == UnsetField) |
| { |
| return library->GetIntlLiteralPartString(); |
| } |
| |
| switch (field) |
| { |
| case UNUM_INTEGER_FIELD: |
| { |
| if (JavascriptNumber::IsNan(num)) |
| { |
| return library->GetIntlNanPartString(); |
| } |
| else if (!NumberUtilities::IsFinite(num)) |
| { |
| return library->GetIntlInfinityPartString(); |
| } |
| else |
| { |
| return library->GetIntlIntegerPartString(); |
| } |
| } |
| case UNUM_FRACTION_FIELD: return library->GetIntlFractionPartString(); |
| case UNUM_DECIMAL_SEPARATOR_FIELD: return library->GetIntlDecimalPartString(); |
| |
| // The following three should only show up if UNUM_SCIENTIFIC is used, which Intl.NumberFormat doesn't currently use |
| case UNUM_EXPONENT_SYMBOL_FIELD: AssertOrFailFastMsg(false, "Unexpected exponent symbol field"); |
| case UNUM_EXPONENT_SIGN_FIELD: AssertOrFailFastMsg(false, "Unexpected exponent sign field"); |
| case UNUM_EXPONENT_FIELD: AssertOrFailFastMsg(false, "Unexpected exponent field"); |
| |
| case UNUM_GROUPING_SEPARATOR_FIELD: return library->GetIntlGroupPartString(); |
| case UNUM_CURRENCY_FIELD: return library->GetIntlCurrencyPartString(); |
| case UNUM_PERCENT_FIELD: return library->GetIntlPercentPartString(); |
| |
| // TODO(jahorto): Determine if this would ever be returned and what it would map to |
| case UNUM_PERMILL_FIELD: AssertOrFailFastMsg(false, "Unexpected permill field"); |
| |
| case UNUM_SIGN_FIELD: return num < 0 ? library->GetIntlMinusSignPartString() : library->GetIntlPlusSignPartString(); |
| |
| // At the ECMA-402 TC39 call for May 2017, it was decided that we should treat unmapped parts as type: "unknown" |
| default: return library->GetIntlUnknownPartString(); |
| } |
| } |
| |
| public: |
| NumberFormatPartsBuilder(double num, const char16 *formatted, charcount_t formattedLength, ScriptContext *scriptContext) |
| : num(num) |
| , formatted(formatted) |
| , formattedLength(formattedLength) |
| , sc(scriptContext) |
| , fields(RecyclerNewArrayLeaf(sc->GetRecycler(), UNumberFormatFields, formattedLength)) |
| { |
| // this will allow InsertPart to tell what fields have been initialized or not, and will |
| // be the value of resulting { type: "literal" } fields in ToPartsArray |
| memset(fields, UnsetField, sizeof(UNumberFormatFields) * formattedLength); |
| } |
| |
| // Note -- ICU reports fields as inclusive start, exclusive end, so thats how we handle the parameters here |
| void InsertPart(UNumberFormatFields field, int start, int end) |
| { |
| AssertOrFailFast(start >= 0); |
| AssertOrFailFast(start < end); |
| |
| // the asserts above mean the cast to charcount_t is safe |
| charcount_t ccStart = static_cast<charcount_t>(start); |
| charcount_t ccEnd = static_cast<charcount_t>(end); |
| |
| AssertOrFailFast(ccEnd <= formattedLength); |
| |
| // Make sure that the part does not overlap/is a strict subset or superset of other fields |
| // The part builder could probably be built and tested to handle this structure not being so rigid, |
| // but its safer for now to make sure this is true. |
| UNumberFormatFields parentStartField = fields[ccStart]; |
| UNumberFormatFields parentEndField = fields[ccEnd - 1]; |
| AssertOrFailFast(parentStartField == parentEndField); |
| |
| // Actually insert the part now |
| // Only overwrite fields in the span that match start and end, since fields that don't match are sub-parts that were already inserted |
| for (charcount_t i = ccStart; i < ccEnd; ++i) |
| { |
| if (fields[i] == parentStartField) |
| { |
| fields[i] = field; |
| } |
| } |
| } |
| |
| JavascriptArray *ToPartsArray() |
| { |
| JavascriptArray *ret = sc->GetLibrary()->CreateArray(0); |
| int retIndex = 0; |
| |
| // With the structure of the fields array, creating the resulting parts list to return to JS is simple |
| // Map set parts to the corresponding `type` value (see ECMA-402 #sec-partitionnumberpattern), and unset parts to the `literal` type |
| for (charcount_t start = 0; start < formattedLength; ++retIndex) |
| { |
| // At the top of the loop, fields[start] should always be different from fields[start - 1] (we should be at the start of a new part), |
| // since start is adjusted later in the loop to the end of the part |
| AssertOrFailFast(start == 0 || fields[start - 1] != fields[start]); |
| |
| charcount_t end = start + 1; |
| while (end < formattedLength && fields[end] == fields[start]) |
| { |
| ++end; |
| } |
| |
| JavascriptString *partType = GetPartTypeString(fields[start]); |
| JavascriptString *partValue = JavascriptString::NewCopyBuffer(formatted + start, end - start, sc); |
| |
| DynamicObject* part = sc->GetLibrary()->CreateObject(); |
| JavascriptOperators::InitProperty(part, PropertyIds::type, partType); |
| JavascriptOperators::InitProperty(part, PropertyIds::value, partValue); |
| |
| ret->SetItem(retIndex, part, PropertyOperationFlags::PropertyOperation_None); |
| |
| start = end; |
| } |
| |
| return ret; |
| } |
| }; |
| #endif |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_FormatNumber(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| #if defined(INTL_ICU) |
| INTL_CHECK_ARGS( |
| args.Info.Count == 5 && |
| (TaggedInt::Is(args[1]) || JavascriptNumber::Is(args[1])) && |
| DynamicObject::IsBaseDynamicObject(args[2]) && |
| VarIs<JavascriptBoolean>(args[3]) && |
| VarIs<JavascriptBoolean>(args[4]) |
| ); |
| |
| double num = JavascriptConversion::ToNumber(args[1], scriptContext); |
| DynamicObject *state = UnsafeVarTo<DynamicObject>(args[2]); |
| bool toParts = UnsafeVarTo<JavascriptBoolean>(args[3])->GetValue(); |
| bool forNumberPrototypeToLocaleString = UnsafeVarTo<JavascriptBoolean>(args[4])->GetValue(); |
| Var cachedUNumberFormat = nullptr; // cached by EntryIntl_CacheNumberFormat |
| AssertOrFailFast(state->GetInternalProperty(state, InternalPropertyIds::CachedUNumberFormat, &cachedUNumberFormat, NULL, scriptContext)); |
| |
| if (forNumberPrototypeToLocaleString) |
| { |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Number_Prototype_toLocaleString); |
| INTL_TRACE("Calling %f.toLocaleString(...)", num); |
| } |
| else if (toParts) |
| { |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(NumberFormat_Prototype_formatToParts); |
| INTL_TRACE("Calling NumberFormat.prototype.formatToParts(%f)", num); |
| } |
| else |
| { |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(NumberFormat_Prototype_format); |
| INTL_TRACE("Calling NumberFormat.prototype.format(%f)", num); |
| } |
| |
| auto fmt = static_cast<FinalizableUNumberFormat *>(cachedUNumberFormat); |
| char16 *formatted = nullptr; |
| int formattedLen = 0; |
| |
| if (!toParts) |
| { |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return unum_formatDouble(*fmt, num, buf, bufLen, nullptr, status); |
| }, scriptContext->GetRecycler(), &formatted, &formattedLen); |
| |
| return JavascriptString::NewWithBuffer(formatted, formattedLen, scriptContext); |
| } |
| |
| // windows-icu does not define `ICU_VERSION` but does support `unum_formatDoubleForFields` (ICU 61+) since Windows 10 1809 "Redstone 5" (`NTDDI_WIN10_RS5`). |
| #if !defined(ICU_VERSION) || ICU_VERSION >= 61 |
| UErrorCode status = U_ZERO_ERROR; |
| ScopedUFieldPositionIterator fpi(ufieldpositer_open(&status)); |
| |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return unum_formatDoubleForFields(*fmt, num, buf, bufLen, fpi, status); |
| }, scriptContext->GetRecycler(), &formatted, &formattedLen); |
| |
| NumberFormatPartsBuilder nfpb(num, formatted, formattedLen, scriptContext); |
| |
| int partStart = 0; |
| int partEnd = 0; |
| int i = 0; |
| for (int kind = ufieldpositer_next(fpi, &partStart, &partEnd); kind >= 0; kind = ufieldpositer_next(fpi, &partStart, &partEnd), ++i) |
| { |
| nfpb.InsertPart(static_cast<UNumberFormatFields>(kind), partStart, partEnd); |
| } |
| |
| return nfpb.ToPartsArray(); |
| #else |
| JavascriptLibrary *library = scriptContext->GetLibrary(); |
| JavascriptArray *ret = library->CreateArray(1); |
| DynamicObject* part = library->CreateObject(); |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return unum_formatDouble(*fmt, num, buf, bufLen, nullptr, status); |
| }, scriptContext->GetRecycler(), &formatted, &formattedLen); |
| JavascriptOperators::InitProperty(part, PropertyIds::type, library->GetIntlUnknownPartString()); |
| JavascriptOperators::InitProperty(part, PropertyIds::value, JavascriptString::NewWithBuffer(formatted, formattedLen, scriptContext)); |
| |
| ret->SetItem(0, part, PropertyOperationFlags::PropertyOperation_None); |
| return ret; |
| #endif // #if ICU_VERSION >= 61 ... #else |
| #else |
| INTL_CHECK_ARGS( |
| args.Info.Count == 3 && |
| (TaggedInt::Is(args.Values[1]) || JavascriptNumber::Is(args.Values[1])) && |
| DynamicObject::IsBaseDynamicObject(args.Values[2]) |
| ); |
| |
| DynamicObject *options = VarTo<DynamicObject>(args.Values[2]); |
| Var hiddenObject = nullptr; |
| AssertOrFailFastMsg(options->GetInternalProperty(options, Js::InternalPropertyIds::HiddenObject, &hiddenObject, NULL, scriptContext), |
| "EntryIntl_FormatNumber: Could not retrieve hiddenObject."); |
| DelayLoadWindowsGlobalization* wsl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| |
| NumberFormatting::INumberFormatter *numberFormatter; |
| numberFormatter = static_cast<NumberFormatting::INumberFormatter *>(((AutoCOMJSObject *)hiddenObject)->GetInstance()); |
| |
| AutoHSTRING result; |
| HRESULT hr; |
| if (TaggedInt::Is(args.Values[1])) |
| { |
| IfFailThrowHr(numberFormatter->FormatInt(TaggedInt::ToInt32(args.Values[1]), &result)); |
| } |
| else |
| { |
| IfFailThrowHr(numberFormatter->FormatDouble(JavascriptNumber::GetValue(args.Values[1]), &result)); |
| } |
| |
| PCWSTR strBuf = wsl->WindowsGetStringRawBuffer(*result, NULL); |
| |
| if (strBuf == nullptr) |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| else |
| { |
| JavascriptStringObject *retVal = scriptContext->GetLibrary()->CreateStringObject(Js::JavascriptString::NewCopySz(strBuf, scriptContext)); |
| return retVal; |
| } |
| #endif |
| } |
| |
| #ifdef INTL_ICU |
| static void AddPartToPartsArray(ScriptContext *scriptContext, JavascriptArray *arr, int arrIndex, const char16 *src, int start, int end, JavascriptString *partType) |
| { |
| JavascriptString *partValue = JavascriptString::NewCopyBuffer( |
| src + start, |
| end - start, |
| scriptContext |
| ); |
| |
| DynamicObject* part = scriptContext->GetLibrary()->CreateObject(); |
| JavascriptOperators::InitProperty(part, PropertyIds::type, partType); |
| JavascriptOperators::InitProperty(part, PropertyIds::value, partValue); |
| |
| arr->SetItem(arrIndex, part, PropertyOperationFlags::PropertyOperation_None); |
| } |
| #endif |
| |
| // For Windows Globalization, this function takes a number (date) and a state object and returns a string |
| // For ICU, this function takes a state object, number, and boolean for whether or not to format to parts. |
| // if args.Values[3] ~= true, an array of objects is returned; else, a string is returned |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_FormatDateTime(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| #ifdef INTL_WINGLOB |
| if (args.Info.Count < 3 || !(TaggedInt::Is(args.Values[1]) || JavascriptNumber::Is(args.Values[1])) || !DynamicObject::IsBaseDynamicObject(args.Values[2])) |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| Windows::Foundation::DateTime winDate; |
| HRESULT hr; |
| if (TaggedInt::Is(args.Values[1])) |
| { |
| hr = Js::DateUtilities::ES5DateToWinRTDate(TaggedInt::ToInt32(args.Values[1]), &(winDate.UniversalTime)); |
| } |
| else |
| { |
| hr = Js::DateUtilities::ES5DateToWinRTDate(JavascriptNumber::GetValue(args.Values[1]), &(winDate.UniversalTime)); |
| } |
| if (FAILED(hr)) |
| { |
| HandleOOMSOEHR(hr); |
| // If conversion failed, double value is outside the range of WinRT DateTime |
| Js::JavascriptError::ThrowRangeError(scriptContext, JSERR_OutOfDateTimeRange); |
| } |
| |
| DynamicObject* obj = VarTo<DynamicObject>(args.Values[2]); |
| Var hiddenObject = nullptr; |
| AssertOrFailFastMsg(obj->GetInternalProperty(obj, Js::InternalPropertyIds::HiddenObject, &hiddenObject, NULL, scriptContext), |
| "EntryIntl_FormatDateTime: Could not retrieve hiddenObject."); |
| |
| //We are going to perform the same check for timeZone as when caching the formatter. |
| Var propertyValue = nullptr; |
| AutoHSTRING result; |
| |
| //If timeZone is undefined; then use the standard dateTimeFormatter to format in local time; otherwise use the IDateTimeFormatter2 to format using specified timezone (UTC) |
| if (!GetPropertyBuiltInFrom(obj, __timeZone) || JavascriptOperators::IsUndefinedObject(propertyValue)) |
| { |
| DateTimeFormatting::IDateTimeFormatter *formatter = static_cast<DateTimeFormatting::IDateTimeFormatter *>(((AutoCOMJSObject *)hiddenObject)->GetInstance()); |
| Assert(formatter); |
| IfFailThrowHr(formatter->Format(winDate, &result)); |
| } |
| else |
| { |
| DateTimeFormatting::IDateTimeFormatter2 *formatter = static_cast<DateTimeFormatting::IDateTimeFormatter2 *>(((AutoCOMJSObject *)hiddenObject)->GetInstance()); |
| Assert(formatter); |
| HSTRING timeZone; |
| HSTRING_HEADER timeZoneHeader; |
| |
| // IsValidTimeZone() has already verified that this is JavascriptString. |
| JavascriptString* userDefinedTimeZoneId = VarTo<JavascriptString>(propertyValue); |
| IfFailThrowHr(WindowsCreateStringReference(userDefinedTimeZoneId->GetSz(), userDefinedTimeZoneId->GetLength(), &timeZoneHeader, &timeZone)); |
| Assert(timeZone); |
| |
| IfFailThrowHr(formatter->FormatUsingTimeZone(winDate, timeZone, &result)); |
| } |
| PCWSTR strBuf = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary()->WindowsGetStringRawBuffer(*result, NULL); |
| |
| return Js::JavascriptString::NewCopySz(strBuf, scriptContext); |
| #else |
| // This function vaguely implements ECMA 402 #sec-partitiondatetimepattern |
| INTL_CHECK_ARGS( |
| args.Info.Count == 5 && |
| DynamicObject::IsBaseDynamicObject(args[1]) && |
| (TaggedInt::Is(args[2]) || JavascriptNumber::Is(args[2])) && |
| VarIs<JavascriptBoolean>(args[3]) && |
| VarIs<JavascriptBoolean>(args[4]) |
| ); |
| |
| DynamicObject *state = UnsafeVarTo<DynamicObject>(args[1]); |
| bool toParts = Js::UnsafeVarTo<Js::JavascriptBoolean>(args[3])->GetValue(); |
| |
| // 1. Let x be TimeClip(x) |
| // 2. If x is NaN, throw a RangeError exception |
| double date = JavascriptDate::TimeClip(args[2]); |
| if (JavascriptNumber::IsNan(date)) |
| { |
| JavascriptError::ThrowRangeError(scriptContext, JSERR_InvalidDate); |
| } |
| |
| bool forDatePrototypeToLocaleString = UnsafeVarTo<JavascriptBoolean>(args[4])->GetValue(); |
| if (forDatePrototypeToLocaleString) |
| { |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Date_Prototype_toLocaleString); |
| INTL_TRACE("Calling new Date(%f).toLocaleString(...)", date); |
| } |
| else if (toParts) |
| { |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(DateTimeFormat_Prototype_formatToParts); |
| INTL_TRACE("Calling DateTimeFormat.prototype.formatToParts(new Date(%f))", date); |
| } |
| else |
| { |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(DateTimeFormat_Prototype_format); |
| INTL_TRACE("Calling DateTimeFormat.prototype.format(new Date(%f))", date); |
| } |
| |
| // Below, we lazy-initialize the backing UDateFormat on the first call to format{ToParts} |
| // On subsequent calls, the UDateFormat will be cached in state.CachedUDateFormat |
| Var cachedUDateFormat = nullptr; |
| FinalizableUDateFormat *dtf = nullptr; |
| UErrorCode status = U_ZERO_ERROR; |
| if (state->GetInternalProperty(state, InternalPropertyIds::CachedUDateFormat, &cachedUDateFormat, nullptr, scriptContext)) |
| { |
| dtf = reinterpret_cast<FinalizableUDateFormat *>(cachedUDateFormat); |
| INTL_TRACE("Using previously cached UDateFormat (0x%x)", dtf); |
| } |
| else |
| { |
| JavascriptString *langtag = AssertStringProperty(state, PropertyIds::locale); |
| JavascriptString *timeZone = AssertStringProperty(state, PropertyIds::timeZone); |
| JavascriptString *pattern = AssertStringProperty(state, PropertyIds::pattern); |
| |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| LangtagToLocaleID(langtag, localeID); |
| |
| dtf = FinalizableUDateFormat::New(scriptContext->GetRecycler(), udat_open( |
| UDAT_PATTERN, |
| UDAT_PATTERN, |
| localeID, |
| reinterpret_cast<const UChar *>(timeZone->GetSz()), |
| timeZone->GetLength(), |
| reinterpret_cast<const UChar *>(pattern->GetSz()), |
| pattern->GetLength(), |
| &status |
| )); |
| ICU_ASSERT(status, true); |
| |
| // DateTimeFormat is expected to use the "proleptic Gregorian calendar", which means that the Julian calendar should never be used. |
| // To accomplish this, we can set the switchover date between julian/gregorian |
| // to the ECMAScript beginning of time, which is -8.64e15 according to ecma262 #sec-time-values-and-time-range |
| UCalendar *cal = const_cast<UCalendar *>(udat_getCalendar(*dtf)); |
| const char *calType = ucal_getType(cal, &status); |
| ICU_ASSERT(status, calType != nullptr); |
| if (strcmp(calType, "gregorian") == 0) |
| { |
| double beginningOfTime = -8.64e15; |
| ucal_setGregorianChange(cal, beginningOfTime, &status); |
| double actualGregorianChange = ucal_getGregorianChange(cal, &status); |
| ICU_ASSERT(status, beginningOfTime == actualGregorianChange); |
| } |
| |
| INTL_TRACE("Caching new UDateFormat (0x%x) with langtag=%s, pattern=%s, timezone=%s", dtf, langtag->GetSz(), pattern->GetSz(), timeZone->GetSz()); |
| |
| // cache dtf for later use (so that the condition that brought us here returns true for future calls) |
| state->SetInternalProperty( |
| InternalPropertyIds::CachedUDateFormat, |
| dtf, |
| PropertyOperationFlags::PropertyOperation_None, |
| nullptr |
| ); |
| } |
| |
| // We intentionally special-case the following two calls to EnsureBuffer to allow zero-length strings. |
| // See comment in GetPatternForSkeleton. |
| |
| char16 *formatted = nullptr; |
| int formattedLen = 0; |
| if (!toParts) |
| { |
| // if we aren't formatting to parts, we simply want to call udat_format with retry |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return udat_format(*dtf, date, buf, bufLen, nullptr, status); |
| }, scriptContext->GetRecycler(), &formatted, &formattedLen, /* allowZeroLengthStrings */ true); |
| return JavascriptString::NewWithBuffer(formatted, formattedLen, scriptContext); |
| } |
| |
| // The rest of this function most closely corresponds to ECMA 402 #sec-partitiondatetimepattern |
| ScopedUFieldPositionIterator fpi(ufieldpositer_open(&status)); |
| ICU_ASSERT(status, true); |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return udat_formatForFields(*dtf, date, buf, bufLen, fpi, status); |
| }, scriptContext->GetRecycler(), &formatted, &formattedLen, /* allowZeroLengthStrings */ true); |
| |
| JavascriptLibrary *library = scriptContext->GetLibrary(); |
| JavascriptArray* ret = library->CreateArray(0); |
| |
| int partStart = 0; |
| int partEnd = 0; |
| int lastPartEnd = 0; |
| int i = 0; |
| for ( |
| int kind = ufieldpositer_next(fpi, &partStart, &partEnd); |
| kind >= 0; |
| kind = ufieldpositer_next(fpi, &partStart, &partEnd), ++i |
| ) |
| { |
| Assert(partStart < partEnd && partEnd <= formattedLen); |
| JavascriptString *typeString = nullptr; |
| UDateFormatField fieldKind = (UDateFormatField)kind; |
| switch (fieldKind) |
| { |
| case UDAT_ERA_FIELD: |
| typeString = library->GetIntlEraPartString(); break; |
| case UDAT_YEAR_FIELD: |
| case UDAT_EXTENDED_YEAR_FIELD: |
| case UDAT_YEAR_NAME_FIELD: |
| typeString = library->GetIntlYearPartString(); break; |
| case UDAT_MONTH_FIELD: |
| case UDAT_STANDALONE_MONTH_FIELD: |
| typeString = library->GetIntlMonthPartString(); break; |
| case UDAT_DATE_FIELD: |
| typeString = library->GetIntlDayPartString(); break; |
| case UDAT_HOUR_OF_DAY1_FIELD: |
| case UDAT_HOUR_OF_DAY0_FIELD: |
| case UDAT_HOUR1_FIELD: |
| case UDAT_HOUR0_FIELD: |
| typeString = library->GetIntlHourPartString(); break; |
| case UDAT_MINUTE_FIELD: |
| typeString = library->GetIntlMinutePartString(); break; |
| case UDAT_SECOND_FIELD: |
| typeString = library->GetIntlSecondPartString(); break; |
| case UDAT_DAY_OF_WEEK_FIELD: |
| case UDAT_STANDALONE_DAY_FIELD: |
| case UDAT_DOW_LOCAL_FIELD: |
| typeString = library->GetIntlWeekdayPartString(); break; |
| case UDAT_AM_PM_FIELD: |
| typeString = library->GetIntlDayPeriodPartString(); break; |
| case UDAT_TIMEZONE_FIELD: |
| case UDAT_TIMEZONE_RFC_FIELD: |
| case UDAT_TIMEZONE_GENERIC_FIELD: |
| case UDAT_TIMEZONE_SPECIAL_FIELD: |
| case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: |
| case UDAT_TIMEZONE_ISO_FIELD: |
| case UDAT_TIMEZONE_ISO_LOCAL_FIELD: |
| typeString = library->GetIntlTimeZoneNamePartString(); break; |
| #if defined(ICU_VERSION) && ICU_VERSION == 55 |
| case UDAT_TIME_SEPARATOR_FIELD: |
| // ICU 55 (Ubuntu 16.04 system default) has the ":" in "5:23 PM" as a special field |
| // Intl should just treat this as a literal |
| typeString = library->GetIntlLiteralPartString(); break; |
| #endif |
| default: |
| typeString = library->GetIntlUnknownPartString(); break; |
| } |
| |
| if (partStart > lastPartEnd) |
| { |
| // formatForFields does not report literal fields directly, so we have to detect them |
| // by seeing if the current part starts after the previous one ended |
| AddPartToPartsArray(scriptContext, ret, i, formatted, lastPartEnd, partStart, library->GetIntlLiteralPartString()); |
| i += 1; |
| } |
| |
| AddPartToPartsArray(scriptContext, ret, i, formatted, partStart, partEnd, typeString); |
| lastPartEnd = partEnd; |
| } |
| |
| // Sometimes, there can be a literal at the end of the string, such as when formatting just the year in |
| // the chinese calendar, where the pattern string will be `r(U)`. The trailing `)` will be a literal |
| if (lastPartEnd != formattedLen) |
| { |
| AssertOrFailFast(lastPartEnd < formattedLen); |
| |
| // `i` was incremented by the consequence of the last iteration of the for loop |
| AddPartToPartsArray(scriptContext, ret, i, formatted, lastPartEnd, formattedLen, library->GetIntlLiteralPartString()); |
| } |
| |
| return ret; |
| #endif |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_GetPatternForSkeleton(RecyclableObject *function, CallInfo callInfo, ...) |
| { |
| #ifdef INTL_ICU |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| INTL_CHECK_ARGS(args.Info.Count == 3 && VarIs<JavascriptString>(args.Values[1]) && VarIs<JavascriptString>(args.Values[2])); |
| |
| JavascriptString *langtag = UnsafeVarTo<JavascriptString>(args.Values[1]); |
| JavascriptString *skeleton = UnsafeVarTo<JavascriptString>(args.Values[2]); |
| UErrorCode status = U_ZERO_ERROR; |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| LangtagToLocaleID(langtag, localeID); |
| |
| // See https://github.com/tc39/ecma402/issues/225 |
| // When picking a format, we should be using the locale data of the basename of the resolved locale, |
| // compared to when we actually format the date using the format string, where we use the full locale including extensions |
| // |
| // ECMA 402 #sec-initializedatetimeformat |
| // 10: Let localeData be %DateTimeFormat%.[[LocaleData]]. |
| // 11: Let r be ResolveLocale( %DateTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]], localeData). |
| // 16: Let dataLocale be r.[[dataLocale]]. |
| // 23: Let dataLocaleData be localeData.[[<dataLocale>]]. |
| // 24: Let formats be dataLocaleData.[[formats]]. |
| char baseLocaleID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| int baseLocaleIDLength = uloc_getBaseName(localeID, baseLocaleID, _countof(baseLocaleID), &status); |
| ICU_ASSERT(status, baseLocaleIDLength > 0 && baseLocaleIDLength < ULOC_FULLNAME_CAPACITY); |
| |
| INTL_TRACE("Converted input langtag '%s' to base locale ID '%S' for pattern generation", langtag->GetSz(), baseLocaleID); |
| |
| ScopedUDateTimePatternGenerator dtpg(udatpg_open(baseLocaleID, &status)); |
| ICU_ASSERT(status, true); |
| |
| char16 *formatted = nullptr; |
| int formattedLen = 0; |
| |
| // OS#17513493 (OSS-Fuzz 7950): It is possible for the skeleton to be a zero-length string |
| // because [[Get]] operations are performed on the options object twice, according to spec. |
| // Follow-up spec discussion here: https://github.com/tc39/ecma402/issues/237. |
| // We need to special-case this because calling udatpg_getBestPatternWithOptions on an empty skeleton |
| // will produce an empty pattern, which causes an assert in EnsureBuffer by default. |
| // As a result, we pass a final optional parameter to EnsureBuffer to say that zero-length results are OK. |
| // TODO(jahorto): re-visit this workaround and the one in FormatDateTime upon resolution of the spec issue. |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return udatpg_getBestPatternWithOptions( |
| dtpg, |
| reinterpret_cast<const UChar *>(skeleton->GetSz()), |
| skeleton->GetLength(), |
| UDATPG_MATCH_ALL_FIELDS_LENGTH, |
| buf, |
| bufLen, |
| status |
| ); |
| }, scriptContext->GetRecycler(), &formatted, &formattedLen, /* allowZeroLengthStrings */ true); |
| |
| INTL_TRACE("Best pattern '%s' will be used for skeleton '%s' and langtag '%s'", formatted, skeleton->GetSz(), langtag->GetSz()); |
| |
| return JavascriptString::NewWithBuffer(formatted, formattedLen, scriptContext); |
| #else |
| AssertOrFailFastMsg(false, "GetPatternForSkeleton is not implemented outside of ICU"); |
| return nullptr; |
| #endif |
| } |
| |
| // given a timezone name as an argument, this will return a canonicalized version of that name, or undefined if the timezone is invalid |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_ValidateAndCanonicalizeTimeZone(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| INTL_CHECK_ARGS(args.Info.Count == 2 && VarIs<JavascriptString>(args.Values[1])); |
| |
| JavascriptString *tz = VarTo<JavascriptString>(args.Values[1]); |
| |
| #ifdef INTL_WINGLOB |
| AutoHSTRING canonicalizedTimeZone; |
| boolean isValidTimeZone = GetWindowsGlobalizationAdapter(scriptContext)->ValidateAndCanonicalizeTimeZone(scriptContext, tz->GetSz(), &canonicalizedTimeZone); |
| if (isValidTimeZone) |
| { |
| DelayLoadWindowsGlobalization* wsl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| PCWSTR strBuf = wsl->WindowsGetStringRawBuffer(*canonicalizedTimeZone, NULL); |
| return Js::JavascriptString::NewCopySz(strBuf, scriptContext); |
| } |
| else |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| #else |
| UErrorCode status = U_ZERO_ERROR; |
| |
| // TODO(jahorto): Is this the list of timeZones that we want? How is this different from |
| // the other two enum values or ucal_openTimeZones? |
| ScopedUEnumeration available(ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, nullptr, nullptr, &status)); |
| int availableLength = uenum_count(available, &status); |
| ICU_ASSERT(status, availableLength > 0); |
| |
| charcount_t matchLen = 0; |
| UChar match[100] = { 0 }; |
| for (int a = 0; a < availableLength; ++a) |
| { |
| int curLen = -1; |
| const UChar *cur = uenum_unext(available, &curLen, &status); |
| ICU_ASSERT(status, true); |
| if (curLen == 0) |
| { |
| // OS#17175014: in rare cases, ICU will return U_ZERO_ERROR and a valid `cur` string but a length of 0 |
| // This only happens in an OOM during uenum_(u)next |
| // Tracked by ICU: http://bugs.icu-project.org/trac/ticket/13739 |
| Throw::OutOfMemory(); |
| } |
| |
| AssertOrFailFast(curLen > 0); |
| if (_wcsicmp(reinterpret_cast<const char16 *>(cur), tz->GetSz()) == 0) |
| { |
| ucal_getCanonicalTimeZoneID(cur, curLen, match, _countof(match), nullptr, &status); |
| ICU_ASSERT(status, true); |
| size_t len = wcslen(reinterpret_cast<const char16 *>(match)); |
| AssertMsg(len < MaxCharCount, "Returned canonicalized timezone is far too long"); |
| matchLen = static_cast<charcount_t>(len); |
| break; |
| } |
| } |
| |
| if (matchLen == 0) |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| return JavascriptString::NewCopyBuffer(reinterpret_cast<const char16 *>(match), matchLen, scriptContext); |
| #endif |
| } |
| |
| // returns the current system time zone |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_GetDefaultTimeZone(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| #ifdef INTL_WINGLOB |
| WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext); |
| DelayLoadWindowsGlobalization* wsl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary(); |
| AutoHSTRING str; |
| |
| HRESULT hr; |
| if (FAILED(hr = wga->GetDefaultTimeZoneId(scriptContext, &str))) |
| { |
| HandleOOMSOEHR(hr); |
| //If we can't get default timeZone, return undefined. |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| PCWSTR strBuf = wsl->WindowsGetStringRawBuffer(*str, NULL); |
| return Js::JavascriptString::NewCopySz(strBuf, scriptContext); |
| #else |
| int timeZoneLen = 0; |
| char16 *timeZone = nullptr; |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return ucal_getDefaultTimeZone(buf, bufLen, status); |
| }, scriptContext->GetRecycler(), &timeZone, &timeZoneLen); |
| return JavascriptString::NewWithBuffer(timeZone, timeZoneLen, scriptContext); |
| #endif |
| } |
| |
| #ifdef INTL_ICU |
| static FinalizableUPluralRules *GetOrCreateCachedUPluralRules(DynamicObject *stateObject, ScriptContext *scriptContext) |
| { |
| Var cachedUPluralRules = nullptr; |
| FinalizableUPluralRules *pr = nullptr; |
| if (stateObject->GetInternalProperty(stateObject, InternalPropertyIds::CachedUPluralRules, &cachedUPluralRules, nullptr, scriptContext)) |
| { |
| pr = reinterpret_cast<FinalizableUPluralRules *>(cachedUPluralRules); |
| INTL_TRACE("Using previously cached UPluralRules (0x%x)", pr); |
| } |
| else |
| { |
| UErrorCode status = U_ZERO_ERROR; |
| |
| JavascriptString *langtag = AssertStringProperty(stateObject, PropertyIds::locale); |
| JavascriptString *type = AssertStringProperty(stateObject, PropertyIds::type); |
| |
| UPluralType prType = UPLURAL_TYPE_CARDINAL; |
| if (wcscmp(type->GetSz(), _u("ordinal")) == 0) |
| { |
| prType = UPLURAL_TYPE_ORDINAL; |
| } |
| else |
| { |
| AssertOrFailFast(wcscmp(type->GetSz(), _u("cardinal")) == 0); |
| } |
| |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| LangtagToLocaleID(langtag, localeID); |
| |
| pr = FinalizableUPluralRules::New(scriptContext->GetRecycler(), uplrules_openForType(localeID, prType, &status)); |
| ICU_ASSERT(status, true); |
| |
| INTL_TRACE("Caching UPluralRules object (0x%x) with langtag %s and type %s", pr, langtag->GetSz(), type->GetSz()); |
| |
| stateObject->SetInternalProperty(InternalPropertyIds::CachedUPluralRules, pr, PropertyOperationFlags::PropertyOperation_None, nullptr); |
| } |
| |
| return pr; |
| } |
| #endif |
| |
| // This method implements Step 13 and 14 of ECMA 402 #sec-initializepluralrules |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_PluralRulesKeywords(RecyclableObject *function, CallInfo callInfo, ...) |
| { |
| #ifdef INTL_ICU |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| INTL_CHECK_ARGS(args.Info.Count == 2 && DynamicObject::IsBaseDynamicObject(args[1])); |
| |
| JavascriptArray *ret = scriptContext->GetLibrary()->CreateArray(0); |
| |
| // uplrules_getKeywords is only stable since ICU 61. |
| // For ICU < 61, we can fake it by creating an array of ["other"], which |
| // uplrules_getKeywords is guaranteed to return at minimum. |
| // This array is only used in resolved options, so the majority of the functionality can remain (namely, select() still works) |
| // |
| // windows-icu does not define `ICU_VERSION` but does support `uplrules_getKeywords` (ICU 61+) since Windows 10 1809 "Redstone 5" (`NTDDI_WIN10_RS5`). |
| #if !defined(ICU_VERSION) || ICU_VERSION >= 61 |
| DynamicObject *state = UnsafeVarTo<DynamicObject>(args[1]); |
| FinalizableUPluralRules *pr = GetOrCreateCachedUPluralRules(state, scriptContext); |
| |
| UErrorCode status = U_ZERO_ERROR; |
| ScopedUEnumeration keywords(uplrules_getKeywords(*pr, &status)); |
| ICU_ASSERT(status, true); |
| |
| ForEachUEnumeration16(keywords, [&](int index, const char16 *kw, charcount_t kwLength) |
| { |
| ret->SetItem(index, JavascriptString::NewCopyBuffer(kw, kwLength, scriptContext), PropertyOperation_None); |
| }); |
| #else |
| ret->SetItem(0, scriptContext->GetLibrary()->GetIntlPluralRulesOtherString(), PropertyOperation_None); |
| #endif |
| |
| return ret; |
| #else |
| AssertOrFailFastMsg(false, "Intl-WinGlob should not be using PluralRulesKeywords"); |
| return nullptr; |
| #endif |
| } |
| |
| // This method roughly implements ECMA 402 #sec-pluralruleselect, except ICU takes care of handling the operand logic for us |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_PluralRulesSelect(RecyclableObject *function, CallInfo callInfo, ...) |
| { |
| #ifdef INTL_ICU |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| INTL_CHECK_ARGS(args.Info.Count == 3 && DynamicObject::IsBaseDynamicObject(args[1])); |
| |
| DynamicObject *state = UnsafeVarTo<DynamicObject>(args[1]); |
| double n = 0.0; |
| if (TaggedInt::Is(args[2])) |
| { |
| n = TaggedInt::ToDouble(args[2]); |
| } |
| else |
| { |
| AssertOrFailFast(JavascriptNumber::Is(args[2])); |
| n = JavascriptNumber::GetValue(args[2]); |
| } |
| |
| CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(PluralRules_Prototype_select); |
| INTL_TRACE("Calling PluralRules.prototype.select(%f)", n); |
| |
| UErrorCode status = U_ZERO_ERROR; |
| |
| FinalizableUPluralRules *pr = GetOrCreateCachedUPluralRules(state, scriptContext); |
| |
| // ICU has an internal API, uplrules_selectWithFormat, that is equivalent to uplrules_select but will respect digit options of the passed UNumberFormat. |
| // Since its an internal API, we can't use it -- however, we can work around it by creating a UNumberFormat with provided digit options, |
| // formatting the requested number to a string, and then converting the string back to a double which we can pass to uplrules_select. |
| // This workaround was suggested during the May 2018 ECMA-402 discussion. |
| // The below is similar to GetOrCreateCachedUPluralRules, but since creating a UNumberFormat for Intl.NumberFormat is much more involved and no one else |
| // uses this functionality, it makes more sense to me to just put the logic inline. |
| Var cachedUNumberFormat = nullptr; |
| FinalizableUNumberFormat *nf = nullptr; |
| if (state->GetInternalProperty(state, InternalPropertyIds::CachedUNumberFormat, &cachedUNumberFormat, nullptr, scriptContext)) |
| { |
| nf = reinterpret_cast<FinalizableUNumberFormat *>(cachedUNumberFormat); |
| INTL_TRACE("Using previously cached UNumberFormat (0x%x)", nf); |
| } |
| else |
| { |
| char localeID[ULOC_FULLNAME_CAPACITY] = { 0 }; |
| LangtagToLocaleID(AssertStringProperty(state, PropertyIds::locale), localeID); |
| nf = FinalizableUNumberFormat::New(scriptContext->GetRecycler(), unum_open(UNUM_DECIMAL, nullptr, 0, localeID, nullptr, &status)); |
| |
| SetUNumberFormatDigitOptions(*nf, state); |
| |
| INTL_TRACE("Caching UNumberFormat object (0x%x) with localeID %S", nf, localeID); |
| |
| state->SetInternalProperty(InternalPropertyIds::CachedUNumberFormat, nf, PropertyOperationFlags::PropertyOperation_None, nullptr); |
| } |
| |
| char16 *formattedN = nullptr; |
| int formattedNLength = 0; |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return unum_formatDouble(*nf, n, buf, bufLen, nullptr, status); |
| }, scriptContext->GetRecycler(), &formattedN, &formattedNLength); |
| |
| double nWithOptions = unum_parseDouble(*nf, reinterpret_cast<UChar *>(formattedN), formattedNLength, nullptr, &status); |
| |
| ICU_ASSERT(status, (n > 0.0 && nWithOptions <= n && nWithOptions >= 0.0) || |
| (n < 0.0 && nWithOptions >= n && nWithOptions <= 0) || |
| (n == 0.0 && nWithOptions == 0)); |
| |
| char16 *selected = nullptr; |
| int selectedLength = 0; |
| EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status) |
| { |
| return uplrules_select(*pr, nWithOptions, buf, bufLen, status); |
| }, scriptContext->GetRecycler(), &selected, &selectedLength); |
| |
| return JavascriptString::NewWithBuffer(selected, static_cast<charcount_t>(selectedLength), scriptContext); |
| #else |
| AssertOrFailFastMsg(false, "Intl-WinGlob should not be using PluralRulesSelect"); |
| return nullptr; |
| #endif |
| } |
| |
| /* |
| * This function registers built in functions when Intl initializes. |
| * Call with (Function : toRegister, integer : id) |
| * ID Mappings: |
| * - 0 for Date.prototype.toLocaleString |
| * - 1 for Date.prototype.toLocaleDateString |
| * - 2 for Date.prototype.toLocaleTimeString |
| * - 3 for Number.prototype.toLocaleString |
| * - 4 for String.prototype.localeCompare |
| */ |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_RegisterBuiltInFunction(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| // This function will only be used during the construction of the Intl object, hence Asserts are in place. |
| Assert(args.Info.Count >= 3 && VarIs<JavascriptFunction>(args.Values[1]) && TaggedInt::Is(args.Values[2])); |
| |
| JavascriptFunction *func = VarTo<JavascriptFunction>(args.Values[1]); |
| int32 id = TaggedInt::ToInt32(args.Values[2]); |
| Assert(id >= 0 && id < (int32)BuiltInFunctionID::Max); |
| |
| EngineInterfaceObject* nativeEngineInterfaceObj = scriptContext->GetLibrary()->GetEngineInterfaceObject(); |
| IntlEngineInterfaceExtensionObject* extensionObject = static_cast<IntlEngineInterfaceExtensionObject*>(nativeEngineInterfaceObj->GetEngineExtension(EngineInterfaceExtensionKind_Intl)); |
| |
| BuiltInFunctionID functionID = static_cast<BuiltInFunctionID>(id); |
| switch (functionID) |
| { |
| case BuiltInFunctionID::DateToLocaleString: |
| extensionObject->dateToLocaleString = func; |
| break; |
| case BuiltInFunctionID::DateToLocaleDateString: |
| extensionObject->dateToLocaleDateString = func; |
| break; |
| case BuiltInFunctionID::DateToLocaleTimeString: |
| extensionObject->dateToLocaleTimeString = func; |
| break; |
| case BuiltInFunctionID::NumberToLocaleString: |
| extensionObject->numberToLocaleString = func; |
| break; |
| case BuiltInFunctionID::StringLocaleCompare: |
| extensionObject->stringLocaleCompare = func; |
| break; |
| default: |
| AssertMsg(false, "functionID was not one of the allowed values. The previous assert should catch this."); |
| break; |
| } |
| |
| // Don't need to return anything |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_GetHiddenObject(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| if (callInfo.Count < 2 || !DynamicObject::IsBaseDynamicObject(args.Values[1])) |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| DynamicObject* obj = VarTo<DynamicObject>(args.Values[1]); |
| Var hiddenObject = nullptr; |
| if (!obj->GetInternalProperty(obj, Js::InternalPropertyIds::HiddenObject, &hiddenObject, NULL, scriptContext)) |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| return hiddenObject; |
| } |
| |
| Var IntlEngineInterfaceExtensionObject::EntryIntl_SetHiddenObject(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| EngineInterfaceObject_CommonFunctionProlog(function, callInfo); |
| |
| if (callInfo.Count < 3 || !DynamicObject::IsBaseDynamicObject(args.Values[1]) || !DynamicObject::IsBaseDynamicObject(args.Values[2])) |
| { |
| return scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| DynamicObject* obj = VarTo<DynamicObject>(args.Values[1]); |
| DynamicObject* value = VarTo<DynamicObject>(args.Values[2]); |
| |
| if (obj->SetInternalProperty(Js::InternalPropertyIds::HiddenObject, value, Js::PropertyOperationFlags::PropertyOperation_None, NULL)) |
| { |
| return scriptContext->GetLibrary()->GetTrue(); |
| } |
| else |
| { |
| return scriptContext->GetLibrary()->GetFalse(); |
| } |
| } |
| #endif |
| } |
| #endif |