JsValueExtensions.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. using System.Diagnostics.Contracts;
  2. using System.Numerics;
  3. using System.Runtime.CompilerServices;
  4. using Esprima;
  5. using Jint.Native;
  6. using Jint.Native.Function;
  7. using Jint.Native.Object;
  8. using Jint.Native.Promise;
  9. using Jint.Native.RegExp;
  10. using Jint.Native.Symbol;
  11. using Jint.Native.TypedArray;
  12. using Jint.Runtime;
  13. namespace Jint;
  14. public static class JsValueExtensions
  15. {
  16. [Pure]
  17. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  18. public static bool IsPrimitive(this JsValue value)
  19. {
  20. return (value._type & (InternalTypes.Primitive | InternalTypes.Undefined | InternalTypes.Null)) != InternalTypes.Empty;
  21. }
  22. [Pure]
  23. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  24. public static bool IsUndefined(this JsValue value)
  25. {
  26. return value._type == InternalTypes.Undefined;
  27. }
  28. [Pure]
  29. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  30. internal static bool IsNullOrUndefined(this JsValue value)
  31. {
  32. return value._type < InternalTypes.Boolean;
  33. }
  34. [Pure]
  35. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  36. public static bool IsDate(this JsValue value)
  37. {
  38. return value is JsDate;
  39. }
  40. [Pure]
  41. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  42. public static bool IsPromise(this JsValue value)
  43. {
  44. return value is JsPromise;
  45. }
  46. [Pure]
  47. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  48. public static bool IsPrivateName(this JsValue value) => value._type == InternalTypes.PrivateName;
  49. [Pure]
  50. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  51. public static bool IsRegExp(this JsValue value)
  52. {
  53. if (value is not ObjectInstance oi)
  54. {
  55. return false;
  56. }
  57. var matcher = oi.Get(GlobalSymbolRegistry.Match);
  58. if (!matcher.IsUndefined())
  59. {
  60. return TypeConverter.ToBoolean(matcher);
  61. }
  62. return value is JsRegExp;
  63. }
  64. [Pure]
  65. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  66. public static bool IsObject(this JsValue value)
  67. {
  68. return (value._type & InternalTypes.Object) != InternalTypes.Empty;
  69. }
  70. [Pure]
  71. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  72. public static bool IsString(this JsValue value)
  73. {
  74. return (value._type & InternalTypes.String) != InternalTypes.Empty;
  75. }
  76. [Pure]
  77. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  78. public static bool IsNumber(this JsValue value)
  79. {
  80. return (value._type & (InternalTypes.Number | InternalTypes.Integer)) != InternalTypes.Empty;
  81. }
  82. [Pure]
  83. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  84. public static bool IsBigInt(this JsValue value)
  85. {
  86. return (value._type & InternalTypes.BigInt) != InternalTypes.Empty;
  87. }
  88. [Pure]
  89. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  90. internal static bool IsInteger(this JsValue value)
  91. {
  92. return value._type == InternalTypes.Integer;
  93. }
  94. [Pure]
  95. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  96. public static bool IsBoolean(this JsValue value)
  97. {
  98. return value._type == InternalTypes.Boolean;
  99. }
  100. [Pure]
  101. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  102. public static bool IsNull(this JsValue value)
  103. {
  104. return value._type == InternalTypes.Null;
  105. }
  106. [Pure]
  107. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  108. public static bool IsSymbol(this JsValue value)
  109. {
  110. return value._type == InternalTypes.Symbol;
  111. }
  112. [Pure]
  113. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  114. internal static bool CanBeHeldWeakly(this JsValue value, GlobalSymbolRegistry symbolRegistry)
  115. {
  116. return value.IsObject() || (value.IsSymbol() && !symbolRegistry.ContainsCustom(value));
  117. }
  118. [Pure]
  119. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  120. public static JsDate AsDate(this JsValue value)
  121. {
  122. if (!value.IsDate())
  123. {
  124. ExceptionHelper.ThrowArgumentException("The value is not a date");
  125. }
  126. return (JsDate) value;
  127. }
  128. [Pure]
  129. public static JsRegExp AsRegExp(this JsValue value)
  130. {
  131. if (!value.IsRegExp())
  132. {
  133. ExceptionHelper.ThrowArgumentException("The value is not a regex");
  134. }
  135. return (JsRegExp) value;
  136. }
  137. [Pure]
  138. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  139. public static ObjectInstance AsObject(this JsValue value)
  140. {
  141. if (!value.IsObject())
  142. {
  143. ExceptionHelper.ThrowArgumentException("The value is not an object");
  144. }
  145. return (ObjectInstance) value;
  146. }
  147. [Pure]
  148. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  149. public static TInstance AsInstance<TInstance>(this JsValue value) where TInstance : class
  150. {
  151. if (!value.IsObject())
  152. {
  153. ExceptionHelper.ThrowArgumentException("The value is not an object");
  154. }
  155. return (value as TInstance)!;
  156. }
  157. [Pure]
  158. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  159. public static JsArray AsArray(this JsValue value)
  160. {
  161. if (!value.IsArray())
  162. {
  163. ExceptionHelper.ThrowArgumentException("The value is not an array");
  164. }
  165. return (JsArray) value;
  166. }
  167. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  168. public static bool AsBoolean(this JsValue value)
  169. {
  170. if (value._type != InternalTypes.Boolean)
  171. {
  172. ThrowWrongTypeException(value, "boolean");
  173. }
  174. return ((JsBoolean) value)._value;
  175. }
  176. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  177. public static double AsNumber(this JsValue value)
  178. {
  179. if (!value.IsNumber())
  180. {
  181. ThrowWrongTypeException(value, "number");
  182. }
  183. return ((JsNumber) value)._value;
  184. }
  185. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  186. internal static int AsInteger(this JsValue value)
  187. {
  188. return (int) ((JsNumber) value)._value;
  189. }
  190. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  191. internal static BigInteger AsBigInt(this JsValue value)
  192. {
  193. return ((JsBigInt) value)._value;
  194. }
  195. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  196. public static string AsString(this JsValue value)
  197. {
  198. if (!value.IsString())
  199. {
  200. ThrowWrongTypeException(value, "string");
  201. }
  202. return value.ToString();
  203. }
  204. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  205. public static bool IsUint8Array(this JsValue value)
  206. {
  207. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Uint8 };
  208. }
  209. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  210. public static byte[] AsUint8Array(this JsValue value)
  211. {
  212. if (!value.IsUint8Array())
  213. {
  214. ThrowWrongTypeException(value, "Uint8Array");
  215. }
  216. return ((JsTypedArray) value).ToNativeArray<byte>();
  217. }
  218. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  219. public static bool IsUint8ClampedArray(this JsValue value)
  220. {
  221. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Uint8C };
  222. }
  223. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  224. public static byte[] AsUint8ClampedArray(this JsValue value)
  225. {
  226. if (!value.IsUint8ClampedArray())
  227. {
  228. ThrowWrongTypeException(value, "Uint8ClampedArray");
  229. }
  230. return ((JsTypedArray) value).ToNativeArray<byte>();
  231. }
  232. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  233. public static bool IsInt8Array(this JsValue value)
  234. {
  235. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Int8 };
  236. }
  237. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  238. public static sbyte[] AsInt8Array(this JsValue value)
  239. {
  240. if (!value.IsInt8Array())
  241. {
  242. ThrowWrongTypeException(value, "Int8Array");
  243. }
  244. return ((JsTypedArray) value).ToNativeArray<sbyte>();
  245. }
  246. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  247. public static bool IsInt16Array(this JsValue value)
  248. {
  249. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Int16 };
  250. }
  251. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  252. public static short[] AsInt16Array(this JsValue value)
  253. {
  254. if (!value.IsInt16Array())
  255. {
  256. ThrowWrongTypeException(value, "Int16Array");
  257. }
  258. return ((JsTypedArray) value).ToNativeArray<short>();
  259. }
  260. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  261. public static bool IsUint16Array(this JsValue value)
  262. {
  263. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Uint16 };
  264. }
  265. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  266. public static ushort[] AsUint16Array(this JsValue value)
  267. {
  268. if (!value.IsUint16Array())
  269. {
  270. ThrowWrongTypeException(value, "Uint16Array");
  271. }
  272. return ((JsTypedArray) value).ToNativeArray<ushort>();
  273. }
  274. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  275. public static bool IsInt32Array(this JsValue value)
  276. {
  277. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Int32 };
  278. }
  279. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  280. public static int[] AsInt32Array(this JsValue value)
  281. {
  282. if (!value.IsInt32Array())
  283. {
  284. ThrowWrongTypeException(value, "Int32Array");
  285. }
  286. return ((JsTypedArray) value).ToNativeArray<int>();
  287. }
  288. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  289. public static bool IsUint32Array(this JsValue value)
  290. {
  291. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Uint32 };
  292. }
  293. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  294. public static uint[] AsUint32Array(this JsValue value)
  295. {
  296. if (!value.IsUint32Array())
  297. {
  298. ThrowWrongTypeException(value, "Uint32Array");
  299. }
  300. return ((JsTypedArray) value).ToNativeArray<uint>();
  301. }
  302. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  303. public static bool IsBigInt64Array(this JsValue value)
  304. {
  305. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.BigInt64 };
  306. }
  307. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  308. public static long[] AsBigInt64Array(this JsValue value)
  309. {
  310. if (!value.IsBigInt64Array())
  311. {
  312. ThrowWrongTypeException(value, "BigInt64Array");
  313. }
  314. return ((JsTypedArray) value).ToNativeArray<long>();
  315. }
  316. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  317. public static bool IsBigUint64Array(this JsValue value)
  318. {
  319. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.BigUint64 };
  320. }
  321. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  322. public static ulong[] AsBigUint64Array(this JsValue value)
  323. {
  324. if (!value.IsBigUint64Array())
  325. {
  326. ThrowWrongTypeException(value, "BigUint64Array");
  327. }
  328. return ((JsTypedArray) value).ToNativeArray<ulong>();
  329. }
  330. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  331. public static bool IsFloat32Array(this JsValue value)
  332. {
  333. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float32 };
  334. }
  335. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  336. public static float[] AsFloat32Array(this JsValue value)
  337. {
  338. if (!value.IsFloat32Array())
  339. {
  340. ThrowWrongTypeException(value, "Float32Array");
  341. }
  342. return ((JsTypedArray) value).ToNativeArray<float>();
  343. }
  344. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  345. public static bool IsFloat64Array(this JsValue value)
  346. {
  347. return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float64 };
  348. }
  349. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  350. public static double[] AsFloat64Array(this JsValue value)
  351. {
  352. if (!value.IsFloat64Array())
  353. {
  354. ThrowWrongTypeException(value, "Float64Array");
  355. }
  356. return ((JsTypedArray) value).ToNativeArray<double>();
  357. }
  358. [Pure]
  359. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  360. public static T? TryCast<T>(this JsValue value) where T : class
  361. {
  362. return value as T;
  363. }
  364. [Pure]
  365. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  366. public static T? TryCast<T>(this JsValue value, Action<JsValue> fail) where T : class
  367. {
  368. if (value is T o)
  369. {
  370. return o;
  371. }
  372. fail.Invoke(value);
  373. return null;
  374. }
  375. [Pure]
  376. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  377. public static T? As<T>(this JsValue value) where T : ObjectInstance
  378. {
  379. if (value.IsObject())
  380. {
  381. return value as T;
  382. }
  383. return null;
  384. }
  385. [Pure]
  386. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  387. public static FunctionInstance AsFunctionInstance(this JsValue value)
  388. {
  389. if (value is not FunctionInstance instance)
  390. {
  391. ThrowWrongTypeException(value, "FunctionInstance");
  392. return null!;
  393. }
  394. return instance;
  395. }
  396. [Pure]
  397. public static JsValue Call(this JsValue value)
  398. {
  399. if (value is ObjectInstance objectInstance)
  400. {
  401. var engine = objectInstance.Engine;
  402. return engine.Call(value, Array.Empty<JsValue>());
  403. }
  404. return ThrowNotObject(value);
  405. }
  406. [Pure]
  407. public static JsValue Call(this JsValue value, JsValue arg1)
  408. {
  409. if (value is ObjectInstance objectInstance)
  410. {
  411. var engine = objectInstance.Engine;
  412. var arguments = engine._jsValueArrayPool.RentArray(1);
  413. arguments[0] = arg1;
  414. var result = engine.Call(value, arguments);
  415. engine._jsValueArrayPool.ReturnArray(arguments);
  416. return result;
  417. }
  418. return ThrowNotObject(value);
  419. }
  420. [Pure]
  421. public static JsValue Call(this JsValue value, JsValue arg1, JsValue arg2)
  422. {
  423. if (value is ObjectInstance objectInstance)
  424. {
  425. var engine = objectInstance.Engine;
  426. var arguments = engine._jsValueArrayPool.RentArray(2);
  427. arguments[0] = arg1;
  428. arguments[1] = arg2;
  429. var result = engine.Call(value, arguments);
  430. engine._jsValueArrayPool.ReturnArray(arguments);
  431. return result;
  432. }
  433. return ThrowNotObject(value);
  434. }
  435. [Pure]
  436. public static JsValue Call(this JsValue value, JsValue arg1, JsValue arg2, JsValue arg3)
  437. {
  438. if (value is ObjectInstance objectInstance)
  439. {
  440. var engine = objectInstance.Engine;
  441. var arguments = engine._jsValueArrayPool.RentArray(3);
  442. arguments[0] = arg1;
  443. arguments[1] = arg2;
  444. arguments[2] = arg3;
  445. var result = engine.Call(value, arguments);
  446. engine._jsValueArrayPool.ReturnArray(arguments);
  447. return result;
  448. }
  449. return ThrowNotObject(value);
  450. }
  451. [Pure]
  452. public static JsValue Call(this JsValue value, params JsValue[] arguments)
  453. {
  454. if (value is ObjectInstance objectInstance)
  455. {
  456. return objectInstance.Engine.Call(value, arguments);
  457. }
  458. return ThrowNotObject(value);
  459. }
  460. [Pure]
  461. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  462. public static JsValue Call(this JsValue value, JsValue thisObj, JsValue[] arguments)
  463. {
  464. if (value is ObjectInstance objectInstance)
  465. {
  466. return objectInstance.Engine.Call(value, thisObj, arguments);
  467. }
  468. return ThrowNotObject(value);
  469. }
  470. [MethodImpl(MethodImplOptions.NoInlining)]
  471. private static JsValue ThrowNotObject(JsValue value)
  472. {
  473. ExceptionHelper.ThrowArgumentException(value + " is not object");
  474. return null;
  475. }
  476. /// <summary>
  477. /// If the value is a Promise
  478. /// 1. If "Fulfilled" returns the value it was fulfilled with
  479. /// 2. If "Rejected" throws "PromiseRejectedException" with the rejection reason
  480. /// 3. If "Pending" throws "InvalidOperationException". Should be called only in "Settled" state
  481. /// Else
  482. /// returns the value intact
  483. /// </summary>
  484. /// <param name="value">value to unwrap</param>
  485. /// <returns>inner value if Promise the value itself otherwise</returns>
  486. public static JsValue UnwrapIfPromise(this JsValue value)
  487. {
  488. if (value is JsPromise promise)
  489. {
  490. switch (promise.State)
  491. {
  492. case PromiseState.Pending:
  493. ExceptionHelper.ThrowInvalidOperationException("'UnwrapIfPromise' called before Promise was settled");
  494. return null;
  495. case PromiseState.Fulfilled:
  496. return promise.Value;
  497. case PromiseState.Rejected:
  498. ExceptionHelper.ThrowPromiseRejectedException(promise.Value);
  499. return null;
  500. default:
  501. ExceptionHelper.ThrowArgumentOutOfRangeException();
  502. return null;
  503. }
  504. }
  505. return value;
  506. }
  507. [MethodImpl(MethodImplOptions.NoInlining)]
  508. private static void ThrowWrongTypeException(JsValue value, string expectedType)
  509. {
  510. ExceptionHelper.ThrowArgumentException($"Expected {expectedType} but got {value._type}");
  511. }
  512. internal static BigInteger ToBigInteger(this JsValue value, Engine engine)
  513. {
  514. try
  515. {
  516. return TypeConverter.ToBigInt(value);
  517. }
  518. catch (ParserException ex)
  519. {
  520. ExceptionHelper.ThrowSyntaxError(engine.Realm, ex.Message);
  521. return default;
  522. }
  523. }
  524. internal static ICallable GetCallable(this JsValue source, Realm realm)
  525. {
  526. if (source is ICallable callable)
  527. {
  528. return callable;
  529. }
  530. ExceptionHelper.ThrowTypeError(realm, "Argument must be callable");
  531. return null;
  532. }
  533. /// <summary>
  534. /// https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption
  535. /// </summary>
  536. internal static uint? GetArrayBufferMaxByteLengthOption(this JsValue options)
  537. {
  538. if (options is not JsObject oi)
  539. {
  540. return null;
  541. }
  542. var maxByteLength = options.Get("maxByteLength");
  543. if (maxByteLength.IsUndefined())
  544. {
  545. return null;
  546. }
  547. return TypeConverter.ToIndex(oi.Engine.Realm, maxByteLength);
  548. }
  549. }