IntlEngineInterfaceExtensionObject.cpp 143 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RuntimeLibraryPch.h"
  6. #include "EngineInterfaceObject.h"
  7. #include "IntlEngineInterfaceExtensionObject.h"
  8. #include "Types/DeferredTypeHandler.h"
  9. #include "Base/WindowsGlobalizationAdapter.h"
  10. #ifdef ENABLE_INTL_OBJECT
  11. #include "ByteCode/ByteCodeSerializer.h"
  12. #include "errstr.h"
  13. #include "ByteCode/ByteCodeDumper.h"
  14. #include "Codex/Utf8Helper.h"
  15. #ifdef INTL_WINGLOB
  16. using namespace Windows::Globalization;
  17. #endif
  18. #ifdef INTL_ICU
  19. #include <CommonPal.h>
  20. #include "PlatformAgnostic/ChakraICU.h"
  21. using namespace PlatformAgnostic::ICUHelpers;
  22. #if defined(DBG) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
  23. #define INTL_TRACE(fmt, ...) Output::Trace(Js::IntlPhase, _u("%S(): " fmt "\n"), __func__, __VA_ARGS__)
  24. #else
  25. #define INTL_TRACE(fmt, ...)
  26. #endif
  27. #define ICU_ASSERT(e, expr) \
  28. do \
  29. { \
  30. if (e == U_MEMORY_ALLOCATION_ERROR) \
  31. { \
  32. Js::Throw::OutOfMemory(); \
  33. } \
  34. else if (ICU_FAILURE(e)) \
  35. { \
  36. AssertOrFailFastMsg(false, ICU_ERRORMESSAGE(e)); \
  37. } \
  38. else if (!(expr)) \
  39. { \
  40. AssertOrFailFast(expr); \
  41. } \
  42. } while (false)
  43. #endif // INTL_ICU
  44. // NOTE(jahorto): Keep these enums in sync with those by the same name in Intl.js
  45. // These enums are used by both WinGlob- and ICU-backed Intl
  46. enum class NumberFormatStyle
  47. {
  48. Decimal, // Intl.NumberFormat(locale, { style: "decimal" }); // aka in our code as "number"
  49. Percent, // Intl.NumberFormat(locale, { style: "percent" });
  50. Currency, // Intl.NumberFormat(locale, { style: "currency", ... });
  51. Max,
  52. Default = Decimal,
  53. };
  54. enum class NumberFormatCurrencyDisplay
  55. {
  56. Symbol, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "symbol" }); // e.g. "$" or "US$" depeding on locale
  57. Code, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "code" }); // e.g. "USD"
  58. Name, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "name" }); // e.g. "US dollar"
  59. Max,
  60. Default = Symbol,
  61. };
  62. enum class CollatorSensitivity
  63. {
  64. Base,
  65. Accent,
  66. Case,
  67. Variant,
  68. Max,
  69. Default = Variant,
  70. };
  71. enum class CollatorCaseFirst
  72. {
  73. Upper,
  74. Lower,
  75. False,
  76. Max,
  77. Default = False,
  78. };
  79. #pragma warning(push)
  80. #pragma warning(disable:4309) // truncation of constant value
  81. #pragma warning(disable:4838) // conversion from 'int' to 'const char' requires a narrowing conversion
  82. #if DISABLE_JIT
  83. #if TARGET_64
  84. #include "InJavascript/Intl.js.nojit.bc.64b.h"
  85. #else
  86. #include "InJavascript/Intl.js.nojit.bc.32b.h"
  87. #endif
  88. #else
  89. #if TARGET_64
  90. #include "InJavascript/Intl.js.bc.64b.h"
  91. #else
  92. #include "InJavascript/Intl.js.bc.32b.h"
  93. #endif
  94. #endif
  95. #pragma warning(pop)
  96. #define IfFailAssertAndThrowHr(op) \
  97. if (FAILED(hr=(op))) \
  98. { \
  99. AssertMsg(false, "HRESULT was a failure."); \
  100. JavascriptError::MapAndThrowError(scriptContext, hr); \
  101. } \
  102. #define IfFailAssertMsgAndThrowHr(op, msg) \
  103. if (FAILED(hr=(op))) \
  104. { \
  105. AssertMsg(false, msg); \
  106. JavascriptError::MapAndThrowError(scriptContext, hr); \
  107. } \
  108. #ifdef INTL_WINGLOB
  109. #define TO_JSBOOL(sc, b) ((b) ? (sc)->GetLibrary()->GetTrue() : (sc)->GetLibrary()->GetFalse())
  110. #define IfCOMFailIgnoreSilentlyAndReturn(op) \
  111. if(FAILED(hr=(op))) \
  112. { \
  113. return; \
  114. } \
  115. #define HandleOOMSOEHR(hr) \
  116. if (hr == E_OUTOFMEMORY) \
  117. { \
  118. JavascriptError::ThrowOutOfMemoryError(scriptContext); \
  119. } \
  120. else if(hr == VBSERR_OutOfStack) \
  121. { \
  122. JavascriptError::ThrowStackOverflowError(scriptContext); \
  123. } \
  124. #define IfFailThrowHr(op) \
  125. if (FAILED(hr=(op))) \
  126. { \
  127. JavascriptError::MapAndThrowError(scriptContext, hr); \
  128. } \
  129. #define SetPropertyOn(obj, propID, value) \
  130. obj->SetProperty(propID, value, Js::PropertyOperationFlags::PropertyOperation_None, nullptr) \
  131. #define SetStringPropertyOn(obj, propID, propValue) \
  132. SetPropertyOn(obj, propID, Js::JavascriptString::NewCopySz(propValue, scriptContext)) \
  133. #define SetPropertyLOn(obj, literalProperty, value) \
  134. obj->SetProperty(Js::JavascriptString::NewCopySz(literalProperty, scriptContext), value, Js::PropertyOperationFlags::PropertyOperation_None, nullptr) \
  135. #define SetStringPropertyLOn(obj, literalProperty, propValue) \
  136. SetPropertyLOn(obj, literalProperty, Js::JavascriptString::NewCopySz(propValue, scriptContext)) \
  137. #define SetPropertyBuiltInOn(obj, builtInPropID, value) \
  138. SetPropertyOn(obj, Js::PropertyIds::builtInPropID, value) \
  139. #define SetStringPropertyBuiltInOn(obj, builtInPropID, propValue) \
  140. SetPropertyBuiltInOn(obj, builtInPropID, Js::JavascriptString::NewCopySz(propValue, scriptContext))
  141. #define GetPropertyFrom(obj, propertyID) \
  142. Js::JavascriptOperators::GetProperty(obj, propertyID, &propertyValue, scriptContext) \
  143. #define GetPropertyLFrom(obj, propertyName) \
  144. GetPropertyFrom(obj, scriptContext->GetOrAddPropertyIdTracked(propertyName, wcslen(propertyName)))
  145. #define GetPropertyBuiltInFrom(obj, builtInPropID) \
  146. GetPropertyFrom(obj, Js::PropertyIds::builtInPropID) \
  147. #define GetTypedPropertyBuiltInFrom(obj, builtInPropID, Type) \
  148. (GetPropertyFrom(obj, Js::PropertyIds::builtInPropID) && Type::Is(propertyValue)) \
  149. #define HasPropertyOn(obj, propID) \
  150. Js::JavascriptOperators::HasProperty(obj, propID) \
  151. #define HasPropertyBuiltInOn(obj, builtInPropID) \
  152. HasPropertyOn(obj, Js::PropertyIds::builtInPropID) \
  153. #define HasPropertyLOn(obj, propertyName) \
  154. HasPropertyOn(obj, scriptContext->GetOrAddPropertyIdTracked(propertyName, wcslen(propertyName)))
  155. #define SetHSTRINGPropertyOn(obj, propID, hstringValue) \
  156. SetStringPropertyOn(obj, propID, wgl->WindowsGetStringRawBuffer(hstringValue, &length)) \
  157. #define SetHSTRINGPropertyLOn(obj, literalProperty, hstringValue) \
  158. SetStringPropertyLOn(obj, literalProperty, wgl->WindowsGetStringRawBuffer(hstringValue, &length)) \
  159. #define SetHSTRINGPropertyBuiltInOn(obj, builtInPropID, hstringValue) \
  160. SetStringPropertyBuiltInOn(obj, builtInPropID, wgl->WindowsGetStringRawBuffer(hstringValue, &length)) \
  161. #endif
  162. #define INTL_CHECK_ARGS(argcheck) AssertOrFailFastMsg((argcheck), "Intl platform function given bad arguments")
  163. namespace Js
  164. {
  165. #ifdef ENABLE_INTL_OBJECT
  166. #ifdef INTL_WINGLOB
  167. class AutoHSTRING
  168. {
  169. PREVENT_COPY(AutoHSTRING)
  170. private:
  171. HSTRING value;
  172. public:
  173. HSTRING *operator&() { Assert(value == nullptr); return &value; }
  174. HSTRING operator*() const { Assert(value != nullptr); return value; }
  175. AutoHSTRING()
  176. : value(nullptr)
  177. { }
  178. ~AutoHSTRING()
  179. {
  180. Clear();
  181. }
  182. void Clear()
  183. {
  184. if (value != nullptr)
  185. {
  186. WindowsDeleteString(value);
  187. value = nullptr;
  188. }
  189. }
  190. };
  191. #endif
  192. // Defining Finalizable wrappers for Intl data
  193. #if defined(INTL_WINGLOB)
  194. class AutoCOMJSObject : public FinalizableObject
  195. {
  196. IInspectable *instance;
  197. public:
  198. DEFINE_VTABLE_CTOR_NOBASE(AutoCOMJSObject);
  199. AutoCOMJSObject(IInspectable *object)
  200. : instance(object)
  201. { }
  202. static AutoCOMJSObject * New(Recycler * recycler, IInspectable *object)
  203. {
  204. return RecyclerNewFinalized(recycler, AutoCOMJSObject, object);
  205. }
  206. void Finalize(bool isShutdown) override
  207. {
  208. }
  209. void Dispose(bool isShutdown) override
  210. {
  211. if (!isShutdown)
  212. {
  213. instance->Release();
  214. }
  215. }
  216. void Mark(Recycler * recycler) override
  217. {
  218. }
  219. IInspectable *GetInstance()
  220. {
  221. return instance;
  222. }
  223. };
  224. #elif defined(INTL_ICU)
  225. template<typename TResource, void(__cdecl * CloseFunction)(TResource)>
  226. class FinalizableICUObject : public FinalizableObject
  227. {
  228. private:
  229. FieldNoBarrier(TResource) resource;
  230. public:
  231. FinalizableICUObject(TResource resource) : resource(resource)
  232. {
  233. }
  234. static FinalizableICUObject<TResource, CloseFunction> *New(Recycler *recycler, TResource resource)
  235. {
  236. return RecyclerNewFinalized(recycler, FinalizableICUObject, resource);
  237. }
  238. TResource GetInstance()
  239. {
  240. return resource;
  241. }
  242. operator TResource()
  243. {
  244. return resource;
  245. }
  246. void Finalize(bool isShutdown) override
  247. {
  248. }
  249. void Dispose(bool isShutdown) override
  250. {
  251. if (!isShutdown)
  252. {
  253. CloseFunction(resource);
  254. }
  255. }
  256. void Mark(Recycler *recycler) override
  257. {
  258. }
  259. };
  260. typedef FinalizableICUObject<UNumberFormat *, unum_close> FinalizableUNumberFormat;
  261. typedef FinalizableICUObject<UDateFormat *, udat_close> FinalizableUDateFormat;
  262. typedef FinalizableICUObject<UFieldPositionIterator *, ufieldpositer_close> FinalizableUFieldPositionIterator;
  263. typedef FinalizableICUObject<UCollator *, ucol_close> FinalizableUCollator;
  264. typedef FinalizableICUObject<UPluralRules *, uplrules_close> FinalizableUPluralRules;
  265. template<typename TExecutor>
  266. 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)
  267. {
  268. UErrorCode status = U_ZERO_ERROR;
  269. *ret = RecyclerNewArrayLeaf(recycler, char16, firstTryLength);
  270. *returnLength = executor(reinterpret_cast<UChar *>(*ret), firstTryLength, &status);
  271. AssertOrFailFast(allowZeroLengthStrings ? *returnLength >= 0 : *returnLength > 0);
  272. if (ICU_BUFFER_FAILURE(status))
  273. {
  274. AssertOrFailFastMsg(*returnLength >= firstTryLength, "Executor reported buffer failure but did not require additional space");
  275. int secondTryLength = *returnLength + 1;
  276. INTL_TRACE("Buffer of length %d was too short, retrying with buffer of length %d", firstTryLength, secondTryLength);
  277. status = U_ZERO_ERROR;
  278. *ret = RecyclerNewArrayLeaf(recycler, char16, secondTryLength);
  279. *returnLength = executor(reinterpret_cast<UChar *>(*ret), secondTryLength, &status);
  280. AssertOrFailFastMsg(*returnLength == secondTryLength - 1, "Second try of executor returned unexpected length");
  281. }
  282. else
  283. {
  284. AssertOrFailFastMsg(*returnLength < firstTryLength, "Executor required additional length but reported successful status");
  285. }
  286. AssertOrFailFastMsg(!ICU_FAILURE(status), ICU_ERRORMESSAGE(status));
  287. }
  288. template <typename T>
  289. static T *AssertProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId)
  290. {
  291. Var propertyValue = nullptr;
  292. JavascriptOperators::GetProperty(state, propertyId, &propertyValue, state->GetScriptContext());
  293. AssertOrFailFast(propertyValue && T::Is(propertyValue));
  294. return T::UnsafeFromVar(propertyValue);
  295. }
  296. static JavascriptString *AssertStringProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId)
  297. {
  298. return AssertProperty<JavascriptString>(state, propertyId);
  299. }
  300. static int AssertIntegerProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId)
  301. {
  302. Var propertyValue = nullptr;
  303. JavascriptOperators::GetProperty(state, propertyId, &propertyValue, state->GetScriptContext());
  304. AssertOrFailFast(propertyValue);
  305. if (TaggedInt::Is(propertyValue))
  306. {
  307. return TaggedInt::ToInt32(propertyValue);
  308. }
  309. else
  310. {
  311. AssertOrFailFast(JavascriptNumber::Is(propertyValue));
  312. int ret;
  313. AssertOrFailFast(JavascriptNumber::TryGetInt32Value(JavascriptNumber::GetValue(propertyValue), &ret));
  314. return ret;
  315. }
  316. }
  317. static bool AssertBooleanProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId)
  318. {
  319. return AssertProperty<JavascriptBoolean>(state, propertyId)->GetValue();
  320. }
  321. template <typename T>
  322. static T AssertEnumProperty(_In_ DynamicObject *state, _In_ PropertyIds propertyId)
  323. {
  324. int p = AssertIntegerProperty(state, propertyId);
  325. T ret = static_cast<T>(p);
  326. AssertMsg(p >= 0 && ret < T::Max, "Invalid value for enum property");
  327. return ret;
  328. }
  329. template <typename T>
  330. static _Ret_notnull_ T ThrowOOMIfNull(_In_ T value)
  331. {
  332. if (value == nullptr)
  333. {
  334. Throw::OutOfMemory();
  335. }
  336. return value;
  337. }
  338. template <size_t N>
  339. static void LangtagToLocaleID(_In_count_(langtagLength) const char16 *langtag, _In_ charcount_t langtagLength, _Out_ char(&localeID)[N])
  340. {
  341. static_assert(N >= ULOC_FULLNAME_CAPACITY, "LocaleID must be large enough to fit the largest possible ICU localeID");
  342. UErrorCode status = U_ZERO_ERROR;
  343. utf8::WideToNarrow langtag8(langtag, langtagLength);
  344. int32_t localeIDLength = 0;
  345. uloc_forLanguageTag(langtag8, localeID, N, &localeIDLength, &status);
  346. ICU_ASSERT(status, localeIDLength > 0 && static_cast<size_t>(localeIDLength) < N);
  347. }
  348. template <size_t N>
  349. static void LangtagToLocaleID(_In_ JavascriptString *langtag, _Out_ char(&localeID)[N])
  350. {
  351. LangtagToLocaleID(langtag->GetSz(), langtag->GetLength(), localeID);
  352. }
  353. template <typename Callback>
  354. static void ForEachUEnumeration(UEnumeration *enumeration, Callback callback)
  355. {
  356. int valueLength = 0;
  357. UErrorCode status = U_ZERO_ERROR;
  358. for (int index = 0, const char *value = uenum_next(enumeration, &valueLength, &status); value != nullptr; index++, value = uenum_next(enumeration, &valueLength, &status))
  359. {
  360. ICU_ASSERT(status, valueLength > 0);
  361. // cast valueLength now since we have verified its greater than 0
  362. callback(index, value, static_cast<charcount_t>(valueLength));
  363. }
  364. }
  365. template <typename Callback>
  366. static void ForEachUEnumeration16(UEnumeration *enumeration, Callback callback)
  367. {
  368. int valueLength = 0;
  369. UErrorCode status = U_ZERO_ERROR;
  370. int index = 0;
  371. for (const UChar *value = uenum_unext(enumeration, &valueLength, &status); value != nullptr; index++, value = uenum_unext(enumeration, &valueLength, &status))
  372. {
  373. ICU_ASSERT(status, valueLength > 0);
  374. // cast valueLength now since we have verified its greater than 0
  375. callback(index, reinterpret_cast<const char16 *>(value), static_cast<charcount_t>(valueLength));
  376. }
  377. }
  378. #endif
  379. IntlEngineInterfaceExtensionObject::IntlEngineInterfaceExtensionObject(Js::ScriptContext* scriptContext) :
  380. EngineExtensionObjectBase(EngineInterfaceExtensionKind_Intl, scriptContext),
  381. dateToLocaleString(nullptr),
  382. dateToLocaleTimeString(nullptr),
  383. dateToLocaleDateString(nullptr),
  384. numberToLocaleString(nullptr),
  385. stringLocaleCompare(nullptr),
  386. intlNativeInterfaces(nullptr),
  387. intlByteCode(nullptr),
  388. wasInitialized(false)
  389. {
  390. }
  391. // Initializes the IntlEngineInterfaceExtensionObject::EntryInfo struct
  392. #ifdef INTL_ENTRY
  393. #undef INTL_ENTRY
  394. #endif
  395. #define INTL_ENTRY(id, func) \
  396. NoProfileFunctionInfo IntlEngineInterfaceExtensionObject::EntryInfo::Intl_##func##(FORCE_NO_WRITE_BARRIER_TAG(IntlEngineInterfaceExtensionObject::EntryIntl_##func##));
  397. #include "IntlExtensionObjectBuiltIns.h"
  398. #undef INTL_ENTRY
  399. #ifdef INTL_WINGLOB
  400. WindowsGlobalizationAdapter* IntlEngineInterfaceExtensionObject::GetWindowsGlobalizationAdapter(_In_ ScriptContext * scriptContext)
  401. {
  402. return scriptContext->GetThreadContext()->GetWindowsGlobalizationAdapter();
  403. }
  404. #endif
  405. void IntlEngineInterfaceExtensionObject::Initialize()
  406. {
  407. if (wasInitialized)
  408. {
  409. return;
  410. }
  411. JavascriptLibrary* library = scriptContext->GetLibrary();
  412. // Ensure JsBuiltIns are initialized before initializing Intl which uses some of them.
  413. library->EnsureBuiltInEngineIsReady();
  414. DynamicObject* commonObject = library->GetEngineInterfaceObject()->GetCommonNativeInterfaces();
  415. if (scriptContext->IsIntlEnabled())
  416. {
  417. Assert(library->GetEngineInterfaceObject() != nullptr);
  418. this->intlNativeInterfaces = DynamicObject::New(library->GetRecycler(),
  419. DynamicType::New(scriptContext, TypeIds_Object, commonObject, nullptr,
  420. DeferredTypeHandler<InitializeIntlNativeInterfaces>::GetDefaultInstance()));
  421. library->AddMember(library->GetEngineInterfaceObject(), Js::PropertyIds::Intl, this->intlNativeInterfaces);
  422. // Only show the platform object publicly if -IntlPlatform is passed
  423. if (CONFIG_FLAG(IntlPlatform))
  424. {
  425. library->AddMember(library->GetIntlObject(), PropertyIds::platform, this->intlNativeInterfaces);
  426. }
  427. }
  428. wasInitialized = true;
  429. }
  430. #if DBG
  431. void IntlEngineInterfaceExtensionObject::DumpByteCode()
  432. {
  433. Output::Print(_u("Dumping Intl Byte Code:"));
  434. Assert(this->intlByteCode);
  435. Js::ByteCodeDumper::DumpRecursively(intlByteCode);
  436. }
  437. #endif
  438. bool IntlEngineInterfaceExtensionObject::InitializeIntlNativeInterfaces(DynamicObject* intlNativeInterfaces, DeferredTypeHandlerBase * typeHandler, DeferredInitializeMode mode)
  439. {
  440. typeHandler->Convert(intlNativeInterfaces, mode, 16);
  441. ScriptContext* scriptContext = intlNativeInterfaces->GetScriptContext();
  442. JavascriptLibrary* library = scriptContext->GetLibrary();
  443. // gives each entrypoint a property ID on the intlNativeInterfaces library object
  444. #ifdef INTL_ENTRY
  445. #undef INTL_ENTRY
  446. #endif
  447. #define INTL_ENTRY(id, func) \
  448. library->AddFunctionToLibraryObject(intlNativeInterfaces, Js::PropertyIds::##id, &IntlEngineInterfaceExtensionObject::EntryInfo::Intl_##func, 1);
  449. #include "IntlExtensionObjectBuiltIns.h"
  450. #undef INTL_ENTRY
  451. #if INTL_WINGLOB
  452. library->AddMember(intlNativeInterfaces, Js::PropertyIds::winglob, library->GetTrue());
  453. #else
  454. library->AddMember(intlNativeInterfaces, Js::PropertyIds::winglob, library->GetFalse());
  455. // when using ICU, we can call ulocdata_getCLDRVersion to ensure that ICU is functioning properly before allowing Intl to continue.
  456. // 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.
  457. // This is to protect against spurious U_MISSING_RESOURCE_ERRORs and U_FILE_ACCESS_ERRORs coming from early-lifecycle
  458. // functions that require ICU data.
  459. // See OS#16897150, OS#16896933, and others relating to bad statuses returned by GetLocaleData and IsLocaleAvailable
  460. // This was initially attempted using u_init, however u_init does not work with Node's default small-icu data file
  461. // because it contains no converters.
  462. UErrorCode status = U_ZERO_ERROR;
  463. UVersionInfo cldrVersion;
  464. ulocdata_getCLDRVersion(cldrVersion, &status);
  465. if (status == U_MEMORY_ALLOCATION_ERROR || status == U_FILE_ACCESS_ERROR || status == U_MISSING_RESOURCE_ERROR)
  466. {
  467. // Trace that this happens in case there are build system changes that actually cause the data file to be not found
  468. INTL_TRACE("Could not initialize ICU - ulocdata_getCLDRVersion returned status %S", u_errorName(status));
  469. Throw::OutOfMemory();
  470. }
  471. else
  472. {
  473. INTL_TRACE("Using CLDR version %d.%d.%d.%d", cldrVersion[0], cldrVersion[1], cldrVersion[2], cldrVersion[3]);
  474. }
  475. AssertOrFailFastMsg(U_SUCCESS(status), "ulocdata_getCLDRVersion returned non-OOM failure");
  476. #endif // else !INTL_WINGLOB
  477. intlNativeInterfaces->SetHasNoEnumerableProperties(true);
  478. return true;
  479. }
  480. void IntlEngineInterfaceExtensionObject::deletePrototypePropertyHelper(ScriptContext* scriptContext, DynamicObject* intlObject, Js::PropertyId objectPropertyId, Js::PropertyId getterFunctionId)
  481. {
  482. DynamicObject *prototypeObject = nullptr;
  483. DynamicObject *functionObj = nullptr;
  484. Var propertyValue = nullptr;
  485. Var prototypeValue = nullptr;
  486. Var resolvedOptionsValue = nullptr;
  487. Var getter = nullptr;
  488. Var setter = nullptr;
  489. if (!JavascriptOperators::GetProperty(intlObject, objectPropertyId, &propertyValue, scriptContext) ||
  490. !JavascriptOperators::IsObject(propertyValue))
  491. {
  492. return;
  493. }
  494. if (!JavascriptOperators::GetProperty(DynamicObject::FromVar(propertyValue), Js::PropertyIds::prototype, &prototypeValue, scriptContext) ||
  495. !JavascriptOperators::IsObject(prototypeValue))
  496. {
  497. return;
  498. }
  499. prototypeObject = DynamicObject::FromVar(prototypeValue);
  500. if (!JavascriptOperators::GetProperty(prototypeObject, Js::PropertyIds::resolvedOptions, &resolvedOptionsValue, scriptContext) ||
  501. !JavascriptOperators::IsObject(resolvedOptionsValue))
  502. {
  503. return;
  504. }
  505. functionObj = DynamicObject::FromVar(resolvedOptionsValue);
  506. functionObj->SetConfigurable(Js::PropertyIds::prototype, true);
  507. functionObj->DeleteProperty(Js::PropertyIds::prototype, Js::PropertyOperationFlags::PropertyOperation_None);
  508. if (!JavascriptOperators::GetOwnAccessors(prototypeObject, getterFunctionId, &getter, &setter, scriptContext) ||
  509. !JavascriptOperators::IsObject(getter))
  510. {
  511. return;
  512. }
  513. functionObj = DynamicObject::FromVar(getter);
  514. functionObj->SetConfigurable(Js::PropertyIds::prototype, true);
  515. functionObj->DeleteProperty(Js::PropertyIds::prototype, Js::PropertyOperationFlags::PropertyOperation_None);
  516. }
  517. void IntlEngineInterfaceExtensionObject::cleanUpIntl(ScriptContext *scriptContext, DynamicObject* intlObject)
  518. {
  519. this->dateToLocaleString = nullptr;
  520. this->dateToLocaleTimeString = nullptr;
  521. this->dateToLocaleDateString = nullptr;
  522. this->numberToLocaleString = nullptr;
  523. this->stringLocaleCompare = nullptr;
  524. //Failed to setup Intl; Windows.Globalization.dll is most likely missing.
  525. if (Js::JavascriptOperators::HasProperty(intlObject, Js::PropertyIds::Collator))
  526. {
  527. intlObject->DeleteProperty(Js::PropertyIds::Collator, Js::PropertyOperationFlags::PropertyOperation_None);
  528. }
  529. if (Js::JavascriptOperators::HasProperty(intlObject, Js::PropertyIds::NumberFormat))
  530. {
  531. intlObject->DeleteProperty(Js::PropertyIds::NumberFormat, Js::PropertyOperationFlags::PropertyOperation_None);
  532. }
  533. if (Js::JavascriptOperators::HasProperty(intlObject, Js::PropertyIds::DateTimeFormat))
  534. {
  535. intlObject->DeleteProperty(Js::PropertyIds::DateTimeFormat, Js::PropertyOperationFlags::PropertyOperation_None);
  536. }
  537. }
  538. void IntlEngineInterfaceExtensionObject::EnsureIntlByteCode(_In_ ScriptContext * scriptContext)
  539. {
  540. if (this->intlByteCode == nullptr)
  541. {
  542. SourceContextInfo * sourceContextInfo = scriptContext->GetSourceContextInfo(Js::Constants::NoHostSourceContext, NULL);
  543. Assert(sourceContextInfo != nullptr);
  544. SRCINFO si;
  545. memset(&si, 0, sizeof(si));
  546. si.sourceContextInfo = sourceContextInfo;
  547. SRCINFO *hsi = scriptContext->AddHostSrcInfo(&si);
  548. uint32 flags = fscrIsLibraryCode | (CONFIG_FLAG(CreateFunctionProxy) && !scriptContext->IsProfiling() ? fscrAllowFunctionProxy : 0);
  549. HRESULT hr = Js::ByteCodeSerializer::DeserializeFromBuffer(scriptContext, flags, (LPCUTF8)nullptr, hsi, (byte*)Library_Bytecode_Intl, nullptr, &this->intlByteCode);
  550. IfFailAssertMsgAndThrowHr(hr, "Failed to deserialize Intl.js bytecode - very probably the bytecode needs to be rebuilt.");
  551. this->SetHasBytecode();
  552. }
  553. }
  554. void IntlEngineInterfaceExtensionObject::InjectIntlLibraryCode(_In_ ScriptContext * scriptContext, DynamicObject* intlObject, IntlInitializationType intlInitializationType)
  555. {
  556. JavascriptExceptionObject *pExceptionObject = nullptr;
  557. #ifdef INTL_WINGLOB
  558. WindowsGlobalizationAdapter* globAdapter = GetWindowsGlobalizationAdapter(scriptContext);
  559. #endif
  560. try {
  561. this->EnsureIntlByteCode(scriptContext);
  562. Assert(intlByteCode != nullptr);
  563. #ifdef INTL_WINGLOB
  564. DelayLoadWindowsGlobalization *library = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  565. #endif
  566. JavascriptString* initType = nullptr;
  567. #ifdef INTL_WINGLOB
  568. HRESULT hr;
  569. //Ensure we have initialized all appropriate COM objects for the adapter (we will be using them now)
  570. IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureCommonObjectsInitialized(library));
  571. #endif
  572. switch (intlInitializationType)
  573. {
  574. default:
  575. AssertMsg(false, "Not a valid intlInitializationType.");
  576. // fall thru
  577. case IntlInitializationType::Intl:
  578. #ifdef INTL_WINGLOB
  579. IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureNumberFormatObjectsInitialized(library));
  580. IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureDateTimeFormatObjectsInitialized(library));
  581. #endif
  582. initType = scriptContext->GetPropertyString(PropertyIds::Intl);
  583. break;
  584. case IntlInitializationType::StringPrototype:
  585. // No other windows globalization adapter needed. Common adapter should suffice
  586. initType = scriptContext->GetPropertyString(PropertyIds::String);
  587. break;
  588. case IntlInitializationType::DatePrototype:
  589. #ifdef INTL_WINGLOB
  590. IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureDateTimeFormatObjectsInitialized(library));
  591. #endif
  592. initType = scriptContext->GetPropertyString(PropertyIds::Date);
  593. break;
  594. case IntlInitializationType::NumberPrototype:
  595. #ifdef INTL_WINGLOB
  596. IfCOMFailIgnoreSilentlyAndReturn(globAdapter->EnsureNumberFormatObjectsInitialized(library));
  597. #endif
  598. initType = scriptContext->GetPropertyString(PropertyIds::Number);
  599. break;
  600. }
  601. Js::ScriptFunction *function = scriptContext->GetLibrary()->CreateScriptFunction(intlByteCode->GetNestedFunctionForExecution(0));
  602. #ifdef ENABLE_SCRIPT_PROFILING
  603. // If we are profiling, we need to register the script to the profiler callback, so the script compiled event will be sent.
  604. if (scriptContext->IsProfiling())
  605. {
  606. scriptContext->RegisterScript(function->GetFunctionProxy());
  607. }
  608. #endif
  609. #ifdef ENABLE_SCRIPT_DEBUGGING
  610. // Mark we are profiling library code already, so that any initialization library code called here won't be reported to profiler.
  611. // Also tell the debugger not to record events during intialization so that we don't leak information about initialization.
  612. AutoInitLibraryCodeScope autoInitLibraryCodeScope(scriptContext);
  613. #endif
  614. Js::Var args[] = { scriptContext->GetLibrary()->GetUndefined(), scriptContext->GetLibrary()->GetEngineInterfaceObject(), initType };
  615. Js::CallInfo callInfo(Js::CallFlags_Value, _countof(args));
  616. // Clear disable implicit call bit as initialization code doesn't have any side effect
  617. Js::ImplicitCallFlags saveImplicitCallFlags = scriptContext->GetThreadContext()->GetImplicitCallFlags();
  618. scriptContext->GetThreadContext()->ClearDisableImplicitFlags();
  619. Js::Arguments arguments(callInfo, args);
  620. BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())
  621. {
  622. JavascriptFunction::CallRootFunctionInScript(function, arguments);
  623. }
  624. END_SAFE_REENTRANT_CALL
  625. scriptContext->GetThreadContext()->SetImplicitCallFlags((Js::ImplicitCallFlags)(saveImplicitCallFlags));
  626. // Delete prototypes on functions if initialized Intl object
  627. if (intlInitializationType == IntlInitializationType::Intl)
  628. {
  629. deletePrototypePropertyHelper(scriptContext, intlObject, Js::PropertyIds::Collator, Js::PropertyIds::compare);
  630. deletePrototypePropertyHelper(scriptContext, intlObject, Js::PropertyIds::NumberFormat, Js::PropertyIds::format);
  631. deletePrototypePropertyHelper(scriptContext, intlObject, Js::PropertyIds::DateTimeFormat, Js::PropertyIds::format);
  632. }
  633. #if DBG_DUMP
  634. if (PHASE_DUMP(Js::ByteCodePhase, function->GetFunctionProxy()) && Js::Configuration::Global.flags.Verbose)
  635. {
  636. DumpByteCode();
  637. }
  638. #endif
  639. }
  640. catch (const JavascriptException& err)
  641. {
  642. pExceptionObject = err.GetAndClear();
  643. }
  644. if (pExceptionObject)
  645. {
  646. if (intlInitializationType == IntlInitializationType::Intl)
  647. {
  648. cleanUpIntl(scriptContext, intlObject);
  649. }
  650. if (pExceptionObject == ThreadContext::GetContextForCurrentThread()->GetPendingOOMErrorObject() ||
  651. pExceptionObject == ThreadContext::GetContextForCurrentThread()->GetPendingSOErrorObject())
  652. {
  653. // Reset factory objects that are might not have fully initialized
  654. #ifdef INTL_WINGLOB
  655. globAdapter->ResetCommonFactoryObjects();
  656. #endif
  657. switch (intlInitializationType) {
  658. default:
  659. AssertMsg(false, "Not a valid intlInitializationType.");
  660. // fall thru
  661. case IntlInitializationType::Intl:
  662. #ifdef INTL_WINGLOB
  663. globAdapter->ResetNumberFormatFactoryObjects();
  664. globAdapter->ResetDateTimeFormatFactoryObjects();
  665. #endif
  666. scriptContext->GetLibrary()->ResetIntlObject();
  667. break;
  668. case IntlInitializationType::StringPrototype:
  669. // No other windows globalization adapter is created. Resetting common adapter should suffice
  670. break;
  671. case IntlInitializationType::DatePrototype:
  672. #ifdef INTL_WINGLOB
  673. globAdapter->ResetDateTimeFormatFactoryObjects();
  674. #endif
  675. break;
  676. case IntlInitializationType::NumberPrototype:
  677. #ifdef INTL_WINGLOB
  678. globAdapter->ResetNumberFormatFactoryObjects();
  679. #endif
  680. break;
  681. }
  682. JavascriptExceptionOperators::DoThrowCheckClone(pExceptionObject, scriptContext);
  683. }
  684. #if DEBUG
  685. JavascriptExceptionOperators::DoThrowCheckClone(pExceptionObject, scriptContext);
  686. #else
  687. JavascriptError::ThrowTypeError(scriptContext, JSERR_IntlNotAvailable);
  688. #endif
  689. }
  690. }
  691. // First parameter is boolean.
  692. Var IntlEngineInterfaceExtensionObject::EntryIntl_RaiseAssert(RecyclableObject* function, CallInfo callInfo, ...)
  693. {
  694. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  695. if (args.Info.Count < 2 || !JavascriptError::Is(args.Values[1]))
  696. {
  697. AssertMsg(false, "Intl's Assert platform API was called incorrectly.");
  698. return scriptContext->GetLibrary()->GetUndefined();
  699. }
  700. #if DEBUG
  701. #ifdef INTL_ICU_DEBUG
  702. Output::Print(_u("EntryIntl_RaiseAssert\n"));
  703. #endif
  704. JavascriptExceptionOperators::Throw(JavascriptError::FromVar(args.Values[1]), scriptContext);
  705. #else
  706. return scriptContext->GetLibrary()->GetUndefined();
  707. #endif
  708. }
  709. Var IntlEngineInterfaceExtensionObject::EntryIntl_IsWellFormedLanguageTag(RecyclableObject* function, CallInfo callInfo, ...)
  710. {
  711. #if defined(INTL_ICU)
  712. AssertOrFailFastMsg(false, "IsWellFormedLanguageTag is not implemented using ICU");
  713. return nullptr;
  714. #else
  715. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  716. if (args.Info.Count < 2 || !JavascriptString::Is(args.Values[1]))
  717. {
  718. // IsWellFormedLanguageTag of undefined or non-string is false
  719. return scriptContext->GetLibrary()->GetFalse();
  720. }
  721. JavascriptString *argString = JavascriptString::FromVar(args.Values[1]);
  722. return TO_JSBOOL(scriptContext, GetWindowsGlobalizationAdapter(scriptContext)->IsWellFormedLanguageTag(scriptContext, argString->GetSz()));
  723. #endif
  724. }
  725. Var IntlEngineInterfaceExtensionObject::EntryIntl_NormalizeLanguageTag(RecyclableObject* function, CallInfo callInfo, ...)
  726. {
  727. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  728. #if defined(INTL_ICU)
  729. INTL_CHECK_ARGS(args.Info.Count == 2 && JavascriptString::Is(args[1]));
  730. UErrorCode status = U_ZERO_ERROR;
  731. JavascriptString *langtag = JavascriptString::UnsafeFromVar(args[1]);
  732. utf8::WideToNarrow langtag8(langtag->GetSz(), langtag->GetLength());
  733. // ICU doesn't have a full-fledged canonicalization implementation that correctly replaces all preferred values
  734. // and grandfathered tags, as required by #sec-canonicalizelanguagetag.
  735. // However, passing the locale through uloc_forLanguageTag -> uloc_toLanguageTag gets us most of the way there
  736. // by replacing some(?) values, correctly capitalizing the tag, and re-ordering extensions
  737. int parsedLength = 0;
  738. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  739. int forLangTagResultLength = uloc_forLanguageTag(langtag8, localeID, ULOC_FULLNAME_CAPACITY, &parsedLength, &status);
  740. AssertOrFailFast(parsedLength >= 0);
  741. if (status == U_ILLEGAL_ARGUMENT_ERROR || ((charcount_t) parsedLength) < langtag->GetLength())
  742. {
  743. // The string passed in to NormalizeLanguageTag has already passed IsStructurallyValidLanguageTag.
  744. // However, duplicate unicode extension keys, such as "de-u-co-phonebk-co-phonebk", are structurally
  745. // valid according to RFC5646 yet still trigger U_ILLEGAL_ARGUMENT_ERROR
  746. // V8 ~6.2 says that the above language tag is invalid, while SpiderMonkey ~58 handles it.
  747. // Until we have a more spec-compliant implementation of CanonicalizeLanguageTag, err on the side
  748. // of caution and say it is invalid.
  749. // We also check for parsedLength < langtag->GetLength() because there are cases when status == U_ZERO_ERROR
  750. // but the langtag was not valid, such as "en-tesTER-TESter" (OSS-Fuzz #6657).
  751. // NOTE: make sure we check for `undefined` at the platform.normalizeLanguageTag callsite.
  752. return scriptContext->GetLibrary()->GetUndefined();
  753. }
  754. // forLangTagResultLength can be 0 if langtag is "und".
  755. // uloc_toLanguageTag("") returns "und", so this works out (forLanguageTag can return >= 0 but toLanguageTag must return > 0)
  756. ICU_ASSERT(status, forLangTagResultLength >= 0 && ((charcount_t) parsedLength) == langtag->GetLength());
  757. char canonicalized[ULOC_FULLNAME_CAPACITY] = { 0 };
  758. int toLangTagResultLength = uloc_toLanguageTag(localeID, canonicalized, ULOC_FULLNAME_CAPACITY, true, &status);
  759. ICU_ASSERT(status, toLangTagResultLength > 0);
  760. // allocate toLangTagResultLength + 1 to leave room for null terminator
  761. char16 *canonicalized16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, toLangTagResultLength + 1);
  762. charcount_t canonicalized16Len = 0;
  763. HRESULT hr = utf8::NarrowStringToWideNoAlloc(
  764. canonicalized,
  765. toLangTagResultLength,
  766. canonicalized16,
  767. toLangTagResultLength + 1,
  768. &canonicalized16Len
  769. );
  770. AssertOrFailFast(hr == S_OK && ((int) canonicalized16Len) == toLangTagResultLength);
  771. return JavascriptString::NewWithBuffer(canonicalized16, toLangTagResultLength, scriptContext);
  772. #else
  773. if (args.Info.Count < 2 || !JavascriptString::Is(args.Values[1]))
  774. {
  775. // NormalizeLanguageTag of undefined or non-string is undefined
  776. return scriptContext->GetLibrary()->GetUndefined();
  777. }
  778. JavascriptString *argString = JavascriptString::FromVar(args.Values[1]);
  779. JavascriptString *retVal;
  780. HRESULT hr;
  781. AutoHSTRING str;
  782. hr = GetWindowsGlobalizationAdapter(scriptContext)->NormalizeLanguageTag(scriptContext, argString->GetSz(), &str);
  783. DelayLoadWindowsGlobalization *wsl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  784. PCWSTR strBuf = wsl->WindowsGetStringRawBuffer(*str, NULL);
  785. retVal = Js::JavascriptString::NewCopySz(strBuf, scriptContext);
  786. if (FAILED(hr))
  787. {
  788. HandleOOMSOEHR(hr);
  789. //If we can't normalize the tag; return undefined.
  790. return scriptContext->GetLibrary()->GetUndefined();
  791. }
  792. return retVal;
  793. #endif
  794. }
  795. #ifdef INTL_ICU
  796. template <const char *(__cdecl *GetAvailableLocalesFunc)(int), int(__cdecl *CountAvailableLocalesFunc)(void)>
  797. static bool BinarySearchForLocale(const char *localeID)
  798. {
  799. const int count = CountAvailableLocalesFunc();
  800. int left = 0;
  801. int right = count - 1;
  802. int iterations = 0;
  803. while (true)
  804. {
  805. iterations += 1;
  806. if (left > right)
  807. {
  808. INTL_TRACE("Could not find localeID %S in %d iterations", localeID, iterations);
  809. return false;
  810. }
  811. int i = (left + right) / 2;
  812. Assert(i >= 0 && i < count);
  813. const char *cur = GetAvailableLocalesFunc(i);
  814. // Ensure that this list is actually binary searchable
  815. Assert(i > 0 ? strcmp(GetAvailableLocalesFunc(i - 1), cur) < 0 : true);
  816. Assert(i < count - 1 ? strcmp(GetAvailableLocalesFunc(i + 1), cur) > 0 : true);
  817. int res = strcmp(localeID, cur);
  818. if (res == 0)
  819. {
  820. INTL_TRACE("Found localeID %S in %d iterations", localeID, iterations);
  821. return true;
  822. }
  823. else if (res < 0)
  824. {
  825. right = i - 1;
  826. }
  827. else
  828. {
  829. left = i + 1;
  830. }
  831. }
  832. }
  833. template <const char *(__cdecl *GetAvailableLocalesFunc)(int), int(__cdecl *CountAvailableLocalesFunc)(void)>
  834. static bool IsLocaleAvailable(JavascriptString *langtag)
  835. {
  836. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  837. LangtagToLocaleID(langtag, localeID);
  838. if (!BinarySearchForLocale<GetAvailableLocalesFunc, CountAvailableLocalesFunc>(localeID))
  839. {
  840. // ICU's "available locales" do not include (all? most?) aliases.
  841. // For example, searching for "zh_TW" will return false, even though
  842. // zh_TW is equivalent to zh_Hant_TW, which would return true.
  843. // We can work around this by searching for both the locale as requested and
  844. // the locale in addition to all of its "likely subtags."
  845. // This works in practice because, for instance, unum_open("zh_TW") will actually
  846. // use "zh_Hant_TW" (confirmed with unum_getLocaleByType(..., ULOC_VALID_LOCALE))
  847. // The code below performs, for example, the following mappings:
  848. // pa_PK -> pa_Arab_PK
  849. // sr_RS -> sr_Cyrl_RS
  850. // zh_CN -> zh_Hans_CN
  851. // zh_TW -> zh_Hant_TW
  852. // TODO(jahorto): Determine if there is any scenario where a language tag + likely subtags is
  853. // not exactly functionally equivalent to the language tag on its own -- basically, where
  854. // constructor_open(language_tag) behaves differently to constructor_open(language_tag_and_likely_subtags)
  855. // for all supported constructors.
  856. UErrorCode status = U_ZERO_ERROR;
  857. char localeIDWithLikelySubtags[ULOC_FULLNAME_CAPACITY] = { 0 };
  858. int likelySubtagLen = uloc_addLikelySubtags(localeID, localeIDWithLikelySubtags, ULOC_FULLNAME_CAPACITY, &status);
  859. ICU_ASSERT(status, likelySubtagLen > 0 && likelySubtagLen < ULOC_FULLNAME_CAPACITY);
  860. return BinarySearchForLocale<GetAvailableLocalesFunc, CountAvailableLocalesFunc>(localeIDWithLikelySubtags);
  861. }
  862. return true;
  863. }
  864. #endif
  865. #ifdef INTL_ICU
  866. #define DEFINE_ISXLOCALEAVAILABLE(ctorShortName, icuNamespace) \
  867. Var IntlEngineInterfaceExtensionObject::EntryIntl_Is##ctorShortName##LocaleAvailable(RecyclableObject* function, CallInfo callInfo, ...) \
  868. { \
  869. EngineInterfaceObject_CommonFunctionProlog(function, callInfo); \
  870. INTL_CHECK_ARGS(args.Info.Count == 2 && JavascriptString::Is(args.Values[1])); \
  871. return scriptContext->GetLibrary()->GetTrueOrFalse( \
  872. IsLocaleAvailable<##icuNamespace##_getAvailable, ##icuNamespace##_countAvailable>(JavascriptString::UnsafeFromVar(args.Values[1])) \
  873. ); \
  874. }
  875. #else
  876. #define DEFINE_ISXLOCALEAVAILABLE(ctorShortName, icuNamespace) \
  877. Var IntlEngineInterfaceExtensionObject::EntryIntl_Is##ctorShortName##LocaleAvailable(RecyclableObject* function, CallInfo callInfo, ...) \
  878. { \
  879. AssertOrFailFastMsg(false, "Intl with Windows Globalization should never call Is" #ctorShortName "LocaleAvailable"); \
  880. return nullptr; \
  881. }
  882. #endif
  883. DEFINE_ISXLOCALEAVAILABLE(Collator, ucol)
  884. DEFINE_ISXLOCALEAVAILABLE(NF, unum)
  885. DEFINE_ISXLOCALEAVAILABLE(DTF, udat)
  886. // uplrules namespace doesn't have its own getAvailable/countAvailable
  887. // assume it supports whatever is supported in the base data
  888. DEFINE_ISXLOCALEAVAILABLE(PR, uloc)
  889. #ifdef INTL_ICU
  890. enum class LocaleDataKind
  891. {
  892. Collation,
  893. CaseFirst,
  894. Numeric,
  895. Calendar,
  896. NumberingSystem,
  897. HourCycle
  898. };
  899. #endif
  900. Var IntlEngineInterfaceExtensionObject::EntryIntl_GetLocaleData(RecyclableObject* function, CallInfo callInfo, ...)
  901. {
  902. #ifdef INTL_ICU
  903. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  904. INTL_CHECK_ARGS(
  905. args.Info.Count == 3 &&
  906. (JavascriptNumber::Is(args.Values[1]) || TaggedInt::Is(args.Values[1])) &&
  907. JavascriptString::Is(args.Values[2])
  908. );
  909. LocaleDataKind kind = (LocaleDataKind) (TaggedInt::Is(args.Values[1])
  910. ? TaggedInt::ToInt32(args.Values[1])
  911. : (int) JavascriptNumber::GetValue(args.Values[1]));
  912. JavascriptArray *ret = nullptr;
  913. UErrorCode status = U_ZERO_ERROR;
  914. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  915. JavascriptString *langtag = JavascriptString::UnsafeFromVar(args.Values[2]);
  916. LangtagToLocaleID(langtag, localeID);
  917. JavascriptLibrary *library = scriptContext->GetLibrary();
  918. PropertyOperationFlags flag = PropertyOperationFlags::PropertyOperation_None;
  919. if (kind == LocaleDataKind::Collation)
  920. {
  921. ScopedUEnumeration collations(ucol_getKeywordValuesForLocale("collation", localeID, false, &status));
  922. ICU_ASSERT(status, true);
  923. // 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]
  924. ret = library->CreateArray(uenum_count(collations, &status) - 1);
  925. ICU_ASSERT(status, true);
  926. ret->SetItem(0, library->GetNull(), flag);
  927. int collationLen = 0;
  928. const char *collation = nullptr;
  929. int i = 0;
  930. for (collation = uenum_next(collations, &collationLen, &status); collation != nullptr; collation = uenum_next(collations, &collationLen, &status))
  931. {
  932. ICU_ASSERT(status, collation != nullptr && collationLen > 0);
  933. if (strcmp(collation, "standard") == 0 || strcmp(collation, "search") == 0)
  934. {
  935. // continue does not create holes in ret because i is set outside the loop
  936. continue;
  937. }
  938. // OS#17172584: OOM during uloc_toUnicodeLocaleType can make this return nullptr even for known collations
  939. const char *unicodeCollation = ThrowOOMIfNull(uloc_toUnicodeLocaleType("collation", collation));
  940. const size_t unicodeCollationLen = strlen(unicodeCollation);
  941. // we only need strlen(unicodeCollation) + 1 char16s because unicodeCollation will always be ASCII (funnily enough)
  942. char16 *unicodeCollation16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, strlen(unicodeCollation) + 1);
  943. charcount_t unicodeCollation16Len = 0;
  944. HRESULT hr = utf8::NarrowStringToWideNoAlloc(
  945. unicodeCollation,
  946. unicodeCollationLen,
  947. unicodeCollation16,
  948. unicodeCollationLen + 1,
  949. &unicodeCollation16Len
  950. );
  951. AssertOrFailFastMsg(
  952. hr == S_OK && unicodeCollation16Len == unicodeCollationLen && unicodeCollation16Len < MaxCharCount,
  953. "Unicode collation char16 conversion was unsuccessful"
  954. );
  955. // i + 1 to not ovewrite leading null element
  956. ret->SetItem(i + 1, JavascriptString::NewWithBuffer(
  957. unicodeCollation16,
  958. unicodeCollation16Len,
  959. scriptContext
  960. ), PropertyOperationFlags::PropertyOperation_None);
  961. i++;
  962. }
  963. }
  964. else if (kind == LocaleDataKind::CaseFirst)
  965. {
  966. ScopedUCollator collator(ucol_open(localeID, &status));
  967. UColAttributeValue kf = ucol_getAttribute(collator, UCOL_CASE_FIRST, &status);
  968. ICU_ASSERT(status, true);
  969. ret = library->CreateArray(3);
  970. JavascriptString *falseStr = library->GetFalseDisplayString();
  971. JavascriptString *upperStr = library->GetIntlCaseFirstUpperString();
  972. JavascriptString *lowerStr = library->GetIntlCaseFirstLowerString();
  973. if (kf == UCOL_OFF)
  974. {
  975. ret->SetItem(0, falseStr, flag);
  976. ret->SetItem(1, upperStr, flag);
  977. ret->SetItem(2, lowerStr, flag);
  978. }
  979. else if (kf == UCOL_UPPER_FIRST)
  980. {
  981. ret->SetItem(0, upperStr, flag);
  982. ret->SetItem(1, lowerStr, flag);
  983. ret->SetItem(2, falseStr, flag);
  984. }
  985. else if (kf == UCOL_LOWER_FIRST)
  986. {
  987. ret->SetItem(0, lowerStr, flag);
  988. ret->SetItem(1, upperStr, flag);
  989. ret->SetItem(2, falseStr, flag);
  990. }
  991. }
  992. else if (kind == LocaleDataKind::Numeric)
  993. {
  994. ScopedUCollator collator(ucol_open(localeID, &status));
  995. UColAttributeValue kn = ucol_getAttribute(collator, UCOL_NUMERIC_COLLATION, &status);
  996. ICU_ASSERT(status, true);
  997. ret = library->CreateArray(2);
  998. JavascriptString *falseStr = library->GetFalseDisplayString();
  999. JavascriptString *trueStr = library->GetTrueDisplayString();
  1000. if (kn == UCOL_OFF)
  1001. {
  1002. ret->SetItem(0, falseStr, flag);
  1003. ret->SetItem(1, trueStr, flag);
  1004. }
  1005. else if (kn == UCOL_ON)
  1006. {
  1007. ret->SetItem(0, trueStr, flag);
  1008. ret->SetItem(1, falseStr, flag);
  1009. }
  1010. }
  1011. else if (kind == LocaleDataKind::Calendar)
  1012. {
  1013. ScopedUEnumeration calendars(ucal_getKeywordValuesForLocale("calendar", localeID, false, &status));
  1014. ret = library->CreateArray(uenum_count(calendars, &status));
  1015. ICU_ASSERT(status, true);
  1016. int calendarLen = 0;
  1017. const char *calendar = nullptr;
  1018. int i = 0;
  1019. for (calendar = uenum_next(calendars, &calendarLen, &status); calendar != nullptr; calendar = uenum_next(calendars, &calendarLen, &status))
  1020. {
  1021. ICU_ASSERT(status, calendar != nullptr && calendarLen > 0);
  1022. // OS#17172584: OOM during uloc_toUnicodeLocaleType can make this return nullptr even for known calendars
  1023. const char *unicodeCalendar = ThrowOOMIfNull(uloc_toUnicodeLocaleType("calendar", calendar));
  1024. const size_t unicodeCalendarLen = strlen(unicodeCalendar);
  1025. // we only need strlen(unicodeCalendar) + 1 char16s because unicodeCalendar will always be ASCII (funnily enough)
  1026. char16 *unicodeCalendar16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, strlen(unicodeCalendar) + 1);
  1027. charcount_t unicodeCalendar16Len = 0;
  1028. HRESULT hr = utf8::NarrowStringToWideNoAlloc(
  1029. unicodeCalendar,
  1030. unicodeCalendarLen,
  1031. unicodeCalendar16,
  1032. unicodeCalendarLen + 1,
  1033. &unicodeCalendar16Len
  1034. );
  1035. AssertOrFailFastMsg(
  1036. hr == S_OK && unicodeCalendar16Len == unicodeCalendarLen && unicodeCalendar16Len < MaxCharCount,
  1037. "Unicode calendar char16 conversion was unsuccessful"
  1038. );
  1039. ret->SetItem(i, JavascriptString::NewWithBuffer(
  1040. unicodeCalendar16,
  1041. static_cast<charcount_t>(unicodeCalendar16Len),
  1042. scriptContext
  1043. ), flag);
  1044. i++;
  1045. }
  1046. }
  1047. else if (kind == LocaleDataKind::NumberingSystem)
  1048. {
  1049. // unumsys_openAvailableNames has multiple bugs (http://bugs.icu-project.org/trac/ticket/11908) and also
  1050. // does not provide a locale-specific set of numbering systems
  1051. // the Intl spec provides a list of required numbering systems to support in #table-numbering-system-digits
  1052. // For now, assume that all of those numbering systems are supported, and just get the default using unumsys_open
  1053. // unumsys_open will also ensure that "native", "traditio", and "finance" are not returned, as per #sec-intl.datetimeformat-internal-slots
  1054. ScopedUNumberingSystem numsys(unumsys_open(localeID, &status));
  1055. ICU_ASSERT(status, true);
  1056. utf8::NarrowToWide numsysName(unumsys_getName(numsys));
  1057. // NOTE: update the initial array length if the list of available numbering systems changes in the future!
  1058. ret = library->CreateArray(22);
  1059. int i = 0;
  1060. ret->SetItem(i++, JavascriptString::NewCopySz(numsysName, scriptContext), flag);
  1061. // It doesn't matter that item 0 will be in the array twice (aside for size), because item 0 is the
  1062. // preferred numbering system for the given locale, so it has precedence over everything else
  1063. ret->SetItem(i++, library->GetIntlNumsysArabString(), flag);
  1064. ret->SetItem(i++, library->GetIntlNumsysArabextString(), flag);
  1065. ret->SetItem(i++, library->GetIntlNumsysBaliString(), flag);
  1066. ret->SetItem(i++, library->GetIntlNumsysBengString(), flag);
  1067. ret->SetItem(i++, library->GetIntlNumsysDevaString(), flag);
  1068. ret->SetItem(i++, library->GetIntlNumsysFullwideString(), flag);
  1069. ret->SetItem(i++, library->GetIntlNumsysGujrString(), flag);
  1070. ret->SetItem(i++, library->GetIntlNumsysGuruString(), flag);
  1071. ret->SetItem(i++, library->GetIntlNumsysHanidecString(), flag);
  1072. ret->SetItem(i++, library->GetIntlNumsysKhmrString(), flag);
  1073. ret->SetItem(i++, library->GetIntlNumsysKndaString(), flag);
  1074. ret->SetItem(i++, library->GetIntlNumsysLaooString(), flag);
  1075. ret->SetItem(i++, library->GetIntlNumsysLatnString(), flag);
  1076. ret->SetItem(i++, library->GetIntlNumsysLimbString(), flag);
  1077. ret->SetItem(i++, library->GetIntlNumsysMlymString(), flag);
  1078. ret->SetItem(i++, library->GetIntlNumsysMongString(), flag);
  1079. ret->SetItem(i++, library->GetIntlNumsysMymrString(), flag);
  1080. ret->SetItem(i++, library->GetIntlNumsysOryaString(), flag);
  1081. ret->SetItem(i++, library->GetIntlNumsysTamldecString(), flag);
  1082. ret->SetItem(i++, library->GetIntlNumsysTeluString(), flag);
  1083. ret->SetItem(i++, library->GetIntlNumsysThaiString(), flag);
  1084. ret->SetItem(i++, library->GetIntlNumsysTibtString(), flag);
  1085. }
  1086. else if (kind == LocaleDataKind::HourCycle)
  1087. {
  1088. // #sec-intl.datetimeformat-internal-slots: "[[LocaleData]][locale].hc must be < null, h11, h12, h23, h24 > for all locale values"
  1089. ret = library->CreateArray(5);
  1090. int i = 0;
  1091. ret->SetItem(i++, library->GetNull(), flag);
  1092. ret->SetItem(i++, library->GetIntlHourCycle11String(), flag);
  1093. ret->SetItem(i++, library->GetIntlHourCycle12String(), flag);
  1094. ret->SetItem(i++, library->GetIntlHourCycle23String(), flag);
  1095. ret->SetItem(i++, library->GetIntlHourCycle24String(), flag);
  1096. }
  1097. else
  1098. {
  1099. AssertOrFailFastMsg(false, "GetLocaleData called with unknown kind parameter");
  1100. }
  1101. return ret;
  1102. #else
  1103. AssertOrFailFastMsg(false, "Intl with Windows Globalization should never call GetLocaleData");
  1104. return nullptr;
  1105. #endif
  1106. }
  1107. Var IntlEngineInterfaceExtensionObject::EntryIntl_ResolveLocaleLookup(RecyclableObject* function, CallInfo callInfo, ...)
  1108. {
  1109. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1110. if (args.Info.Count < 2 || !JavascriptString::Is(args.Values[1]))
  1111. {
  1112. // ResolveLocaleLookup of undefined or non-string is undefined
  1113. return scriptContext->GetLibrary()->GetUndefined();
  1114. }
  1115. #if defined(INTL_ICU)
  1116. #if defined(INTL_ICU_DEBUG)
  1117. Output::Print(_u("Intl::ResolveLocaleLookup returned false: EntryIntl_ResolveLocaleLookup returning null to fallback to JS\n"));
  1118. #endif
  1119. return scriptContext->GetLibrary()->GetNull();
  1120. #else
  1121. JavascriptString *argString = JavascriptString::FromVar(args.Values[1]);
  1122. PCWSTR passedLocale = argString->GetSz();
  1123. // REVIEW should we zero the whole array for safety?
  1124. WCHAR resolvedLocaleName[LOCALE_NAME_MAX_LENGTH];
  1125. resolvedLocaleName[0] = '\0';
  1126. ResolveLocaleName(passedLocale, resolvedLocaleName, _countof(resolvedLocaleName));
  1127. if (resolvedLocaleName[0] == '\0')
  1128. {
  1129. return scriptContext->GetLibrary()->GetUndefined();
  1130. }
  1131. return JavascriptString::NewCopySz(resolvedLocaleName, scriptContext);
  1132. #endif
  1133. }
  1134. Var IntlEngineInterfaceExtensionObject::EntryIntl_ResolveLocaleBestFit(RecyclableObject* function, CallInfo callInfo, ...)
  1135. {
  1136. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1137. if (args.Info.Count < 2 || !JavascriptString::Is(args.Values[1]))
  1138. {
  1139. // NormalizeLanguageTag of undefined or non-string is undefined
  1140. return scriptContext->GetLibrary()->GetUndefined();
  1141. }
  1142. #if defined(INTL_ICU)
  1143. AssertOrFailFastMsg(false, "Intl-ICU does not implement ResolveLocaleBestFit");
  1144. return nullptr;
  1145. #else // !INTL_ICU
  1146. JavascriptString *localeStrings = JavascriptString::FromVar(args.Values[1]);
  1147. PCWSTR passedLocale = localeStrings->GetSz();
  1148. DelayLoadWindowsGlobalization* wgl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  1149. WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext);
  1150. AutoCOMPtr<DateTimeFormatting::IDateTimeFormatter> formatter;
  1151. HRESULT hr;
  1152. if (FAILED(hr = wga->CreateDateTimeFormatter(scriptContext, _u("longdate"), &passedLocale, 1, nullptr, nullptr, &formatter)))
  1153. {
  1154. HandleOOMSOEHR(hr);
  1155. return scriptContext->GetLibrary()->GetUndefined();
  1156. }
  1157. AutoHSTRING locale;
  1158. if (FAILED(hr = wga->GetResolvedLanguage(formatter, &locale)))
  1159. {
  1160. HandleOOMSOEHR(hr);
  1161. return scriptContext->GetLibrary()->GetUndefined();
  1162. }
  1163. return JavascriptString::NewCopySz(wgl->WindowsGetStringRawBuffer(*locale, NULL), scriptContext);
  1164. #endif
  1165. }
  1166. Var IntlEngineInterfaceExtensionObject::EntryIntl_GetDefaultLocale(RecyclableObject* function, CallInfo callInfo, ...)
  1167. {
  1168. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1169. #ifdef INTL_WINGLOB
  1170. char16 defaultLocale[LOCALE_NAME_MAX_LENGTH];
  1171. defaultLocale[0] = '\0';
  1172. if (GetUserDefaultLocaleName(defaultLocale, _countof(defaultLocale)) == 0)
  1173. {
  1174. JavascriptError::MapAndThrowError(scriptContext, HRESULT_FROM_WIN32(GetLastError()));
  1175. }
  1176. return JavascriptString::NewCopySz(defaultLocale, scriptContext);
  1177. #else
  1178. UErrorCode status = U_ZERO_ERROR;
  1179. char defaultLangtag[ULOC_FULLNAME_CAPACITY] = { 0 };
  1180. char defaultLocaleID[ULOC_FULLNAME_CAPACITY] = { 0 };
  1181. int localeIDActual = uloc_getName(nullptr, defaultLocaleID, _countof(defaultLocaleID), &status);
  1182. ICU_ASSERT(status, localeIDActual > 0 && localeIDActual < _countof(defaultLocaleID));
  1183. int langtagActual = uloc_toLanguageTag(defaultLocaleID, defaultLangtag, _countof(defaultLangtag), true, &status);
  1184. ICU_ASSERT(status, langtagActual > 0 && langtagActual < _countof(defaultLangtag));
  1185. char16 *defaultLangtag16 = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), char16, langtagActual + 1);
  1186. charcount_t defaultLangtag16Actual = 0;
  1187. utf8::NarrowStringToWideNoAlloc(defaultLangtag, static_cast<size_t>(langtagActual), defaultLangtag16, langtagActual + 1, &defaultLangtag16Actual);
  1188. AssertOrFailFastMsg(defaultLangtag16Actual == static_cast<size_t>(langtagActual), "Language tags should always be ASCII");
  1189. return JavascriptString::NewWithBuffer(defaultLangtag16, defaultLangtag16Actual, scriptContext);
  1190. #endif
  1191. }
  1192. Var IntlEngineInterfaceExtensionObject::EntryIntl_GetExtensions(RecyclableObject* function, CallInfo callInfo, ...)
  1193. {
  1194. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1195. if (args.Info.Count < 2 || !JavascriptString::Is(args.Values[1]))
  1196. {
  1197. // NormalizeLanguageTag of undefined or non-string is undefined
  1198. return scriptContext->GetLibrary()->GetUndefined();
  1199. }
  1200. #ifdef INTL_WINGLOB
  1201. DelayLoadWindowsGlobalization* wgl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  1202. WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext);
  1203. AutoCOMPtr<ILanguage> language;
  1204. AutoCOMPtr<ILanguageExtensionSubtags> extensionSubtags;
  1205. HRESULT hr;
  1206. if (FAILED(hr = wga->CreateLanguage(scriptContext, JavascriptString::FromVar(args.Values[1])->GetSz(), &language)))
  1207. {
  1208. HandleOOMSOEHR(hr);
  1209. return scriptContext->GetLibrary()->GetUndefined();
  1210. }
  1211. if (FAILED(hr = language->QueryInterface(__uuidof(ILanguageExtensionSubtags), reinterpret_cast<void**>(&extensionSubtags))))
  1212. {
  1213. HandleOOMSOEHR(hr);
  1214. return scriptContext->GetLibrary()->GetUndefined();
  1215. }
  1216. Assert(extensionSubtags);
  1217. AutoHSTRING singletonString;
  1218. AutoCOMPtr<Windows::Foundation::Collections::IVectorView<HSTRING>> subtags;
  1219. uint32 length;
  1220. if (FAILED(hr = wgl->WindowsCreateString(_u("u"), 1, &singletonString)) || FAILED(hr = extensionSubtags->GetExtensionSubtags(*singletonString, &subtags)) || FAILED(subtags->get_Size(&length)))
  1221. {
  1222. HandleOOMSOEHR(hr);
  1223. return scriptContext->GetLibrary()->GetUndefined();
  1224. }
  1225. JavascriptArray *toReturn = scriptContext->GetLibrary()->CreateArray(length);
  1226. for (uint32 i = 0; i < length; i++)
  1227. {
  1228. AutoHSTRING str;
  1229. if (!FAILED(hr = wga->GetItemAt(subtags, i, &str)))
  1230. {
  1231. toReturn->SetItem(i, JavascriptString::NewCopySz(wgl->WindowsGetStringRawBuffer(*str, NULL), scriptContext), Js::PropertyOperationFlags::PropertyOperation_None);
  1232. }
  1233. else
  1234. {
  1235. HandleOOMSOEHR(hr);
  1236. }
  1237. }
  1238. return toReturn;
  1239. #else
  1240. AssertOrFailFastMsg(false, "ICU should not be calling platform.getExtensions");
  1241. return nullptr;
  1242. #endif
  1243. }
  1244. #ifdef INTL_ICU
  1245. // This is used by both NumberFormat and PluralRules
  1246. static void SetUNumberFormatDigitOptions(UNumberFormat *fmt, DynamicObject *state)
  1247. {
  1248. if (JavascriptOperators::HasProperty(state, PropertyIds::minimumSignificantDigits))
  1249. {
  1250. unum_setAttribute(fmt, UNUM_SIGNIFICANT_DIGITS_USED, true);
  1251. unum_setAttribute(fmt, UNUM_MIN_SIGNIFICANT_DIGITS, AssertIntegerProperty(state, PropertyIds::minimumSignificantDigits));
  1252. unum_setAttribute(fmt, UNUM_MAX_SIGNIFICANT_DIGITS, AssertIntegerProperty(state, PropertyIds::maximumSignificantDigits));
  1253. }
  1254. else
  1255. {
  1256. unum_setAttribute(fmt, UNUM_MIN_INTEGER_DIGITS, AssertIntegerProperty(state, PropertyIds::minimumIntegerDigits));
  1257. unum_setAttribute(fmt, UNUM_MIN_FRACTION_DIGITS, AssertIntegerProperty(state, PropertyIds::minimumFractionDigits));
  1258. unum_setAttribute(fmt, UNUM_MAX_FRACTION_DIGITS, AssertIntegerProperty(state, PropertyIds::maximumFractionDigits));
  1259. }
  1260. }
  1261. #endif
  1262. Var IntlEngineInterfaceExtensionObject::EntryIntl_CacheNumberFormat(RecyclableObject * function, CallInfo callInfo, ...)
  1263. {
  1264. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1265. INTL_CHECK_ARGS(args.Info.Count == 2 && DynamicObject::Is(args.Values[1]));
  1266. #if defined(INTL_ICU)
  1267. DynamicObject *state = DynamicObject::UnsafeFromVar(args.Values[1]);
  1268. // always AssertOrFailFast that the properties we need are there, because if they aren't, Intl.js isn't functioning correctly
  1269. NumberFormatStyle style = AssertEnumProperty<NumberFormatStyle>(state, PropertyIds::formatterToUse);
  1270. UNumberFormatStyle unumStyle = UNUM_IGNORE;
  1271. UErrorCode status = U_ZERO_ERROR;
  1272. JavascriptString *currency = nullptr;
  1273. JavascriptString *langtag = AssertStringProperty(state, PropertyIds::locale);
  1274. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  1275. LangtagToLocaleID(langtag, localeID);
  1276. if (style == NumberFormatStyle::Decimal)
  1277. {
  1278. unumStyle = UNUM_DECIMAL;
  1279. }
  1280. else if (style == NumberFormatStyle::Percent)
  1281. {
  1282. unumStyle = UNUM_PERCENT;
  1283. }
  1284. else if (style == NumberFormatStyle::Currency)
  1285. {
  1286. NumberFormatCurrencyDisplay nfcd = AssertEnumProperty<NumberFormatCurrencyDisplay>(state, PropertyIds::currencyDisplayToUse);
  1287. // TODO(jahorto): Investigate making our enum values equal to the corresponding UNumberFormatStyle values
  1288. if (nfcd == NumberFormatCurrencyDisplay::Symbol)
  1289. {
  1290. unumStyle = UNUM_CURRENCY;
  1291. }
  1292. else if (nfcd == NumberFormatCurrencyDisplay::Code)
  1293. {
  1294. unumStyle = UNUM_CURRENCY_ISO;
  1295. }
  1296. else if (nfcd == NumberFormatCurrencyDisplay::Name)
  1297. {
  1298. unumStyle = UNUM_CURRENCY_PLURAL;
  1299. }
  1300. currency = AssertStringProperty(state, PropertyIds::currency);
  1301. }
  1302. AssertOrFailFast(unumStyle != UNUM_IGNORE);
  1303. auto fmt = FinalizableUNumberFormat::New(scriptContext->GetRecycler(), unum_open(unumStyle, nullptr, 0, localeID, nullptr, &status));
  1304. ICU_ASSERT(status, true);
  1305. bool groupingUsed = AssertBooleanProperty(state, PropertyIds::useGrouping);
  1306. unum_setAttribute(*fmt, UNUM_GROUPING_USED, groupingUsed);
  1307. unum_setAttribute(*fmt, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
  1308. SetUNumberFormatDigitOptions(*fmt, state);
  1309. if (currency != nullptr)
  1310. {
  1311. unum_setTextAttribute(*fmt, UNUM_CURRENCY_CODE, reinterpret_cast<const UChar *>(currency->GetSz()), currency->GetLength(), &status);
  1312. ICU_ASSERT(status, true);
  1313. }
  1314. state->SetInternalProperty(
  1315. InternalPropertyIds::HiddenObject,
  1316. fmt,
  1317. PropertyOperationFlags::PropertyOperation_None,
  1318. nullptr
  1319. );
  1320. return scriptContext->GetLibrary()->GetUndefined();
  1321. #else
  1322. HRESULT hr = S_OK;
  1323. JavascriptString *localeJSstr = nullptr;
  1324. DynamicObject *options = DynamicObject::FromVar(args.Values[1]);
  1325. DelayLoadWindowsGlobalization* wgl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  1326. WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext);
  1327. Var propertyValue;
  1328. // Verify locale is present
  1329. // REVIEW (doilij): Fix comparison of the unsigned value <= 0
  1330. if (!GetTypedPropertyBuiltInFrom(options, __locale, JavascriptString) || (localeJSstr = JavascriptString::FromVar(propertyValue))->GetLength() <= 0)
  1331. {
  1332. // REVIEW (doilij): Should we throw? Or otherwise, from Intl.js, should detect something didn't work right here...
  1333. return scriptContext->GetLibrary()->GetUndefined();
  1334. }
  1335. //First we have to determine which formatter(number, percent, or currency) we will be using.
  1336. //Note some options might not be present.
  1337. AutoCOMPtr<NumberFormatting::INumberFormatter> numberFormatter(nullptr);
  1338. PCWSTR locale = localeJSstr->GetSz();
  1339. uint16 formatterToUseVal = 0; // 0 (default) is number, 1 is percent, 2 is currency
  1340. if (GetTypedPropertyBuiltInFrom(options, __formatterToUse, TaggedInt) && (formatterToUseVal = TaggedInt::ToUInt16(propertyValue)) == 1)
  1341. {
  1342. //Use the percent formatter
  1343. IfFailThrowHr(wga->CreatePercentFormatter(scriptContext, &locale, 1, &numberFormatter));
  1344. }
  1345. else if (formatterToUseVal == 2)
  1346. {
  1347. //Use the currency formatter
  1348. AutoCOMPtr<NumberFormatting::ICurrencyFormatter> currencyFormatter(nullptr);
  1349. if (!GetTypedPropertyBuiltInFrom(options, __currency, JavascriptString))
  1350. {
  1351. return scriptContext->GetLibrary()->GetUndefined();
  1352. }
  1353. //API call retrieves a currency formatter, have to query its interface for numberFormatter
  1354. IfFailThrowHr(GetWindowsGlobalizationAdapter(scriptContext)->CreateCurrencyFormatter(scriptContext, &locale, 1, JavascriptString::FromVar(propertyValue)->GetSz(), &currencyFormatter));
  1355. if (GetTypedPropertyBuiltInFrom(options, __currencyDisplayToUse, TaggedInt)) // 0 is for symbol, 1 is for code, 2 is for name.
  1356. //Currently name isn't supported; so it will default to code in that case.
  1357. {
  1358. AutoCOMPtr<NumberFormatting::ICurrencyFormatter2> currencyFormatter2(nullptr);
  1359. IfFailThrowHr(currencyFormatter->QueryInterface(__uuidof(NumberFormatting::ICurrencyFormatter2), reinterpret_cast<void**>(&currencyFormatter2)));
  1360. if (TaggedInt::ToUInt16(propertyValue) == 0)
  1361. {
  1362. IfFailThrowHr(currencyFormatter2->put_Mode(NumberFormatting::CurrencyFormatterMode::CurrencyFormatterMode_UseSymbol));
  1363. }
  1364. else
  1365. {
  1366. IfFailThrowHr(currencyFormatter2->put_Mode(NumberFormatting::CurrencyFormatterMode::CurrencyFormatterMode_UseCurrencyCode));
  1367. }
  1368. }
  1369. IfFailThrowHr(currencyFormatter->QueryInterface(__uuidof(NumberFormatting::INumberFormatter), reinterpret_cast<void**>(&numberFormatter)));
  1370. }
  1371. else
  1372. {
  1373. //Use the number formatter (default)
  1374. IfFailThrowHr(wga->CreateNumberFormatter(scriptContext, &locale, 1, &numberFormatter));
  1375. }
  1376. Assert(numberFormatter);
  1377. AutoCOMPtr<NumberFormatting::ISignedZeroOption> signedZeroOption(nullptr);
  1378. IfFailThrowHr(numberFormatter->QueryInterface(__uuidof(NumberFormatting::ISignedZeroOption), reinterpret_cast<void**>(&signedZeroOption)));
  1379. IfFailThrowHr(signedZeroOption->put_IsZeroSigned(true));
  1380. //Configure non-digit related options
  1381. AutoCOMPtr<NumberFormatting::INumberFormatterOptions> numberFormatterOptions(nullptr);
  1382. IfFailThrowHr(numberFormatter->QueryInterface(__uuidof(NumberFormatting::INumberFormatterOptions), reinterpret_cast<void**>(&numberFormatterOptions)));
  1383. Assert(numberFormatterOptions);
  1384. if (GetTypedPropertyBuiltInFrom(options, __useGrouping, JavascriptBoolean))
  1385. {
  1386. IfFailThrowHr(numberFormatterOptions->put_IsGrouped((boolean)(JavascriptBoolean::FromVar(propertyValue)->GetValue())));
  1387. }
  1388. //Get the numeral system and add it to the object since it will be located in the locale
  1389. AutoHSTRING hNumeralSystem;
  1390. AutoHSTRING hResolvedLanguage;
  1391. uint32 length;
  1392. IfFailThrowHr(wga->GetNumeralSystem(numberFormatterOptions, &hNumeralSystem));
  1393. SetHSTRINGPropertyBuiltInOn(options, __numberingSystem, *hNumeralSystem);
  1394. IfFailThrowHr(wga->GetResolvedLanguage(numberFormatterOptions, &hResolvedLanguage));
  1395. SetHSTRINGPropertyBuiltInOn(options, __locale, *hResolvedLanguage);
  1396. AutoCOMPtr<NumberFormatting::INumberRounderOption> rounderOptions(nullptr);
  1397. IfFailThrowHr(numberFormatter->QueryInterface(__uuidof(NumberFormatting::INumberRounderOption), reinterpret_cast<void**>(&rounderOptions)));
  1398. Assert(rounderOptions);
  1399. if (HasPropertyBuiltInOn(options, __minimumSignificantDigits) || HasPropertyBuiltInOn(options, __maximumSignificantDigits))
  1400. {
  1401. uint16 minSignificantDigits = 1, maxSignificantDigits = 21;
  1402. //Do significant digit rounding
  1403. if (GetTypedPropertyBuiltInFrom(options, __minimumSignificantDigits, TaggedInt))
  1404. {
  1405. minSignificantDigits = max<uint16>(min<uint16>(TaggedInt::ToUInt16(propertyValue), 21), 1);
  1406. }
  1407. if (GetTypedPropertyBuiltInFrom(options, __maximumSignificantDigits, TaggedInt))
  1408. {
  1409. maxSignificantDigits = max<uint16>(min<uint16>(TaggedInt::ToUInt16(propertyValue), 21), minSignificantDigits);
  1410. }
  1411. prepareWithSignificantDigits(scriptContext, rounderOptions, numberFormatter, numberFormatterOptions, minSignificantDigits, maxSignificantDigits);
  1412. }
  1413. else
  1414. {
  1415. uint16 minFractionDigits = 0, maxFractionDigits = 3, minIntegerDigits = 1;
  1416. //Do fraction/integer digit rounding
  1417. if (GetTypedPropertyBuiltInFrom(options, __minimumIntegerDigits, TaggedInt))
  1418. {
  1419. minIntegerDigits = max<uint16>(min<uint16>(TaggedInt::ToUInt16(propertyValue), 21), 1);
  1420. }
  1421. if (GetTypedPropertyBuiltInFrom(options, __minimumFractionDigits, TaggedInt))
  1422. {
  1423. minFractionDigits = min<uint16>(TaggedInt::ToUInt16(propertyValue), 20);//ToUInt16 will get rid of negatives by making them high
  1424. }
  1425. if (GetTypedPropertyBuiltInFrom(options, __maximumFractionDigits, TaggedInt))
  1426. {
  1427. maxFractionDigits = max(min<uint16>(TaggedInt::ToUInt16(propertyValue), 20), minFractionDigits);//ToUInt16 will get rid of negatives by making them high
  1428. }
  1429. prepareWithFractionIntegerDigits(scriptContext, rounderOptions, numberFormatterOptions, minFractionDigits, maxFractionDigits + (formatterToUseVal == 1 ? 2 : 0), minIntegerDigits);//extend max fractions for percent
  1430. }
  1431. //Set the object as a cache
  1432. numberFormatter->AddRef();
  1433. options->SetInternalProperty(Js::InternalPropertyIds::HiddenObject, AutoCOMJSObject::New(scriptContext->GetRecycler(), numberFormatter), Js::PropertyOperationFlags::PropertyOperation_None, NULL);
  1434. return scriptContext->GetLibrary()->GetUndefined();
  1435. #endif
  1436. }
  1437. // Unlike CacheNumberFormat; this call takes an additional parameter to specify whether we are going to cache it.
  1438. // We have to create this formatter twice; first time get the date/time patterns; and second time cache with correct format string.
  1439. Var IntlEngineInterfaceExtensionObject::EntryIntl_CreateDateTimeFormat(RecyclableObject * function, CallInfo callInfo, ...)
  1440. {
  1441. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1442. if (args.Info.Count < 3 || !DynamicObject::Is(args.Values[1]) || !JavascriptBoolean::Is(args.Values[2]))
  1443. {
  1444. return scriptContext->GetLibrary()->GetUndefined();
  1445. }
  1446. #ifdef INTL_WINGLOB
  1447. DynamicObject* obj = DynamicObject::FromVar(args.Values[1]);
  1448. DelayLoadWindowsGlobalization* wgl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  1449. WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext);
  1450. HRESULT hr;
  1451. Var propertyValue = nullptr;
  1452. uint32 length;
  1453. PCWSTR locale = GetTypedPropertyBuiltInFrom(obj, __locale, JavascriptString) ? JavascriptString::FromVar(propertyValue)->GetSz() : nullptr;
  1454. PCWSTR templateString = GetTypedPropertyBuiltInFrom(obj, __templateString, JavascriptString) ? JavascriptString::FromVar(propertyValue)->GetSz() : nullptr;
  1455. if (locale == nullptr || templateString == nullptr)
  1456. {
  1457. AssertMsg(false, "For some reason, locale and templateString aren't defined or aren't a JavascriptString.");
  1458. return scriptContext->GetLibrary()->GetUndefined();
  1459. }
  1460. PCWSTR clock = GetTypedPropertyBuiltInFrom(obj, __windowsClock, JavascriptString) ? JavascriptString::FromVar(propertyValue)->GetSz() : nullptr;
  1461. AutoHSTRING hDummyCalendar;
  1462. if (clock != nullptr)
  1463. {
  1464. //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.
  1465. AutoCOMPtr<DateTimeFormatting::IDateTimeFormatter> dummyFormatter;
  1466. IfFailThrowHr(wga->CreateDateTimeFormatter(scriptContext, templateString, &locale, 1, nullptr, nullptr, &dummyFormatter));
  1467. IfFailThrowHr(wga->GetCalendar(dummyFormatter, &hDummyCalendar));
  1468. }
  1469. //Now create the real formatter.
  1470. AutoCOMPtr<DateTimeFormatting::IDateTimeFormatter> cachedFormatter;
  1471. IfFailThrowHr(wga->CreateDateTimeFormatter(scriptContext, templateString, &locale, 1,
  1472. clock == nullptr ? nullptr : wgl->WindowsGetStringRawBuffer(*hDummyCalendar, &length), clock, &cachedFormatter));
  1473. AutoHSTRING hCalendar;
  1474. AutoHSTRING hClock;
  1475. AutoHSTRING hLocale;
  1476. AutoHSTRING hNumberingSystem;
  1477. //In case the upper code path wasn't hit; extract the calendar string again so it can be set.
  1478. IfFailThrowHr(wga->GetCalendar(cachedFormatter, &hCalendar));
  1479. SetHSTRINGPropertyBuiltInOn(obj, __windowsCalendar, *hCalendar);
  1480. IfFailThrowHr(wga->GetClock(cachedFormatter, &hClock));
  1481. SetHSTRINGPropertyBuiltInOn(obj, __windowsClock, *hClock);
  1482. IfFailThrowHr(wga->GetResolvedLanguage(cachedFormatter, &hLocale));
  1483. SetHSTRINGPropertyBuiltInOn(obj, __locale, *hLocale);
  1484. //Get the numbering system
  1485. IfFailThrowHr(wga->GetNumeralSystem(cachedFormatter, &hNumberingSystem));
  1486. SetHSTRINGPropertyBuiltInOn(obj, __numberingSystem, *hNumberingSystem);
  1487. //Extract the pattern strings
  1488. AutoCOMPtr<Windows::Foundation::Collections::IVectorView<HSTRING>> dateResult;
  1489. IfFailThrowHr(cachedFormatter->get_Patterns(&dateResult));
  1490. IfFailThrowHr(dateResult->get_Size(&length));
  1491. JavascriptArray *patternStrings = scriptContext->GetLibrary()->CreateArray(length);
  1492. for (uint32 i = 0; i < length; i++)
  1493. {
  1494. AutoHSTRING item;
  1495. IfFailThrowHr(wga->GetItemAt(dateResult, i, &item));
  1496. patternStrings->SetItem(i, Js::JavascriptString::NewCopySz(wgl->WindowsGetStringRawBuffer(*item, NULL), scriptContext), PropertyOperation_None);
  1497. }
  1498. SetPropertyBuiltInOn(obj, __patternStrings, patternStrings);
  1499. //This parameter tells us whether we are caching it this time around; or just validating pattern strings
  1500. if ((boolean)(JavascriptBoolean::FromVar(args.Values[2])->GetValue()))
  1501. {
  1502. //If timeZone is undefined; then use the standard dateTimeFormatter to format in local time; otherwise use the IDateTimeFormatter2 to format using specified timezone (UTC)
  1503. if (!GetPropertyBuiltInFrom(obj, __timeZone) || JavascriptOperators::IsUndefinedObject(propertyValue))
  1504. {
  1505. cachedFormatter->AddRef();
  1506. obj->SetInternalProperty(Js::InternalPropertyIds::HiddenObject, AutoCOMJSObject::New(scriptContext->GetRecycler(), cachedFormatter), Js::PropertyOperationFlags::PropertyOperation_None, NULL);
  1507. }
  1508. else
  1509. {
  1510. AutoCOMPtr<DateTimeFormatting::IDateTimeFormatter2> tzCachedFormatter;
  1511. IfFailThrowHr(cachedFormatter->QueryInterface(__uuidof(DateTimeFormatting::IDateTimeFormatter2), reinterpret_cast<void**>(&tzCachedFormatter)));
  1512. tzCachedFormatter->AddRef();
  1513. //Set the object as a cache
  1514. obj->SetInternalProperty(Js::InternalPropertyIds::HiddenObject, AutoCOMJSObject::New(scriptContext->GetRecycler(), tzCachedFormatter), Js::PropertyOperationFlags::PropertyOperation_None, NULL);
  1515. }
  1516. }
  1517. return scriptContext->GetLibrary()->GetUndefined();
  1518. #else
  1519. // TODO (doilij): implement INTL_ICU version
  1520. #ifdef INTL_ICU_DEBUG
  1521. Output::Print(_u("EntryIntl_CreateDateTimeFormat > returning null, fallback to JS\n"));
  1522. #endif
  1523. return scriptContext->GetLibrary()->GetNull();
  1524. #endif
  1525. }
  1526. Var IntlEngineInterfaceExtensionObject::EntryIntl_LocaleCompare(RecyclableObject* function, CallInfo callInfo, ...)
  1527. {
  1528. #ifdef INTL_WINGLOB
  1529. AssertOrFailFastMsg(false, "platform.localeCompare should not be called in Intl-WinGlob");
  1530. return nullptr;
  1531. #else
  1532. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1533. INTL_CHECK_ARGS(
  1534. args.Info.Count == 5 &&
  1535. JavascriptString::Is(args[1]) &&
  1536. JavascriptString::Is(args[2]) &&
  1537. DynamicObject::Is(args[3]) &&
  1538. JavascriptBoolean::Is(args[4])
  1539. );
  1540. JavascriptString *left = JavascriptString::UnsafeFromVar(args[1]);
  1541. JavascriptString *right = JavascriptString::UnsafeFromVar(args[2]);
  1542. DynamicObject *state = DynamicObject::UnsafeFromVar(args[3]);
  1543. bool forStringPrototypeLocaleCompare = JavascriptBoolean::UnsafeFromVar(args[4])->GetValue();
  1544. if (forStringPrototypeLocaleCompare)
  1545. {
  1546. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(String_Prototype_localeCompare);
  1547. INTL_TRACE("Calling '%s'.localeCompare('%s', ...)", left->GetSz(), right->GetSz());
  1548. }
  1549. else
  1550. {
  1551. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Collator_Prototype_compare);
  1552. INTL_TRACE("Calling Collator.prototype.compare('%s', '%s')", left->GetSz(), right->GetSz());
  1553. }
  1554. // Below, we lazy-initialize the backing UCollator on the first call to localeCompare
  1555. // On subsequent calls, the UCollator will be cached in state.hiddenObject
  1556. // TODO(jahorto): Make these property IDs sane, so that hiddenObject doesn't have different meanings in different contexts
  1557. Var hiddenObject = nullptr;
  1558. FinalizableUCollator *coll = nullptr;
  1559. UErrorCode status = U_ZERO_ERROR;
  1560. if (state->GetInternalProperty(state, Js::InternalPropertyIds::HiddenObject, &hiddenObject, nullptr, scriptContext))
  1561. {
  1562. coll = reinterpret_cast<FinalizableUCollator *>(hiddenObject);
  1563. INTL_TRACE("Using previously cached UCollator (0x%x)", coll);
  1564. }
  1565. else
  1566. {
  1567. // the object key is locale according to Intl spec, but its more accurately a BCP47 Language Tag, not an ICU LocaleID
  1568. JavascriptString *langtag = AssertStringProperty(state, PropertyIds::locale);
  1569. CollatorSensitivity sensitivity = AssertEnumProperty<CollatorSensitivity>(state, PropertyIds::sensitivityEnum);
  1570. bool ignorePunctuation = AssertBooleanProperty(state, PropertyIds::ignorePunctuation);
  1571. bool numeric = AssertBooleanProperty(state, PropertyIds::numeric);
  1572. CollatorCaseFirst caseFirst = AssertEnumProperty<CollatorCaseFirst>(state, PropertyIds::caseFirstEnum);
  1573. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  1574. LangtagToLocaleID(langtag, localeID);
  1575. coll = FinalizableUCollator::New(scriptContext->GetRecycler(), ucol_open(localeID, &status));
  1576. ICU_ASSERT(status, true);
  1577. // REVIEW(jahorto): Anything that requires a status will no-op if its in a failure state
  1578. // Thus, we can ICU_ASSERT the status once after all of the properties are set for simplicity
  1579. if (sensitivity == CollatorSensitivity::Base)
  1580. {
  1581. ucol_setStrength(*coll, UCOL_PRIMARY);
  1582. }
  1583. else if (sensitivity == CollatorSensitivity::Accent)
  1584. {
  1585. ucol_setStrength(*coll, UCOL_SECONDARY);
  1586. }
  1587. else if (sensitivity == CollatorSensitivity::Case)
  1588. {
  1589. // see "description" for the caseLevel default option: http://userguide.icu-project.org/collation/customization
  1590. ucol_setStrength(*coll, UCOL_PRIMARY);
  1591. ucol_setAttribute(*coll, UCOL_CASE_LEVEL, UCOL_ON, &status);
  1592. }
  1593. else if (sensitivity == CollatorSensitivity::Variant)
  1594. {
  1595. ucol_setStrength(*coll, UCOL_TERTIARY);
  1596. }
  1597. if (ignorePunctuation)
  1598. {
  1599. // see http://userguide.icu-project.org/collation/customization/ignorepunct
  1600. ucol_setAttribute(*coll, UCOL_ALTERNATE_HANDLING, UCOL_SHIFTED, &status);
  1601. }
  1602. if (numeric)
  1603. {
  1604. ucol_setAttribute(*coll, UCOL_NUMERIC_COLLATION, UCOL_ON, &status);
  1605. }
  1606. if (caseFirst == CollatorCaseFirst::Upper)
  1607. {
  1608. ucol_setAttribute(*coll, UCOL_CASE_FIRST, UCOL_UPPER_FIRST, &status);
  1609. }
  1610. else if (caseFirst == CollatorCaseFirst::Lower)
  1611. {
  1612. ucol_setAttribute(*coll, UCOL_CASE_FIRST, UCOL_LOWER_FIRST, &status);
  1613. }
  1614. // Ensure that collator configuration was successfull
  1615. ICU_ASSERT(status, true);
  1616. INTL_TRACE(
  1617. "Caching UCollator (0x%x) with langtag = %s, sensitivity = %d, caseFirst = %d, ignorePunctuation = %d, and numeric = %d",
  1618. coll,
  1619. langtag->GetSz(),
  1620. sensitivity,
  1621. caseFirst,
  1622. ignorePunctuation,
  1623. numeric
  1624. );
  1625. // cache coll for later use (so that the condition that brought us here returns true for future calls)
  1626. state->SetInternalProperty(
  1627. InternalPropertyIds::HiddenObject,
  1628. coll,
  1629. PropertyOperationFlags::PropertyOperation_None,
  1630. nullptr
  1631. );
  1632. }
  1633. // As of ES2015, String.prototype.localeCompare must compare canonically equivalent strings as equal
  1634. BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("EntryIntl_LocaleCompare"));
  1635. const char16 *leftNormalized = nullptr;
  1636. charcount_t leftNormalizedLength = 0;
  1637. if (UnicodeText::IsNormalizedString(UnicodeText::NormalizationForm::C, left->GetSz(), left->GetLength()))
  1638. {
  1639. leftNormalized = left->GetSz();
  1640. leftNormalizedLength = left ->GetLength();
  1641. }
  1642. else
  1643. {
  1644. leftNormalized = left->GetNormalizedString(UnicodeText::NormalizationForm::C, tempAllocator, leftNormalizedLength);
  1645. }
  1646. const char16 *rightNormalized = nullptr;
  1647. charcount_t rightNormalizedLength = 0;
  1648. if (UnicodeText::IsNormalizedString(UnicodeText::NormalizationForm::C, right->GetSz(), right->GetLength()))
  1649. {
  1650. rightNormalized = right->GetSz();
  1651. rightNormalizedLength = right->GetLength();
  1652. }
  1653. else
  1654. {
  1655. rightNormalized = right->GetNormalizedString(UnicodeText::NormalizationForm::C, tempAllocator, rightNormalizedLength);
  1656. }
  1657. static_assert(UCOL_LESS == -1 && UCOL_EQUAL == 0 && UCOL_GREATER == 1, "ucol_strcoll should return values compatible with localeCompare");
  1658. Var ret = JavascriptNumber::ToVar(ucol_strcoll(
  1659. *coll,
  1660. reinterpret_cast<const UChar *>(leftNormalized),
  1661. leftNormalizedLength,
  1662. reinterpret_cast<const UChar *>(rightNormalized),
  1663. rightNormalizedLength
  1664. ), scriptContext);
  1665. END_TEMP_ALLOCATOR(tempAllocator, scriptContext);
  1666. return ret;
  1667. #endif
  1668. }
  1669. #ifdef INTL_WINGLOB
  1670. static DWORD GetCompareStringComparisonFlags(CollatorSensitivity sensitivity, bool ignorePunctuation, bool numeric)
  1671. {
  1672. DWORD flags = 0;
  1673. if (sensitivity == CollatorSensitivity::Base)
  1674. {
  1675. flags |= LINGUISTIC_IGNOREDIACRITIC | LINGUISTIC_IGNORECASE | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH;
  1676. }
  1677. else if (sensitivity == CollatorSensitivity::Accent)
  1678. {
  1679. flags |= LINGUISTIC_IGNORECASE | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH;
  1680. }
  1681. else if (sensitivity == CollatorSensitivity::Case)
  1682. {
  1683. flags |= NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH | LINGUISTIC_IGNOREDIACRITIC;
  1684. }
  1685. else if (sensitivity == CollatorSensitivity::Variant)
  1686. {
  1687. flags |= NORM_LINGUISTIC_CASING;
  1688. }
  1689. if (ignorePunctuation)
  1690. {
  1691. flags |= NORM_IGNORESYMBOLS;
  1692. }
  1693. if (numeric)
  1694. {
  1695. flags |= SORT_DIGITSASNUMBERS;
  1696. }
  1697. return flags;
  1698. }
  1699. #endif
  1700. Var IntlEngineInterfaceExtensionObject::EntryIntl_CompareString(RecyclableObject* function, CallInfo callInfo, ...)
  1701. {
  1702. #ifdef INTL_ICU
  1703. AssertOrFailFastMsg(false, "EntryIntl_CompareString should not be called in Intl-ICU");
  1704. return nullptr;
  1705. #else
  1706. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1707. INTL_CHECK_ARGS(args.Info.Count >= 3 && JavascriptString::Is(args[1]) && JavascriptString::Is(args[2]));
  1708. const char16 *locale = nullptr; // args[3]
  1709. char16 defaultLocale[LOCALE_NAME_MAX_LENGTH] = { 0 };
  1710. CollatorSensitivity sensitivity = CollatorSensitivity::Default; // args[4]
  1711. bool ignorePunctuation = false; // args[5]
  1712. bool numeric = false; // args[6]
  1713. JavascriptString *str1 = JavascriptString::FromVar(args.Values[1]);
  1714. JavascriptString *str2 = JavascriptString::FromVar(args.Values[2]);
  1715. CollatorCaseFirst caseFirst = CollatorCaseFirst::Default; // args[7]
  1716. // we only need to parse arguments 3 through 7 if locale and options are provided
  1717. // see fast path in JavascriptString::EntryLocaleCompare
  1718. if (args.Info.Count > 3)
  1719. {
  1720. if (args.Info.Count < 8)
  1721. {
  1722. JavascriptError::MapAndThrowError(scriptContext, E_INVALIDARG);
  1723. }
  1724. if (!JavascriptOperators::IsUndefinedObject(args.Values[3]) && JavascriptString::Is(args.Values[3]))
  1725. {
  1726. locale = JavascriptString::FromVar(args.Values[3])->GetSz();
  1727. }
  1728. else
  1729. {
  1730. JavascriptError::MapAndThrowError(scriptContext, E_INVALIDARG);
  1731. }
  1732. if (!JavascriptOperators::IsUndefinedObject(args.Values[4]) && TaggedInt::Is(args.Values[4]))
  1733. {
  1734. sensitivity = static_cast<CollatorSensitivity>(TaggedInt::ToUInt16(args.Values[4]));
  1735. }
  1736. if (!JavascriptOperators::IsUndefinedObject(args.Values[5]) && JavascriptBoolean::Is(args.Values[5]))
  1737. {
  1738. ignorePunctuation = (JavascriptBoolean::FromVar(args.Values[5])->GetValue() != 0);
  1739. }
  1740. if (!JavascriptOperators::IsUndefinedObject(args.Values[6]) && JavascriptBoolean::Is(args.Values[6]))
  1741. {
  1742. numeric = (JavascriptBoolean::FromVar(args.Values[6])->GetValue() != 0);
  1743. }
  1744. if (!JavascriptOperators::IsUndefinedObject(args.Values[7]) && TaggedInt::Is(args.Values[7]))
  1745. {
  1746. caseFirst = static_cast<CollatorCaseFirst>(TaggedInt::ToUInt16(args.Values[7]));
  1747. }
  1748. }
  1749. else
  1750. {
  1751. if (GetUserDefaultLocaleName(defaultLocale, _countof(defaultLocale)) != 0)
  1752. {
  1753. locale = defaultLocale;
  1754. }
  1755. else
  1756. {
  1757. JavascriptError::MapAndThrowError(scriptContext, HRESULT_FROM_WIN32(GetLastError()));
  1758. }
  1759. }
  1760. Assert(locale != nullptr);
  1761. Assert((int)sensitivity >= 0 && sensitivity < CollatorSensitivity::Max);
  1762. Assert((int)caseFirst >= 0 && caseFirst < CollatorCaseFirst::Max);
  1763. BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("EntryIntl_CompareString"));
  1764. const char16 *left = nullptr;
  1765. charcount_t leftLen = 0;
  1766. if (UnicodeText::IsNormalizedString(UnicodeText::NormalizationForm::C, str1->GetSz(), str1->GetLength()))
  1767. {
  1768. left = str1->GetSz();
  1769. leftLen = str1->GetLength();
  1770. }
  1771. else
  1772. {
  1773. left = str1->GetNormalizedString(UnicodeText::NormalizationForm::C, tempAllocator, leftLen);
  1774. }
  1775. const char16 *right = nullptr;
  1776. charcount_t rightLen = 0;
  1777. if (UnicodeText::IsNormalizedString(UnicodeText::NormalizationForm::C, str2->GetSz(), str2->GetLength()))
  1778. {
  1779. right = str2->GetSz();
  1780. rightLen = str2->GetLength();
  1781. }
  1782. else
  1783. {
  1784. right = str2->GetNormalizedString(UnicodeText::NormalizationForm::C, tempAllocator, rightLen);
  1785. }
  1786. // CompareStringEx on Windows returns 0 for error, 1 if less, 2 if equal, 3 if greater
  1787. // Default to the strings being equal, because sorting with == causes no change in the order but converges, whereas < would cause an infinite loop.
  1788. int compareResult = 2;
  1789. HRESULT error = S_OK;
  1790. DWORD comparisonFlags = GetCompareStringComparisonFlags(sensitivity, ignorePunctuation, numeric);
  1791. compareResult = CompareStringEx(locale, comparisonFlags, left, leftLen, right, rightLen, NULL, NULL, 0);
  1792. error = HRESULT_FROM_WIN32(GetLastError());
  1793. END_TEMP_ALLOCATOR(tempAllocator, scriptContext);
  1794. if (compareResult == 0)
  1795. {
  1796. JavascriptError::MapAndThrowError(scriptContext, error);
  1797. }
  1798. return JavascriptNumber::ToVar(compareResult - 2, scriptContext);
  1799. #endif
  1800. }
  1801. Var IntlEngineInterfaceExtensionObject::EntryIntl_CurrencyDigits(RecyclableObject* function, CallInfo callInfo, ...)
  1802. {
  1803. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  1804. INTL_CHECK_ARGS(
  1805. args.Info.Count == 2 &&
  1806. JavascriptString::Is(args.Values[1])
  1807. );
  1808. const char16 *currencyCode = JavascriptString::UnsafeFromVar(args.Values[1])->GetSz();
  1809. #if defined(INTL_ICU)
  1810. UErrorCode status = U_ZERO_ERROR;
  1811. ScopedUNumberFormat fmt(unum_open(UNUM_CURRENCY, nullptr, 0, nullptr, nullptr, &status));
  1812. unum_setTextAttribute(fmt, UNUM_CURRENCY_CODE, reinterpret_cast<const UChar *>(currencyCode), -1, &status);
  1813. ICU_ASSERT(status, true);
  1814. int currencyDigits = unum_getAttribute(fmt, UNUM_FRACTION_DIGITS);
  1815. return JavascriptNumber::ToVar(currencyDigits, scriptContext);
  1816. #else
  1817. HRESULT hr;
  1818. AutoCOMPtr<NumberFormatting::ICurrencyFormatter> currencyFormatter(nullptr);
  1819. IfFailThrowHr(GetWindowsGlobalizationAdapter(scriptContext)->CreateCurrencyFormatterCode(scriptContext, currencyCode, &currencyFormatter));
  1820. AutoCOMPtr<NumberFormatting::INumberFormatterOptions> numberFormatterOptions;
  1821. IfFailThrowHr(currencyFormatter->QueryInterface(__uuidof(NumberFormatting::INumberFormatterOptions), reinterpret_cast<void**>(&numberFormatterOptions)));
  1822. Assert(numberFormatterOptions);
  1823. INT32 fractionDigits;
  1824. IfFailThrowHr(numberFormatterOptions->get_FractionDigits(&fractionDigits));
  1825. return JavascriptNumber::ToVar(fractionDigits, scriptContext);
  1826. #endif
  1827. }
  1828. #ifdef INTL_WINGLOB
  1829. //Helper, this just prepares based on fraction and integer format options
  1830. void IntlEngineInterfaceExtensionObject::prepareWithFractionIntegerDigits(ScriptContext* scriptContext, NumberFormatting::INumberRounderOption* rounderOptions,
  1831. NumberFormatting::INumberFormatterOptions* formatterOptions, uint16 minFractionDigits, uint16 maxFractionDigits, uint16 minIntegerDigits)
  1832. {
  1833. HRESULT hr;
  1834. WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext);
  1835. AutoCOMPtr<NumberFormatting::INumberRounder> numberRounder(nullptr);
  1836. AutoCOMPtr<NumberFormatting::IIncrementNumberRounder> incrementNumberRounder(nullptr);
  1837. IfFailThrowHr(wga->CreateIncrementNumberRounder(scriptContext, &numberRounder));
  1838. IfFailThrowHr(numberRounder->QueryInterface(__uuidof(NumberFormatting::IIncrementNumberRounder), reinterpret_cast<void**>(&incrementNumberRounder)));
  1839. Assert(incrementNumberRounder);
  1840. IfFailThrowHr(incrementNumberRounder->put_RoundingAlgorithm(Windows::Globalization::NumberFormatting::RoundingAlgorithm::RoundingAlgorithm_RoundHalfAwayFromZero));
  1841. IfFailThrowHr(incrementNumberRounder->put_Increment(pow(10.0, -maxFractionDigits)));
  1842. IfFailThrowHr(rounderOptions->put_NumberRounder(numberRounder));
  1843. IfFailThrowHr(formatterOptions->put_FractionDigits(minFractionDigits));
  1844. IfFailThrowHr(formatterOptions->put_IntegerDigits(minIntegerDigits));
  1845. }
  1846. //Helper, this just prepares based on significant digits format options
  1847. void IntlEngineInterfaceExtensionObject::prepareWithSignificantDigits(ScriptContext* scriptContext, NumberFormatting::INumberRounderOption* rounderOptions, NumberFormatting::INumberFormatter *numberFormatter,
  1848. NumberFormatting::INumberFormatterOptions* formatterOptions, uint16 minSignificantDigits, uint16 maxSignificantDigits)
  1849. {
  1850. HRESULT hr;
  1851. WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext);
  1852. AutoCOMPtr<NumberFormatting::INumberRounder> numberRounder(nullptr);
  1853. AutoCOMPtr<NumberFormatting::ISignificantDigitsNumberRounder> incrementNumberRounder(nullptr);
  1854. AutoCOMPtr<NumberFormatting::ISignificantDigitsOption> significantDigitsOptions(nullptr);
  1855. IfFailThrowHr(wga->CreateSignificantDigitsRounder(scriptContext, &numberRounder));
  1856. IfFailThrowHr(numberRounder->QueryInterface(__uuidof(NumberFormatting::ISignificantDigitsNumberRounder), reinterpret_cast<void**>(&incrementNumberRounder)));
  1857. Assert(incrementNumberRounder);
  1858. IfFailThrowHr(incrementNumberRounder->put_RoundingAlgorithm(Windows::Globalization::NumberFormatting::RoundingAlgorithm::RoundingAlgorithm_RoundHalfAwayFromZero));
  1859. IfFailThrowHr(incrementNumberRounder->put_SignificantDigits(maxSignificantDigits));
  1860. IfFailThrowHr(rounderOptions->put_NumberRounder(numberRounder));
  1861. IfFailThrowHr(numberFormatter->QueryInterface(__uuidof(NumberFormatting::ISignificantDigitsOption), reinterpret_cast<void**>(&significantDigitsOptions)));
  1862. IfFailThrowHr(significantDigitsOptions->put_SignificantDigits(minSignificantDigits));
  1863. Assert(significantDigitsOptions);
  1864. //Clear minimum fraction digits as in the case of percent 2 is supplied
  1865. IfFailThrowHr(formatterOptions->put_FractionDigits(0));
  1866. }
  1867. #endif
  1868. #ifdef INTL_ICU
  1869. // Rationale for this data structure: ICU reports back a tree of parts where each node in the tree
  1870. // has a type and a width, and there must be at least one node corresponding to each character in the tree
  1871. // (nodes can be wider than one character). Nodes can have children, and the parent-child relationship is
  1872. // that child node represents a more specific type for a given character range than the parent. Since ICU
  1873. // doesn't ever report "literal" parts of strings (like spaces or other extra characters), the root node in
  1874. // the tree will always be the entire width of the string with the type UnsetField, and we later map UnsetField
  1875. // to the "literal" part. Then, for a string like "US$ 1,000", there will be two child nodes, one of type
  1876. // currency with width [0, 3) and one of type integer with width [4, 9). The integer node will have a child
  1877. // of type group and width [6, 7). So the most specific type for characters 0 to 3 is currency, 3 to 4 is unset
  1878. // (literal), 4 to 5 is integer, 5 to 6 is group, and 6 to 9 is integer. This linear scan across the string,
  1879. // determining the part of consecutive spans of characters, is what the NumberFormat.prototype.formatToParts
  1880. // API needs to return to the user.
  1881. //
  1882. // I thought about this a bunch and I didn't like the idea of traversing an actual tree structure to get that
  1883. // information because it felt awkward to encode the "width" of nodes with specific meaning during traveral.
  1884. // So, I came up with an array structure where basically when we are told a part exists from position x to y,
  1885. // we can figure out what type used to apply to that span and update that section of the array with the new type.
  1886. // We skip over sections of the span [x, y) that have a type that doesn't match the start and end because that
  1887. // means we have already gotten a more specific part for that sub-span (for instance if we got a grouping
  1888. // separator before it's parent integer)
  1889. class NumberFormatPartsBuilder
  1890. {
  1891. private:
  1892. double num;
  1893. Field(const char16 *) formatted;
  1894. const charcount_t formattedLength;
  1895. Field(ScriptContext *) sc;
  1896. Field(UNumberFormatFields *) fields;
  1897. static const UNumberFormatFields UnsetField = static_cast<UNumberFormatFields>(0xFFFFFFFF);
  1898. JavascriptString *GetPartTypeString(UNumberFormatFields field)
  1899. {
  1900. JavascriptLibrary *library = sc->GetLibrary();
  1901. // this is outside the switch because MSVC doesn't like that UnsetField is not a valid enum value
  1902. if (field == UnsetField)
  1903. {
  1904. return library->GetIntlLiteralPartString();
  1905. }
  1906. switch (field)
  1907. {
  1908. case UNUM_INTEGER_FIELD:
  1909. {
  1910. if (JavascriptNumber::IsNan(num))
  1911. {
  1912. return library->GetIntlNanPartString();
  1913. }
  1914. else if (!NumberUtilities::IsFinite(num))
  1915. {
  1916. return library->GetIntlInfinityPartString();
  1917. }
  1918. else
  1919. {
  1920. return library->GetIntlIntegerPartString();
  1921. }
  1922. }
  1923. case UNUM_FRACTION_FIELD: return library->GetIntlFractionPartString();
  1924. case UNUM_DECIMAL_SEPARATOR_FIELD: return library->GetIntlDecimalPartString();
  1925. // The following three should only show up if UNUM_SCIENTIFIC is used, which Intl.NumberFormat doesn't currently use
  1926. case UNUM_EXPONENT_SYMBOL_FIELD: AssertOrFailFastMsg(false, "Unexpected exponent symbol field");
  1927. case UNUM_EXPONENT_SIGN_FIELD: AssertOrFailFastMsg(false, "Unexpected exponent sign field");
  1928. case UNUM_EXPONENT_FIELD: AssertOrFailFastMsg(false, "Unexpected exponent field");
  1929. case UNUM_GROUPING_SEPARATOR_FIELD: return library->GetIntlGroupPartString();
  1930. case UNUM_CURRENCY_FIELD: return library->GetIntlCurrencyPartString();
  1931. case UNUM_PERCENT_FIELD: return library->GetIntlPercentPartString();
  1932. // TODO(jahorto): Determine if this would ever be returned and what it would map to
  1933. case UNUM_PERMILL_FIELD: AssertOrFailFastMsg(false, "Unexpected permill field");
  1934. case UNUM_SIGN_FIELD: return num < 0 ? library->GetIntlMinusSignPartString() : library->GetIntlPlusSignPartString();
  1935. // At the ECMA-402 TC39 call for May 2017, it was decided that we should treat unmapped parts as type: "unknown"
  1936. default: return library->GetIntlUnknownPartString();
  1937. }
  1938. }
  1939. public:
  1940. NumberFormatPartsBuilder(double num, const char16 *formatted, charcount_t formattedLength, ScriptContext *scriptContext)
  1941. : num(num)
  1942. , formatted(formatted)
  1943. , formattedLength(formattedLength)
  1944. , sc(scriptContext)
  1945. , fields(RecyclerNewArrayLeaf(sc->GetRecycler(), UNumberFormatFields, formattedLength))
  1946. {
  1947. // this will allow InsertPart to tell what fields have been initialized or not, and will
  1948. // be the value of resulting { type: "literal" } fields in ToPartsArray
  1949. memset(fields, UnsetField, sizeof(UNumberFormatFields) * formattedLength);
  1950. }
  1951. // Note -- ICU reports fields as inclusive start, exclusive end, so thats how we handle the parameters here
  1952. void InsertPart(UNumberFormatFields field, int start, int end)
  1953. {
  1954. AssertOrFailFast(start >= 0);
  1955. AssertOrFailFast(start < end);
  1956. // the asserts above mean the cast to charcount_t is safe
  1957. charcount_t ccStart = static_cast<charcount_t>(start);
  1958. charcount_t ccEnd = static_cast<charcount_t>(end);
  1959. AssertOrFailFast(ccEnd <= formattedLength);
  1960. // Make sure that the part does not overlap/is a strict subset or superset of other fields
  1961. // The part builder could probably be built and tested to handle this structure not being so rigid,
  1962. // but its safer for now to make sure this is true.
  1963. UNumberFormatFields parentStartField = fields[ccStart];
  1964. UNumberFormatFields parentEndField = fields[ccEnd - 1];
  1965. AssertOrFailFast(parentStartField == parentEndField);
  1966. // Actually insert the part now
  1967. // Only overwrite fields in the span that match start and end, since fields that don't match are sub-parts that were already inserted
  1968. for (charcount_t i = ccStart; i < ccEnd; ++i)
  1969. {
  1970. if (fields[i] == parentStartField)
  1971. {
  1972. fields[i] = field;
  1973. }
  1974. }
  1975. }
  1976. JavascriptArray *ToPartsArray()
  1977. {
  1978. JavascriptArray *ret = sc->GetLibrary()->CreateArray(0);
  1979. int retIndex = 0;
  1980. // With the structure of the fields array, creating the resulting parts list to return to JS is simple
  1981. // Map set parts to the corresponding `type` value (see ECMA-402 #sec-partitionnumberpattern), and unset parts to the `literal` type
  1982. for (charcount_t start = 0; start < formattedLength; ++retIndex)
  1983. {
  1984. // 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),
  1985. // since start is adjusted later in the loop to the end of the part
  1986. AssertOrFailFast(start == 0 || fields[start - 1] != fields[start]);
  1987. charcount_t end = start + 1;
  1988. while (end < formattedLength && fields[end] == fields[start])
  1989. {
  1990. ++end;
  1991. }
  1992. JavascriptString *partType = GetPartTypeString(fields[start]);
  1993. JavascriptString *partValue = JavascriptString::NewCopyBuffer(formatted + start, end - start, sc);
  1994. DynamicObject* part = sc->GetLibrary()->CreateObject();
  1995. JavascriptOperators::InitProperty(part, PropertyIds::type, partType);
  1996. JavascriptOperators::InitProperty(part, PropertyIds::value, partValue);
  1997. ret->SetItem(retIndex, part, PropertyOperationFlags::PropertyOperation_None);
  1998. start = end;
  1999. }
  2000. return ret;
  2001. }
  2002. };
  2003. #endif
  2004. Var IntlEngineInterfaceExtensionObject::EntryIntl_FormatNumber(RecyclableObject* function, CallInfo callInfo, ...)
  2005. {
  2006. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2007. #if defined(INTL_ICU)
  2008. INTL_CHECK_ARGS(
  2009. args.Info.Count == 5 &&
  2010. (TaggedInt::Is(args[1]) || JavascriptNumber::Is(args[1])) &&
  2011. DynamicObject::Is(args[2]) &&
  2012. JavascriptBoolean::Is(args[3]) &&
  2013. JavascriptBoolean::Is(args[4])
  2014. );
  2015. double num = JavascriptConversion::ToNumber(args[1], scriptContext);
  2016. DynamicObject *state = DynamicObject::UnsafeFromVar(args[2]);
  2017. bool toParts = JavascriptBoolean::UnsafeFromVar(args[3])->GetValue();
  2018. bool forNumberPrototypeToLocaleString = JavascriptBoolean::UnsafeFromVar(args[4])->GetValue();
  2019. Var cachedFormatter = nullptr; // cached by EntryIntl_CacheNumberFormat
  2020. AssertOrFailFast(state->GetInternalProperty(state, Js::InternalPropertyIds::HiddenObject, &cachedFormatter, NULL, scriptContext));
  2021. if (forNumberPrototypeToLocaleString)
  2022. {
  2023. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Number_Prototype_toLocaleString);
  2024. INTL_TRACE("Calling %f.toLocaleString(...)", num);
  2025. }
  2026. else if (toParts)
  2027. {
  2028. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(NumberFormat_Prototype_formatToParts);
  2029. INTL_TRACE("Calling NumberFormat.prototype.formatToParts(%f)", num);
  2030. }
  2031. else
  2032. {
  2033. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(NumberFormat_Prototype_format);
  2034. INTL_TRACE("Calling NumberFormat.prototype.format(%f)", num);
  2035. }
  2036. auto fmt = static_cast<FinalizableUNumberFormat *>(cachedFormatter);
  2037. char16 *formatted = nullptr;
  2038. int formattedLen = 0;
  2039. if (!toParts)
  2040. {
  2041. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2042. {
  2043. return unum_formatDouble(*fmt, num, buf, bufLen, nullptr, status);
  2044. }, scriptContext->GetRecycler(), &formatted, &formattedLen);
  2045. return JavascriptString::NewWithBuffer(formatted, formattedLen, scriptContext);
  2046. }
  2047. #if defined(ICU_VERSION) && ICU_VERSION >= 61
  2048. UErrorCode status = U_ZERO_ERROR;
  2049. ScopedUFieldPositionIterator fpi(ufieldpositer_open(&status));
  2050. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2051. {
  2052. return unum_formatDoubleForFields(*fmt, num, buf, bufLen, fpi, status);
  2053. }, scriptContext->GetRecycler(), &formatted, &formattedLen);
  2054. NumberFormatPartsBuilder nfpb(num, formatted, formattedLen, scriptContext);
  2055. int partStart = 0;
  2056. int partEnd = 0;
  2057. int i = 0;
  2058. for (int kind = ufieldpositer_next(fpi, &partStart, &partEnd); kind >= 0; kind = ufieldpositer_next(fpi, &partStart, &partEnd), ++i)
  2059. {
  2060. nfpb.InsertPart(static_cast<UNumberFormatFields>(kind), partStart, partEnd);
  2061. }
  2062. return nfpb.ToPartsArray();
  2063. #else
  2064. JavascriptLibrary *library = scriptContext->GetLibrary();
  2065. JavascriptArray *ret = library->CreateArray(1);
  2066. DynamicObject* part = library->CreateObject();
  2067. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2068. {
  2069. return unum_formatDouble(*fmt, num, buf, bufLen, nullptr, status);
  2070. }, scriptContext->GetRecycler(), &formatted, &formattedLen);
  2071. JavascriptOperators::InitProperty(part, PropertyIds::type, library->GetIntlUnknownPartString());
  2072. JavascriptOperators::InitProperty(part, PropertyIds::value, JavascriptString::NewWithBuffer(formatted, formattedLen, scriptContext));
  2073. ret->SetItem(0, part, PropertyOperationFlags::PropertyOperation_None);
  2074. return ret;
  2075. #endif // #if ICU_VERSION >= 61 ... #else
  2076. #else
  2077. INTL_CHECK_ARGS(
  2078. args.Info.Count == 3 &&
  2079. (TaggedInt::Is(args.Values[1]) || JavascriptNumber::Is(args.Values[1])) &&
  2080. DynamicObject::Is(args.Values[2])
  2081. );
  2082. DynamicObject *options = DynamicObject::FromVar(args.Values[2]);
  2083. Var hiddenObject = nullptr;
  2084. AssertOrFailFastMsg(options->GetInternalProperty(options, Js::InternalPropertyIds::HiddenObject, &hiddenObject, NULL, scriptContext),
  2085. "EntryIntl_FormatNumber: Could not retrieve hiddenObject.");
  2086. DelayLoadWindowsGlobalization* wsl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  2087. NumberFormatting::INumberFormatter *numberFormatter;
  2088. numberFormatter = static_cast<NumberFormatting::INumberFormatter *>(((AutoCOMJSObject *)hiddenObject)->GetInstance());
  2089. AutoHSTRING result;
  2090. HRESULT hr;
  2091. if (TaggedInt::Is(args.Values[1]))
  2092. {
  2093. IfFailThrowHr(numberFormatter->FormatInt(TaggedInt::ToInt32(args.Values[1]), &result));
  2094. }
  2095. else
  2096. {
  2097. IfFailThrowHr(numberFormatter->FormatDouble(JavascriptNumber::GetValue(args.Values[1]), &result));
  2098. }
  2099. PCWSTR strBuf = wsl->WindowsGetStringRawBuffer(*result, NULL);
  2100. if (strBuf == nullptr)
  2101. {
  2102. return scriptContext->GetLibrary()->GetUndefined();
  2103. }
  2104. else
  2105. {
  2106. JavascriptStringObject *retVal = scriptContext->GetLibrary()->CreateStringObject(Js::JavascriptString::NewCopySz(strBuf, scriptContext));
  2107. return retVal;
  2108. }
  2109. #endif
  2110. }
  2111. #ifdef INTL_ICU
  2112. // Implementation of ECMA 262 #sec-timeclip
  2113. // REVIEW(jahorto): Where is a better place for this function? JavascriptDate? DateUtilities? JavascriptConversion?
  2114. static double TimeClip(Var x)
  2115. {
  2116. double time = 0.0;
  2117. if (TaggedInt::Is(x))
  2118. {
  2119. time = TaggedInt::ToDouble(x);
  2120. }
  2121. else
  2122. {
  2123. AssertOrFailFast(JavascriptNumber::Is(x));
  2124. time = JavascriptNumber::GetValue(x);
  2125. // Only perform steps 1, 3, and 4 if the input was not a TaggedInt, since TaggedInts cant be infinite or -0
  2126. if (!NumberUtilities::IsFinite(time))
  2127. {
  2128. return NumberConstants::NaN;
  2129. }
  2130. // This performs both steps 3 and 4
  2131. time = JavascriptConversion::ToInteger(time);
  2132. }
  2133. // Step 2: If abs(time) > 8.64e15, return NaN.
  2134. if (Math::Abs(time) > 8.64e15)
  2135. {
  2136. return NumberConstants::NaN;
  2137. }
  2138. return time;
  2139. }
  2140. static void AddPartToPartsArray(ScriptContext *scriptContext, JavascriptArray *arr, int arrIndex, const char16 *src, int start, int end, JavascriptString *partType)
  2141. {
  2142. JavascriptString *partValue = JavascriptString::NewCopyBuffer(
  2143. src + start,
  2144. end - start,
  2145. scriptContext
  2146. );
  2147. DynamicObject* part = scriptContext->GetLibrary()->CreateObject();
  2148. JavascriptOperators::InitProperty(part, PropertyIds::type, partType);
  2149. JavascriptOperators::InitProperty(part, PropertyIds::value, partValue);
  2150. arr->SetItem(arrIndex, part, PropertyOperationFlags::PropertyOperation_None);
  2151. }
  2152. #endif
  2153. // For Windows Globalization, this function takes a number (date) and a state object and returns a string
  2154. // For ICU, this function takes a state object, number, and boolean for whether or not to format to parts.
  2155. // if args.Values[3] ~= true, an array of objects is returned; else, a string is returned
  2156. Var IntlEngineInterfaceExtensionObject::EntryIntl_FormatDateTime(RecyclableObject* function, CallInfo callInfo, ...)
  2157. {
  2158. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2159. #ifdef INTL_WINGLOB
  2160. if (args.Info.Count < 3 || !(TaggedInt::Is(args.Values[1]) || JavascriptNumber::Is(args.Values[1])) || !DynamicObject::Is(args.Values[2]))
  2161. {
  2162. return scriptContext->GetLibrary()->GetUndefined();
  2163. }
  2164. Windows::Foundation::DateTime winDate;
  2165. HRESULT hr;
  2166. if (TaggedInt::Is(args.Values[1]))
  2167. {
  2168. hr = Js::DateUtilities::ES5DateToWinRTDate(TaggedInt::ToInt32(args.Values[1]), &(winDate.UniversalTime));
  2169. }
  2170. else
  2171. {
  2172. hr = Js::DateUtilities::ES5DateToWinRTDate(JavascriptNumber::GetValue(args.Values[1]), &(winDate.UniversalTime));
  2173. }
  2174. if (FAILED(hr))
  2175. {
  2176. HandleOOMSOEHR(hr);
  2177. // If conversion failed, double value is outside the range of WinRT DateTime
  2178. Js::JavascriptError::ThrowRangeError(scriptContext, JSERR_OutOfDateTimeRange);
  2179. }
  2180. DynamicObject* obj = DynamicObject::FromVar(args.Values[2]);
  2181. Var hiddenObject = nullptr;
  2182. AssertOrFailFastMsg(obj->GetInternalProperty(obj, Js::InternalPropertyIds::HiddenObject, &hiddenObject, NULL, scriptContext),
  2183. "EntryIntl_FormatDateTime: Could not retrieve hiddenObject.");
  2184. //We are going to perform the same check for timeZone as when caching the formatter.
  2185. Var propertyValue = nullptr;
  2186. AutoHSTRING result;
  2187. //If timeZone is undefined; then use the standard dateTimeFormatter to format in local time; otherwise use the IDateTimeFormatter2 to format using specified timezone (UTC)
  2188. if (!GetPropertyBuiltInFrom(obj, __timeZone) || JavascriptOperators::IsUndefinedObject(propertyValue))
  2189. {
  2190. DateTimeFormatting::IDateTimeFormatter *formatter = static_cast<DateTimeFormatting::IDateTimeFormatter *>(((AutoCOMJSObject *)hiddenObject)->GetInstance());
  2191. Assert(formatter);
  2192. IfFailThrowHr(formatter->Format(winDate, &result));
  2193. }
  2194. else
  2195. {
  2196. DateTimeFormatting::IDateTimeFormatter2 *formatter = static_cast<DateTimeFormatting::IDateTimeFormatter2 *>(((AutoCOMJSObject *)hiddenObject)->GetInstance());
  2197. Assert(formatter);
  2198. HSTRING timeZone;
  2199. HSTRING_HEADER timeZoneHeader;
  2200. // IsValidTimeZone() has already verified that this is JavascriptString.
  2201. JavascriptString* userDefinedTimeZoneId = JavascriptString::FromVar(propertyValue);
  2202. IfFailThrowHr(WindowsCreateStringReference(userDefinedTimeZoneId->GetSz(), userDefinedTimeZoneId->GetLength(), &timeZoneHeader, &timeZone));
  2203. Assert(timeZone);
  2204. IfFailThrowHr(formatter->FormatUsingTimeZone(winDate, timeZone, &result));
  2205. }
  2206. PCWSTR strBuf = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary()->WindowsGetStringRawBuffer(*result, NULL);
  2207. return Js::JavascriptString::NewCopySz(strBuf, scriptContext);
  2208. #else
  2209. // This function vaguely implements ECMA 402 #sec-partitiondatetimepattern
  2210. INTL_CHECK_ARGS(
  2211. args.Info.Count == 5 &&
  2212. DynamicObject::Is(args[1]) &&
  2213. (TaggedInt::Is(args[2]) || JavascriptNumber::Is(args[2])) &&
  2214. JavascriptBoolean::Is(args[3]) &&
  2215. JavascriptBoolean::Is(args[4])
  2216. );
  2217. DynamicObject *state = DynamicObject::UnsafeFromVar(args[1]);
  2218. bool toParts = Js::JavascriptBoolean::UnsafeFromVar(args[3])->GetValue();
  2219. // 1. Let x be TimeClip(x)
  2220. // 2. If x is NaN, throw a RangeError exception
  2221. double date = TimeClip(args[2]);
  2222. if (JavascriptNumber::IsNan(date))
  2223. {
  2224. JavascriptError::ThrowRangeError(scriptContext, JSERR_InvalidDate);
  2225. }
  2226. bool forDatePrototypeToLocaleString = JavascriptBoolean::UnsafeFromVar(args[4])->GetValue();
  2227. if (forDatePrototypeToLocaleString)
  2228. {
  2229. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Date_Prototype_toLocaleString);
  2230. INTL_TRACE("Calling new Date(%f).toLocaleString(...)", date);
  2231. }
  2232. else if (toParts)
  2233. {
  2234. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(DateTimeFormat_Prototype_formatToParts);
  2235. INTL_TRACE("Calling DateTimeFormat.prototype.formatToParts(new Date(%f))", date);
  2236. }
  2237. else
  2238. {
  2239. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(DateTimeFormat_Prototype_format);
  2240. INTL_TRACE("Calling DateTimeFormat.prototype.format(new Date(%f))", date);
  2241. }
  2242. // Below, we lazy-initialize the backing UDateFormat on the first call to format{ToParts}
  2243. // On subsequent calls, the UDateFormat will be cached in state.hiddenObject
  2244. // TODO(jahorto): Make these property IDs sane, so that hiddenObject doesn't have different meanings in different contexts
  2245. Var hiddenObject = nullptr;
  2246. FinalizableUDateFormat *dtf = nullptr;
  2247. UErrorCode status = U_ZERO_ERROR;
  2248. if (state->GetInternalProperty(state, Js::InternalPropertyIds::HiddenObject, &hiddenObject, nullptr, scriptContext))
  2249. {
  2250. dtf = reinterpret_cast<FinalizableUDateFormat *>(hiddenObject);
  2251. INTL_TRACE("Using previously cached UDateFormat (0x%x)", dtf);
  2252. }
  2253. else
  2254. {
  2255. JavascriptString *langtag = AssertStringProperty(state, PropertyIds::locale);
  2256. JavascriptString *timeZone = AssertStringProperty(state, PropertyIds::timeZone);
  2257. JavascriptString *pattern = AssertStringProperty(state, PropertyIds::pattern);
  2258. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  2259. LangtagToLocaleID(langtag, localeID);
  2260. dtf = FinalizableUDateFormat::New(scriptContext->GetRecycler(), udat_open(
  2261. UDAT_PATTERN,
  2262. UDAT_PATTERN,
  2263. localeID,
  2264. reinterpret_cast<const UChar *>(timeZone->GetSz()),
  2265. timeZone->GetLength(),
  2266. reinterpret_cast<const UChar *>(pattern->GetSz()),
  2267. pattern->GetLength(),
  2268. &status
  2269. ));
  2270. ICU_ASSERT(status, true);
  2271. // DateTimeFormat is expected to use the "proleptic Gregorian calendar", which means that the Julian calendar should never be used.
  2272. // To accomplish this, we can set the switchover date between julian/gregorian
  2273. // to the ECMAScript beginning of time, which is -8.64e15 according to ecma262 #sec-time-values-and-time-range
  2274. UCalendar *cal = const_cast<UCalendar *>(udat_getCalendar(*dtf));
  2275. ucal_setGregorianChange(cal, -8.64e15, &status);
  2276. // status can be U_UNSUPPORTED_ERROR if the calendar isn't gregorian, which
  2277. // there does not seem to be a way to check for ahead of time in the C API
  2278. AssertOrFailFastMsg(U_SUCCESS(status) || status == U_UNSUPPORTED_ERROR, ICU_ERRORMESSAGE(status));
  2279. // If we passed the previous check, we should reset the status to U_ZERO_ERROR (in case it was U_UNSUPPORTED_ERROR)
  2280. status = U_ZERO_ERROR;
  2281. INTL_TRACE("Caching new UDateFormat (0x%x) with langtag=%s, pattern=%s, timezone=%s", dtf, langtag->GetSz(), pattern->GetSz(), timeZone->GetSz());
  2282. // cache dtf for later use (so that the condition that brought us here returns true for future calls)
  2283. state->SetInternalProperty(
  2284. InternalPropertyIds::HiddenObject,
  2285. dtf,
  2286. PropertyOperationFlags::PropertyOperation_None,
  2287. nullptr
  2288. );
  2289. }
  2290. // We intentionally special-case the following two calls to EnsureBuffer to allow zero-length strings.
  2291. // See comment in GetPatternForSkeleton.
  2292. char16 *formatted = nullptr;
  2293. int formattedLen = 0;
  2294. if (!toParts)
  2295. {
  2296. // if we aren't formatting to parts, we simply want to call udat_format with retry
  2297. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2298. {
  2299. return udat_format(*dtf, date, buf, bufLen, nullptr, status);
  2300. }, scriptContext->GetRecycler(), &formatted, &formattedLen, /* allowZeroLengthStrings */ true);
  2301. return JavascriptString::NewWithBuffer(formatted, formattedLen, scriptContext);
  2302. }
  2303. // The rest of this function most closely corresponds to ECMA 402 #sec-partitiondatetimepattern
  2304. ScopedUFieldPositionIterator fpi(ufieldpositer_open(&status));
  2305. ICU_ASSERT(status, true);
  2306. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2307. {
  2308. return udat_formatForFields(*dtf, date, buf, bufLen, fpi, status);
  2309. }, scriptContext->GetRecycler(), &formatted, &formattedLen, /* allowZeroLengthStrings */ true);
  2310. JavascriptLibrary *library = scriptContext->GetLibrary();
  2311. JavascriptArray* ret = library->CreateArray(0);
  2312. int partStart = 0;
  2313. int partEnd = 0;
  2314. int lastPartEnd = 0;
  2315. int i = 0;
  2316. for (
  2317. int kind = ufieldpositer_next(fpi, &partStart, &partEnd);
  2318. kind >= 0;
  2319. kind = ufieldpositer_next(fpi, &partStart, &partEnd), ++i
  2320. )
  2321. {
  2322. Assert(partStart < partEnd && partEnd <= formattedLen);
  2323. JavascriptString *typeString = nullptr;
  2324. UDateFormatField fieldKind = (UDateFormatField)kind;
  2325. switch (fieldKind)
  2326. {
  2327. case UDAT_ERA_FIELD:
  2328. typeString = library->GetIntlEraPartString(); break;
  2329. case UDAT_YEAR_FIELD:
  2330. case UDAT_EXTENDED_YEAR_FIELD:
  2331. case UDAT_YEAR_NAME_FIELD:
  2332. typeString = library->GetIntlYearPartString(); break;
  2333. case UDAT_MONTH_FIELD:
  2334. case UDAT_STANDALONE_MONTH_FIELD:
  2335. typeString = library->GetIntlMonthPartString(); break;
  2336. case UDAT_DATE_FIELD:
  2337. typeString = library->GetIntlDayPartString(); break;
  2338. case UDAT_HOUR_OF_DAY1_FIELD:
  2339. case UDAT_HOUR_OF_DAY0_FIELD:
  2340. case UDAT_HOUR1_FIELD:
  2341. case UDAT_HOUR0_FIELD:
  2342. typeString = library->GetIntlHourPartString(); break;
  2343. case UDAT_MINUTE_FIELD:
  2344. typeString = library->GetIntlMinutePartString(); break;
  2345. case UDAT_SECOND_FIELD:
  2346. typeString = library->GetIntlSecondPartString(); break;
  2347. case UDAT_DAY_OF_WEEK_FIELD:
  2348. case UDAT_STANDALONE_DAY_FIELD:
  2349. case UDAT_DOW_LOCAL_FIELD:
  2350. typeString = library->GetIntlWeekdayPartString(); break;
  2351. case UDAT_AM_PM_FIELD:
  2352. typeString = library->GetIntlDayPeriodPartString(); break;
  2353. case UDAT_TIMEZONE_FIELD:
  2354. case UDAT_TIMEZONE_RFC_FIELD:
  2355. case UDAT_TIMEZONE_GENERIC_FIELD:
  2356. case UDAT_TIMEZONE_SPECIAL_FIELD:
  2357. case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
  2358. case UDAT_TIMEZONE_ISO_FIELD:
  2359. case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
  2360. typeString = library->GetIntlTimeZoneNamePartString(); break;
  2361. #if defined(ICU_VERSION) && ICU_VERSION == 55
  2362. case UDAT_TIME_SEPARATOR_FIELD:
  2363. // ICU 55 (Ubuntu 16.04 system default) has the ":" in "5:23 PM" as a special field
  2364. // Intl should just treat this as a literal
  2365. typeString = library->GetIntlLiteralPartString(); break;
  2366. #endif
  2367. default:
  2368. typeString = library->GetIntlUnknownPartString(); break;
  2369. }
  2370. if (partStart > lastPartEnd)
  2371. {
  2372. // formatForFields does not report literal fields directly, so we have to detect them
  2373. // by seeing if the current part starts after the previous one ended
  2374. AddPartToPartsArray(scriptContext, ret, i, formatted, lastPartEnd, partStart, library->GetIntlLiteralPartString());
  2375. i += 1;
  2376. }
  2377. AddPartToPartsArray(scriptContext, ret, i, formatted, partStart, partEnd, typeString);
  2378. lastPartEnd = partEnd;
  2379. }
  2380. // Sometimes, there can be a literal at the end of the string, such as when formatting just the year in
  2381. // the chinese calendar, where the pattern string will be `r(U)`. The trailing `)` will be a literal
  2382. if (lastPartEnd != formattedLen)
  2383. {
  2384. AssertOrFailFast(lastPartEnd < formattedLen);
  2385. // `i` was incremented by the consequence of the last iteration of the for loop
  2386. AddPartToPartsArray(scriptContext, ret, i, formatted, lastPartEnd, formattedLen, library->GetIntlLiteralPartString());
  2387. }
  2388. return ret;
  2389. #endif
  2390. }
  2391. Var IntlEngineInterfaceExtensionObject::EntryIntl_GetPatternForSkeleton(RecyclableObject *function, CallInfo callInfo, ...)
  2392. {
  2393. #ifdef INTL_ICU
  2394. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2395. INTL_CHECK_ARGS(args.Info.Count == 3 && JavascriptString::Is(args.Values[1]) && JavascriptString::Is(args.Values[2]));
  2396. JavascriptString *langtag = JavascriptString::UnsafeFromVar(args.Values[1]);
  2397. JavascriptString *skeleton = JavascriptString::UnsafeFromVar(args.Values[2]);
  2398. UErrorCode status = U_ZERO_ERROR;
  2399. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  2400. LangtagToLocaleID(langtag, localeID);
  2401. // See https://github.com/tc39/ecma402/issues/225
  2402. // When picking a format, we should be using the locale data of the basename of the resolved locale,
  2403. // compared to when we actually format the date using the format string, where we use the full locale including extensions
  2404. //
  2405. // ECMA 402 #sec-initializedatetimeformat
  2406. // 10: Let localeData be %DateTimeFormat%.[[LocaleData]].
  2407. // 11: Let r be ResolveLocale( %DateTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]], localeData).
  2408. // 16: Let dataLocale be r.[[dataLocale]].
  2409. // 23: Let dataLocaleData be localeData.[[<dataLocale>]].
  2410. // 24: Let formats be dataLocaleData.[[formats]].
  2411. char baseLocaleID[ULOC_FULLNAME_CAPACITY] = { 0 };
  2412. int baseLocaleIDLength = uloc_getBaseName(localeID, baseLocaleID, _countof(baseLocaleID), &status);
  2413. ICU_ASSERT(status, baseLocaleIDLength > 0 && baseLocaleIDLength < ULOC_FULLNAME_CAPACITY);
  2414. INTL_TRACE("Converted input langtag '%s' to base locale ID '%S' for pattern generation", langtag->GetSz(), baseLocaleID);
  2415. ScopedUDateTimePatternGenerator dtpg(udatpg_open(baseLocaleID, &status));
  2416. ICU_ASSERT(status, true);
  2417. char16 *formatted = nullptr;
  2418. int formattedLen = 0;
  2419. // OS#17513493 (OSS-Fuzz 7950): It is possible for the skeleton to be a zero-length string
  2420. // because [[Get]] operations are performed on the options object twice, according to spec.
  2421. // Follow-up spec discussion here: https://github.com/tc39/ecma402/issues/237.
  2422. // We need to special-case this because calling udatpg_getBestPatternWithOptions on an empty skeleton
  2423. // will produce an empty pattern, which causes an assert in EnsureBuffer by default.
  2424. // As a result, we pass a final optional parameter to EnsureBuffer to say that zero-length results are OK.
  2425. // TODO(jahorto): re-visit this workaround and the one in FormatDateTime upon resolution of the spec issue.
  2426. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2427. {
  2428. return udatpg_getBestPatternWithOptions(
  2429. dtpg,
  2430. reinterpret_cast<const UChar *>(skeleton->GetSz()),
  2431. skeleton->GetLength(),
  2432. UDATPG_MATCH_ALL_FIELDS_LENGTH,
  2433. buf,
  2434. bufLen,
  2435. status
  2436. );
  2437. }, scriptContext->GetRecycler(), &formatted, &formattedLen, /* allowZeroLengthStrings */ true);
  2438. INTL_TRACE("Best pattern '%s' will be used for skeleton '%s' and langtag '%s'", formatted, skeleton->GetSz(), langtag->GetSz());
  2439. return JavascriptString::NewWithBuffer(formatted, formattedLen, scriptContext);
  2440. #else
  2441. AssertOrFailFastMsg(false, "GetPatternForSkeleton is not implemented outside of ICU");
  2442. return nullptr;
  2443. #endif
  2444. }
  2445. // given a timezone name as an argument, this will return a canonicalized version of that name, or undefined if the timezone is invalid
  2446. Var IntlEngineInterfaceExtensionObject::EntryIntl_ValidateAndCanonicalizeTimeZone(RecyclableObject* function, CallInfo callInfo, ...)
  2447. {
  2448. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2449. INTL_CHECK_ARGS(args.Info.Count == 2 && JavascriptString::Is(args.Values[1]));
  2450. JavascriptString *tz = JavascriptString::FromVar(args.Values[1]);
  2451. #ifdef INTL_WINGLOB
  2452. AutoHSTRING canonicalizedTimeZone;
  2453. boolean isValidTimeZone = GetWindowsGlobalizationAdapter(scriptContext)->ValidateAndCanonicalizeTimeZone(scriptContext, tz->GetSz(), &canonicalizedTimeZone);
  2454. if (isValidTimeZone)
  2455. {
  2456. DelayLoadWindowsGlobalization* wsl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  2457. PCWSTR strBuf = wsl->WindowsGetStringRawBuffer(*canonicalizedTimeZone, NULL);
  2458. return Js::JavascriptString::NewCopySz(strBuf, scriptContext);
  2459. }
  2460. else
  2461. {
  2462. return scriptContext->GetLibrary()->GetUndefined();
  2463. }
  2464. #else
  2465. UErrorCode status = U_ZERO_ERROR;
  2466. // TODO(jahorto): Is this the list of timeZones that we want? How is this different from
  2467. // the other two enum values or ucal_openTimeZones?
  2468. ScopedUEnumeration available(ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, nullptr, nullptr, &status));
  2469. int availableLength = uenum_count(available, &status);
  2470. ICU_ASSERT(status, availableLength > 0);
  2471. charcount_t matchLen = 0;
  2472. UChar match[100] = { 0 };
  2473. for (int a = 0; a < availableLength; ++a)
  2474. {
  2475. int curLen = -1;
  2476. const UChar *cur = uenum_unext(available, &curLen, &status);
  2477. ICU_ASSERT(status, true);
  2478. if (curLen == 0)
  2479. {
  2480. // OS#17175014: in rare cases, ICU will return U_ZERO_ERROR and a valid `cur` string but a length of 0
  2481. // This only happens in an OOM during uenum_(u)next
  2482. // Tracked by ICU: http://bugs.icu-project.org/trac/ticket/13739
  2483. Throw::OutOfMemory();
  2484. }
  2485. AssertOrFailFast(curLen > 0);
  2486. if (_wcsicmp(reinterpret_cast<const char16 *>(cur), tz->GetSz()) == 0)
  2487. {
  2488. ucal_getCanonicalTimeZoneID(cur, curLen, match, _countof(match), nullptr, &status);
  2489. ICU_ASSERT(status, true);
  2490. size_t len = wcslen(reinterpret_cast<const char16 *>(match));
  2491. AssertMsg(len < MaxCharCount, "Returned canonicalized timezone is far too long");
  2492. matchLen = static_cast<charcount_t>(len);
  2493. break;
  2494. }
  2495. }
  2496. if (matchLen == 0)
  2497. {
  2498. return scriptContext->GetLibrary()->GetUndefined();
  2499. }
  2500. return JavascriptString::NewCopyBuffer(reinterpret_cast<const char16 *>(match), matchLen, scriptContext);
  2501. #endif
  2502. }
  2503. // returns the current system time zone
  2504. Var IntlEngineInterfaceExtensionObject::EntryIntl_GetDefaultTimeZone(RecyclableObject* function, CallInfo callInfo, ...)
  2505. {
  2506. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2507. #ifdef INTL_WINGLOB
  2508. WindowsGlobalizationAdapter* wga = GetWindowsGlobalizationAdapter(scriptContext);
  2509. DelayLoadWindowsGlobalization* wsl = scriptContext->GetThreadContext()->GetWindowsGlobalizationLibrary();
  2510. AutoHSTRING str;
  2511. HRESULT hr;
  2512. if (FAILED(hr = wga->GetDefaultTimeZoneId(scriptContext, &str)))
  2513. {
  2514. HandleOOMSOEHR(hr);
  2515. //If we can't get default timeZone, return undefined.
  2516. return scriptContext->GetLibrary()->GetUndefined();
  2517. }
  2518. PCWSTR strBuf = wsl->WindowsGetStringRawBuffer(*str, NULL);
  2519. return Js::JavascriptString::NewCopySz(strBuf, scriptContext);
  2520. #else
  2521. int timeZoneLen = 0;
  2522. char16 *timeZone = nullptr;
  2523. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2524. {
  2525. return ucal_getDefaultTimeZone(buf, bufLen, status);
  2526. }, scriptContext->GetRecycler(), &timeZone, &timeZoneLen);
  2527. return JavascriptString::NewWithBuffer(timeZone, timeZoneLen, scriptContext);
  2528. #endif
  2529. }
  2530. #ifdef INTL_ICU
  2531. static FinalizableUPluralRules *GetOrCreatePluralRulesCache(DynamicObject *stateObject, ScriptContext *scriptContext)
  2532. {
  2533. Var hiddenObject = nullptr;
  2534. FinalizableUPluralRules *pr = nullptr;
  2535. if (stateObject->GetInternalProperty(stateObject, InternalPropertyIds::HiddenObject, &hiddenObject, nullptr, scriptContext))
  2536. {
  2537. pr = reinterpret_cast<FinalizableUPluralRules *>(hiddenObject);
  2538. INTL_TRACE("Using previously cached UPluralRules (0x%x)", pr);
  2539. }
  2540. else
  2541. {
  2542. UErrorCode status = U_ZERO_ERROR;
  2543. JavascriptString *langtag = AssertStringProperty(stateObject, PropertyIds::locale);
  2544. JavascriptString *type = AssertStringProperty(stateObject, PropertyIds::type);
  2545. UPluralType prType = UPLURAL_TYPE_CARDINAL;
  2546. if (wcscmp(type->GetSz(), _u("ordinal")) == 0)
  2547. {
  2548. prType = UPLURAL_TYPE_ORDINAL;
  2549. }
  2550. else
  2551. {
  2552. AssertOrFailFast(wcscmp(type->GetSz(), _u("cardinal")) == 0);
  2553. }
  2554. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  2555. LangtagToLocaleID(langtag, localeID);
  2556. pr = FinalizableUPluralRules::New(scriptContext->GetRecycler(), uplrules_openForType(localeID, prType, &status));
  2557. ICU_ASSERT(status, true);
  2558. INTL_TRACE("Caching UPluralRules object (0x%x) with langtag %s and type %s", langtag->GetSz(), type->GetSz());
  2559. stateObject->SetInternalProperty(InternalPropertyIds::HiddenObject, pr, PropertyOperationFlags::PropertyOperation_None, nullptr);
  2560. }
  2561. return pr;
  2562. }
  2563. #endif
  2564. // This method implements Step 13 and 14 of ECMA 402 #sec-initializepluralrules
  2565. Var IntlEngineInterfaceExtensionObject::EntryIntl_PluralRulesKeywords(RecyclableObject *function, CallInfo callInfo, ...)
  2566. {
  2567. #ifdef INTL_ICU
  2568. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2569. INTL_CHECK_ARGS(args.Info.Count == 2 && DynamicObject::Is(args[1]));
  2570. JavascriptArray *ret = scriptContext->GetLibrary()->CreateArray(0);
  2571. // uplrules_getKeywords is only stable since ICU 61.
  2572. // For ICU < 61, we can fake it by creating an array of ["other"], which
  2573. // uplrules_getKeywords is guaranteed to return at minimum.
  2574. // This array is only used in resolved options, so the majority of the functionality can remain (namely, select() still works)
  2575. #if defined(ICU_VERSION) && ICU_VERSION >= 61
  2576. DynamicObject *state = DynamicObject::UnsafeFromVar(args[1]);
  2577. FinalizableUPluralRules *pr = GetOrCreatePluralRulesCache(state, scriptContext);
  2578. UErrorCode status = U_ZERO_ERROR;
  2579. ScopedUEnumeration keywords(uplrules_getKeywords(*pr, &status));
  2580. ICU_ASSERT(status, true);
  2581. ForEachUEnumeration16(keywords, [&](int index, const char16 *kw, charcount_t kwLength)
  2582. {
  2583. ret->SetItem(index, JavascriptString::NewCopyBuffer(kw, kwLength, scriptContext), PropertyOperation_None);
  2584. });
  2585. #else
  2586. ret->SetItem(0, scriptContext->GetLibrary()->GetIntlPluralRulesOtherString(), PropertyOperation_None);
  2587. #endif
  2588. return ret;
  2589. #else
  2590. AssertOrFailFastMsg(false, "Intl-WinGlob should not be using PluralRulesKeywords");
  2591. return nullptr;
  2592. #endif
  2593. }
  2594. // This method roughly implements ECMA 402 #sec-pluralruleselect, except ICU takes care of handling the operand logic for us
  2595. Var IntlEngineInterfaceExtensionObject::EntryIntl_PluralRulesSelect(RecyclableObject *function, CallInfo callInfo, ...)
  2596. {
  2597. #ifdef INTL_ICU
  2598. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2599. INTL_CHECK_ARGS(args.Info.Count == 3 && DynamicObject::Is(args[1]));
  2600. DynamicObject *state = DynamicObject::UnsafeFromVar(args[1]);
  2601. double n = 0.0;
  2602. if (TaggedInt::Is(args[2]))
  2603. {
  2604. n = TaggedInt::ToDouble(args[2]);
  2605. }
  2606. else
  2607. {
  2608. AssertOrFailFast(JavascriptNumber::Is(args[2]));
  2609. n = JavascriptNumber::GetValue(args[2]);
  2610. }
  2611. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(PluralRules_Prototype_select);
  2612. INTL_TRACE("Calling PluralRules.prototype.select(%f)", n);
  2613. FinalizableUPluralRules *pr = GetOrCreatePluralRulesCache(state, scriptContext);
  2614. // ICU has an internal API, uplrules_selectWithFormat, that is equivalent to uplrules_select but will respect digit options of the passed UNumberFormat.
  2615. // Since its an internal API, we can't use it -- however, we can work around it by creating a UNumberFormat with provided digit options,
  2616. // formatting the requested number to a string, and then converting the string back to a double which we can pass to uplrules_select.
  2617. // This workaround was suggested during the May 2018 ECMA-402 discussion.
  2618. // TODO(jahorto): investigate caching this UNumberFormat on the state as well. This is currently not possible because we are using InternalProperyIds::HiddenObject
  2619. // for all ICU object caching, but once we move to better names for the cache property IDs, we can cache both the UNumberFormat as well as the UPluralRules.
  2620. char localeID[ULOC_FULLNAME_CAPACITY] = { 0 };
  2621. LangtagToLocaleID(AssertStringProperty(state, PropertyIds::locale), localeID);
  2622. UErrorCode status = U_ZERO_ERROR;
  2623. FinalizableUNumberFormat *fmt = FinalizableUNumberFormat::New(scriptContext->GetRecycler(), unum_open(UNUM_DECIMAL, nullptr, 0, localeID, nullptr, &status));
  2624. SetUNumberFormatDigitOptions(*fmt, state);
  2625. char16 *formattedN = nullptr;
  2626. int formattedNLength = 0;
  2627. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2628. {
  2629. return unum_formatDouble(*fmt, n, buf, bufLen, nullptr, status);
  2630. }, scriptContext->GetRecycler(), &formattedN, &formattedNLength);
  2631. double nWithOptions = unum_parseDouble(*fmt, reinterpret_cast<UChar *>(formattedN), formattedNLength, nullptr, &status);
  2632. double roundtripDiff = n - nWithOptions;
  2633. ICU_ASSERT(status, roundtripDiff <= 1.0 && roundtripDiff >= -1.0);
  2634. char16 *selected = nullptr;
  2635. int selectedLength = 0;
  2636. EnsureBuffer([&](UChar *buf, int bufLen, UErrorCode *status)
  2637. {
  2638. return uplrules_select(*pr, nWithOptions, buf, bufLen, status);
  2639. }, scriptContext->GetRecycler(), &selected, &selectedLength);
  2640. return JavascriptString::NewWithBuffer(selected, static_cast<charcount_t>(selectedLength), scriptContext);
  2641. #else
  2642. AssertOrFailFastMsg(false, "Intl-WinGlob should not be using PluralRulesSelect");
  2643. return nullptr;
  2644. #endif
  2645. }
  2646. /*
  2647. * This function registers built in functions when Intl initializes.
  2648. * Call with (Function : toRegister, integer : id)
  2649. * ID Mappings:
  2650. * - 0 for Date.prototype.toLocaleString
  2651. * - 1 for Date.prototype.toLocaleDateString
  2652. * - 2 for Date.prototype.toLocaleTimeString
  2653. * - 3 for Number.prototype.toLocaleString
  2654. * - 4 for String.prototype.localeCompare
  2655. */
  2656. Var IntlEngineInterfaceExtensionObject::EntryIntl_RegisterBuiltInFunction(RecyclableObject* function, CallInfo callInfo, ...)
  2657. {
  2658. // Don't put this in a header or add it to the namespace even in this file. Keep it to the minimum scope needed.
  2659. enum class IntlBuiltInFunctionID : int32 {
  2660. Min = 0,
  2661. DateToLocaleString = Min,
  2662. DateToLocaleDateString,
  2663. DateToLocaleTimeString,
  2664. NumberToLocaleString,
  2665. StringLocaleCompare,
  2666. Max
  2667. };
  2668. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2669. // This function will only be used during the construction of the Intl object, hence Asserts are in place.
  2670. Assert(args.Info.Count >= 3 && JavascriptFunction::Is(args.Values[1]) && TaggedInt::Is(args.Values[2]));
  2671. JavascriptFunction *func = JavascriptFunction::FromVar(args.Values[1]);
  2672. int32 id = TaggedInt::ToInt32(args.Values[2]);
  2673. Assert(id >= (int32)IntlBuiltInFunctionID::Min && id < (int32)IntlBuiltInFunctionID::Max);
  2674. EngineInterfaceObject* nativeEngineInterfaceObj = scriptContext->GetLibrary()->GetEngineInterfaceObject();
  2675. IntlEngineInterfaceExtensionObject* extensionObject = static_cast<IntlEngineInterfaceExtensionObject*>(nativeEngineInterfaceObj->GetEngineExtension(EngineInterfaceExtensionKind_Intl));
  2676. IntlBuiltInFunctionID functionID = static_cast<IntlBuiltInFunctionID>(id);
  2677. switch (functionID)
  2678. {
  2679. case IntlBuiltInFunctionID::DateToLocaleString:
  2680. extensionObject->dateToLocaleString = func;
  2681. break;
  2682. case IntlBuiltInFunctionID::DateToLocaleDateString:
  2683. extensionObject->dateToLocaleDateString = func;
  2684. break;
  2685. case IntlBuiltInFunctionID::DateToLocaleTimeString:
  2686. extensionObject->dateToLocaleTimeString = func;
  2687. break;
  2688. case IntlBuiltInFunctionID::NumberToLocaleString:
  2689. extensionObject->numberToLocaleString = func;
  2690. break;
  2691. case IntlBuiltInFunctionID::StringLocaleCompare:
  2692. extensionObject->stringLocaleCompare = func;
  2693. break;
  2694. default:
  2695. AssertMsg(false, "functionID was not one of the allowed values. The previous assert should catch this.");
  2696. break;
  2697. }
  2698. // Don't need to return anything
  2699. return scriptContext->GetLibrary()->GetUndefined();
  2700. }
  2701. Var IntlEngineInterfaceExtensionObject::EntryIntl_GetHiddenObject(RecyclableObject* function, CallInfo callInfo, ...)
  2702. {
  2703. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2704. if (callInfo.Count < 2 || !DynamicObject::Is(args.Values[1]))
  2705. {
  2706. return scriptContext->GetLibrary()->GetUndefined();
  2707. }
  2708. DynamicObject* obj = DynamicObject::FromVar(args.Values[1]);
  2709. Var hiddenObject = nullptr;
  2710. if (!obj->GetInternalProperty(obj, Js::InternalPropertyIds::HiddenObject, &hiddenObject, NULL, scriptContext))
  2711. {
  2712. return scriptContext->GetLibrary()->GetUndefined();
  2713. }
  2714. return hiddenObject;
  2715. }
  2716. Var IntlEngineInterfaceExtensionObject::EntryIntl_SetHiddenObject(RecyclableObject* function, CallInfo callInfo, ...)
  2717. {
  2718. EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
  2719. if (callInfo.Count < 3 || !DynamicObject::Is(args.Values[1]) || !DynamicObject::Is(args.Values[2]))
  2720. {
  2721. return scriptContext->GetLibrary()->GetUndefined();
  2722. }
  2723. DynamicObject* obj = DynamicObject::FromVar(args.Values[1]);
  2724. DynamicObject* value = DynamicObject::FromVar(args.Values[2]);
  2725. if (obj->SetInternalProperty(Js::InternalPropertyIds::HiddenObject, value, Js::PropertyOperationFlags::PropertyOperation_None, NULL))
  2726. {
  2727. return scriptContext->GetLibrary()->GetTrue();
  2728. }
  2729. else
  2730. {
  2731. return scriptContext->GetLibrary()->GetFalse();
  2732. }
  2733. }
  2734. #endif
  2735. }
  2736. #endif