GlobalObject.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. using System.Globalization;
  2. using System.Linq;
  3. using System.Runtime.CompilerServices;
  4. using System.Text;
  5. using Esprima;
  6. using Jint.Collections;
  7. using Jint.Native.Object;
  8. using Jint.Native.String;
  9. using Jint.Runtime;
  10. using Jint.Runtime.Descriptors;
  11. using Jint.Runtime.Descriptors.Specialized;
  12. using Jint.Runtime.Interop;
  13. namespace Jint.Native.Global
  14. {
  15. public sealed class GlobalObject : ObjectInstance
  16. {
  17. private readonly Realm _realm;
  18. private readonly StringBuilder _stringBuilder = new();
  19. internal GlobalObject(
  20. Engine engine,
  21. Realm realm) : base(engine, ObjectClass.Object, InternalTypes.Object | InternalTypes.PlainObject)
  22. {
  23. _realm = realm;
  24. }
  25. protected override void Initialize()
  26. {
  27. const PropertyFlag lengthFlags = PropertyFlag.Configurable;
  28. const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
  29. var properties = new PropertyDictionary(56, checkExistingKeys: false)
  30. {
  31. ["AggregateError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.AggregateError, propertyFlags),
  32. ["Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Array, propertyFlags),
  33. ["ArrayBuffer"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ArrayBuffer, propertyFlags),
  34. ["Atomics"] = new LazyPropertyDescriptor(this, static state => Undefined, propertyFlags),
  35. ["BigInt"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt, propertyFlags),
  36. ["BigInt64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigInt64Array, propertyFlags),
  37. ["BigUint64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.BigUint64Array, propertyFlags),
  38. ["Boolean"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Boolean, propertyFlags),
  39. ["DataView"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.DataView, propertyFlags),
  40. ["Date"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Date, propertyFlags),
  41. ["Error"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Error, propertyFlags),
  42. ["EvalError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.EvalError, propertyFlags),
  43. ["FinalizationRegistry"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.FinalizationRegistry, propertyFlags),
  44. ["Float32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, propertyFlags),
  45. ["Float64Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, propertyFlags),
  46. ["Function"] = new PropertyDescriptor(_realm.Intrinsics.Function, propertyFlags),
  47. ["Int16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int16Array, propertyFlags),
  48. ["Int32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int32Array, propertyFlags),
  49. ["Int8Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Int8Array, propertyFlags),
  50. // TODO ["Intl"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Intl, propertyFlags),
  51. ["JSON"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Json, propertyFlags),
  52. ["Map"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Map, propertyFlags),
  53. ["Math"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Math, propertyFlags),
  54. ["Number"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Number, propertyFlags),
  55. ["Object"] = new PropertyDescriptor(_realm.Intrinsics.Object, propertyFlags),
  56. ["Promise"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Promise, propertyFlags),
  57. ["Proxy"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Proxy, propertyFlags),
  58. ["RangeError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RangeError, propertyFlags),
  59. ["ReferenceError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ReferenceError, propertyFlags),
  60. ["Reflect"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Reflect, propertyFlags),
  61. ["RegExp"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.RegExp, propertyFlags),
  62. ["Set"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Set, propertyFlags),
  63. ["ShadowRealm"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ShadowRealm, propertyFlags),
  64. ["SharedArrayBuffer"] = new LazyPropertyDescriptor(this, static state => Undefined, propertyFlags),
  65. ["String"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.String, propertyFlags),
  66. ["Symbol"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Symbol, propertyFlags),
  67. ["SyntaxError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.SyntaxError, propertyFlags),
  68. ["TypeError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypeError, propertyFlags),
  69. ["TypedArray"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.TypedArray, propertyFlags),
  70. ["URIError"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.UriError, propertyFlags),
  71. ["Uint16Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint16Array, propertyFlags),
  72. ["Uint32Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint32Array, propertyFlags),
  73. ["Uint8Array"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8Array, propertyFlags),
  74. ["Uint8ClampedArray"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Uint8ClampedArray, propertyFlags),
  75. ["WeakMap"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakMap, propertyFlags),
  76. ["WeakRef"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakRef, propertyFlags),
  77. ["WeakSet"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.WeakSet, propertyFlags),
  78. ["NaN"] = new PropertyDescriptor(double.NaN, PropertyFlag.AllForbidden),
  79. ["Infinity"] = new PropertyDescriptor(double.PositiveInfinity, PropertyFlag.AllForbidden),
  80. ["undefined"] = new PropertyDescriptor(Undefined, PropertyFlag.AllForbidden),
  81. ["parseInt"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseInt", ParseInt, 2, lengthFlags), propertyFlags),
  82. ["parseFloat"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "parseFloat", ParseFloat, 1, lengthFlags), propertyFlags),
  83. ["isNaN"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isNaN", IsNaN, 1, lengthFlags), propertyFlags),
  84. ["isFinite"] = new LazyPropertyDescriptor(this, static state => new ClrFunctionInstance(((GlobalObject) state!)._engine, "isFinite", IsFinite, 1, lengthFlags), propertyFlags),
  85. ["decodeURI"] = new LazyPropertyDescriptor(this, static state =>
  86. {
  87. var global = (GlobalObject) state!;
  88. return new ClrFunctionInstance(global._engine, "decodeURI", global.DecodeUri, 1, lengthFlags);
  89. }, propertyFlags),
  90. ["decodeURIComponent"] = new LazyPropertyDescriptor(this, static state =>
  91. {
  92. var global = (GlobalObject) state!;
  93. return new ClrFunctionInstance(global._engine, "decodeURIComponent", global.DecodeUriComponent, 1, lengthFlags);
  94. }, propertyFlags),
  95. ["encodeURI"] = new LazyPropertyDescriptor(this, static state =>
  96. {
  97. var global = (GlobalObject) state!;
  98. return new ClrFunctionInstance(global._engine, "encodeURI", global.EncodeUri, 1, lengthFlags);
  99. }, propertyFlags),
  100. ["encodeURIComponent"] = new LazyPropertyDescriptor(this, static state =>
  101. {
  102. var global = (GlobalObject) state!;
  103. return new ClrFunctionInstance(global._engine, "encodeURIComponent", global.EncodeUriComponent, 1, lengthFlags);
  104. }, propertyFlags),
  105. ["escape"] = new LazyPropertyDescriptor(this, static state =>
  106. {
  107. var global = (GlobalObject) state!;
  108. return new ClrFunctionInstance(global._engine, "escape", global.Escape, 1, lengthFlags);
  109. }, propertyFlags),
  110. ["unescape"] = new LazyPropertyDescriptor(this, static state =>
  111. {
  112. var global = (GlobalObject) state!;
  113. return new ClrFunctionInstance(global._engine, "unescape", global.Unescape, 1, lengthFlags);
  114. }, propertyFlags),
  115. ["globalThis"] = new PropertyDescriptor(this, propertyFlags),
  116. ["eval"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Eval, PropertyFlag.Configurable | PropertyFlag.Writable),
  117. // toString is not mentioned or actually required in spec, but some tests rely on it
  118. ["toString"] = new LazyPropertyDescriptor(this, static state =>
  119. {
  120. var global = (GlobalObject) state!;
  121. return new ClrFunctionInstance(global._engine, "toString", global.ToStringString, 1);
  122. }, propertyFlags)
  123. };
  124. SetProperties(properties);
  125. }
  126. private JsValue ToStringString(JsValue thisObj, JsValue[] arguments)
  127. {
  128. return _realm.Intrinsics.Object.PrototypeObject.ToObjectString(thisObj, Arguments.Empty);
  129. }
  130. /// <summary>
  131. /// https://tc39.es/ecma262/#sec-parseint-string-radix
  132. /// </summary>
  133. public static JsValue ParseInt(JsValue thisObject, JsValue[] arguments)
  134. {
  135. var inputString = TypeConverter.ToString(arguments.At(0));
  136. var trimmed = StringPrototype.TrimEx(inputString);
  137. var s = trimmed.AsSpan();
  138. var radix = arguments.Length > 1 ? TypeConverter.ToInt32(arguments[1]) : 0;
  139. var hexStart = s.Length > 1 && trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase);
  140. var stripPrefix = true;
  141. if (radix == 0)
  142. {
  143. radix = hexStart ? 16 : 10;
  144. }
  145. else if (radix < 2 || radix > 36)
  146. {
  147. return JsNumber.DoubleNaN;
  148. }
  149. else if (radix != 16)
  150. {
  151. stripPrefix = false;
  152. }
  153. // check fast case
  154. if (radix == 10 && int.TryParse(trimmed, out var number))
  155. {
  156. return JsNumber.Create(number);
  157. }
  158. var sign = 1;
  159. if (s.Length > 0)
  160. {
  161. var c = s[0];
  162. if (c == '-')
  163. {
  164. sign = -1;
  165. }
  166. if (c is '-' or '+')
  167. {
  168. s = s.Slice(1);
  169. }
  170. }
  171. if (stripPrefix && hexStart)
  172. {
  173. s = s.Slice(2);
  174. }
  175. if (s.Length == 0)
  176. {
  177. return double.NaN;
  178. }
  179. var hasResult = false;
  180. double result = 0;
  181. double pow = 1;
  182. for (var i = s.Length - 1; i >= 0; i--)
  183. {
  184. var digit = s[i];
  185. var index = digit switch
  186. {
  187. >= '0' and <= '9' => digit - '0',
  188. >= 'a' and <= 'z' => digit - 'a' + 10,
  189. >= 'A' and <= 'Z' => digit - 'A' + 10,
  190. _ => -1
  191. };
  192. if (index == -1 || index >= radix)
  193. {
  194. // reset
  195. hasResult = false;
  196. result = 0;
  197. pow = 1;
  198. continue;
  199. }
  200. hasResult = true;
  201. result += index * pow;
  202. pow *= radix;
  203. }
  204. return hasResult ? JsNumber.Create(sign * result) : JsNumber.DoubleNaN;
  205. }
  206. /// <summary>
  207. /// https://tc39.es/ecma262/#sec-parsefloat-string
  208. /// </summary>
  209. public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments)
  210. {
  211. var inputString = TypeConverter.ToString(arguments.At(0));
  212. var trimmedString = StringPrototype.TrimStartEx(inputString);
  213. if (string.IsNullOrWhiteSpace(trimmedString))
  214. {
  215. return JsNumber.DoubleNaN;
  216. }
  217. // start of string processing
  218. var i = 0;
  219. // check known string constants
  220. if (!char.IsDigit(trimmedString[0]))
  221. {
  222. if (trimmedString[0] == '-')
  223. {
  224. i++;
  225. if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("-Infinity"))
  226. {
  227. return JsNumber.DoubleNegativeInfinity;
  228. }
  229. }
  230. if (trimmedString[0] == '+')
  231. {
  232. i++;
  233. if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("+Infinity"))
  234. {
  235. return JsNumber.DoublePositiveInfinity;
  236. }
  237. }
  238. if (trimmedString.StartsWith("Infinity"))
  239. {
  240. return JsNumber.DoublePositiveInfinity;
  241. }
  242. if (trimmedString.StartsWith("NaN"))
  243. {
  244. return JsNumber.DoubleNaN;
  245. }
  246. }
  247. // find the starting part of string that is still acceptable JS number
  248. var dotFound = false;
  249. var exponentFound = false;
  250. while (i < trimmedString.Length)
  251. {
  252. var c = trimmedString[i];
  253. if (Character.IsDecimalDigit(c))
  254. {
  255. i++;
  256. continue;
  257. }
  258. if (c == '.')
  259. {
  260. if (dotFound)
  261. {
  262. // does not look right
  263. break;
  264. }
  265. i++;
  266. dotFound = true;
  267. continue;
  268. }
  269. if (c is 'e' or 'E')
  270. {
  271. if (exponentFound)
  272. {
  273. // does not look right
  274. break;
  275. }
  276. i++;
  277. exponentFound = true;
  278. continue;
  279. }
  280. if (c is '+' or '-' && trimmedString[i - 1] is 'e' or 'E')
  281. {
  282. // ok
  283. i++;
  284. continue;
  285. }
  286. break;
  287. }
  288. while (exponentFound && i > 0 && !Character.IsDecimalDigit(trimmedString[i - 1]))
  289. {
  290. // we are missing required exponent number part info
  291. i--;
  292. }
  293. // we should now have proper input part
  294. #if NETSTANDARD2_1_OR_GREATER
  295. var substring = trimmedString.AsSpan(0, i);
  296. #else
  297. var substring = trimmedString.Substring(0, i);
  298. #endif
  299. const NumberStyles Styles = NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign;
  300. if (double.TryParse(substring, Styles, CultureInfo.InvariantCulture, out var d))
  301. {
  302. return d;
  303. }
  304. return JsNumber.DoubleNaN;
  305. }
  306. /// <summary>
  307. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.4
  308. /// </summary>
  309. public static JsValue IsNaN(JsValue thisObject, JsValue[] arguments)
  310. {
  311. var value = arguments.At(0);
  312. if (ReferenceEquals(value, JsNumber.DoubleNaN))
  313. {
  314. return true;
  315. }
  316. var x = TypeConverter.ToNumber(value);
  317. return double.IsNaN(x);
  318. }
  319. /// <summary>
  320. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.5
  321. /// </summary>
  322. public static JsValue IsFinite(JsValue thisObject, JsValue[] arguments)
  323. {
  324. if (arguments.Length != 1)
  325. {
  326. return false;
  327. }
  328. var n = TypeConverter.ToNumber(arguments.At(0));
  329. if (double.IsNaN(n) || double.IsInfinity(n))
  330. {
  331. return false;
  332. }
  333. return true;
  334. }
  335. private static readonly HashSet<char> UriReserved = new HashSet<char>
  336. {
  337. ';', '/', '?', ':', '@', '&', '=', '+', '$', ','
  338. };
  339. private static readonly HashSet<char> UriUnescaped = new HashSet<char>
  340. {
  341. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
  342. 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
  343. 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', '.', '!',
  344. '~', '*', '\'', '(', ')'
  345. };
  346. private static readonly HashSet<char> UnescapedUriSet = new HashSet<char>(UriReserved.Concat(UriUnescaped).Concat(new[] { '#' }));
  347. private static readonly HashSet<char> ReservedUriSet = new HashSet<char>(UriReserved.Concat(new[] { '#' }));
  348. private const string HexaMap = "0123456789ABCDEF";
  349. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  350. private static bool IsValidHexaChar(char c) => Uri.IsHexDigit(c);
  351. /// <summary>
  352. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.2
  353. /// </summary>
  354. /// <param name="thisObject"></param>
  355. /// <param name="arguments"></param>
  356. /// <returns></returns>
  357. public JsValue EncodeUri(JsValue thisObject, JsValue[] arguments)
  358. {
  359. var uriString = TypeConverter.ToString(arguments.At(0));
  360. return Encode(uriString, UnescapedUriSet);
  361. }
  362. /// <summary>
  363. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
  364. /// </summary>
  365. /// <param name="thisObject"></param>
  366. /// <param name="arguments"></param>
  367. /// <returns></returns>
  368. public JsValue EncodeUriComponent(JsValue thisObject, JsValue[] arguments)
  369. {
  370. var uriString = TypeConverter.ToString(arguments.At(0));
  371. return Encode(uriString, UriUnescaped);
  372. }
  373. private string Encode(string uriString, HashSet<char> unescapedUriSet)
  374. {
  375. var strLen = uriString.Length;
  376. _stringBuilder.EnsureCapacity(uriString.Length);
  377. _stringBuilder.Clear();
  378. for (var k = 0; k < strLen; k++)
  379. {
  380. var c = uriString[k];
  381. if (unescapedUriSet != null && unescapedUriSet.Contains(c))
  382. {
  383. _stringBuilder.Append(c);
  384. }
  385. else
  386. {
  387. if (c >= 0xDC00 && c <= 0xDBFF)
  388. {
  389. ExceptionHelper.ThrowUriError(_realm);
  390. }
  391. int v;
  392. if (c < 0xD800 || c > 0xDBFF)
  393. {
  394. v = c;
  395. }
  396. else
  397. {
  398. k++;
  399. if (k == strLen)
  400. {
  401. ExceptionHelper.ThrowUriError(_realm);
  402. }
  403. var kChar = (int)uriString[k];
  404. if (kChar < 0xDC00 || kChar > 0xDFFF)
  405. {
  406. ExceptionHelper.ThrowUriError(_realm);
  407. }
  408. v = (c - 0xD800) * 0x400 + (kChar - 0xDC00) + 0x10000;
  409. }
  410. byte[] octets = System.Array.Empty<byte>();
  411. if (v >= 0 && v <= 0x007F)
  412. {
  413. // 00000000 0zzzzzzz -> 0zzzzzzz
  414. octets = new[] { (byte)v };
  415. }
  416. else if (v <= 0x07FF)
  417. {
  418. // 00000yyy yyzzzzzz -> 110yyyyy ; 10zzzzzz
  419. octets = new[]
  420. {
  421. (byte)(0xC0 | (v >> 6)),
  422. (byte)(0x80 | (v & 0x3F))
  423. };
  424. }
  425. else if (v <= 0xD7FF)
  426. {
  427. // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz
  428. octets = new[]
  429. {
  430. (byte)(0xE0 | (v >> 12)),
  431. (byte)(0x80 | ((v >> 6) & 0x3F)),
  432. (byte)(0x80 | (v & 0x3F))
  433. };
  434. }
  435. else if (v <= 0xDFFF)
  436. {
  437. ExceptionHelper.ThrowUriError(_realm);
  438. }
  439. else if (v <= 0xFFFF)
  440. {
  441. octets = new[]
  442. {
  443. (byte) (0xE0 | (v >> 12)),
  444. (byte) (0x80 | ((v >> 6) & 0x3F)),
  445. (byte) (0x80 | (v & 0x3F))
  446. };
  447. }
  448. else
  449. {
  450. octets = new[]
  451. {
  452. (byte) (0xF0 | (v >> 18)),
  453. (byte) (0x80 | (v >> 12 & 0x3F)),
  454. (byte) (0x80 | (v >> 6 & 0x3F)),
  455. (byte) (0x80 | (v >> 0 & 0x3F))
  456. };
  457. }
  458. foreach (var octet in octets)
  459. {
  460. var x1 = HexaMap[octet / 16];
  461. var x2 = HexaMap[octet % 16];
  462. _stringBuilder.Append('%').Append(x1).Append(x2);
  463. }
  464. }
  465. }
  466. return _stringBuilder.ToString();
  467. }
  468. public JsValue DecodeUri(JsValue thisObject, JsValue[] arguments)
  469. {
  470. var uriString = TypeConverter.ToString(arguments.At(0));
  471. return Decode(uriString, ReservedUriSet);
  472. }
  473. public JsValue DecodeUriComponent(JsValue thisObject, JsValue[] arguments)
  474. {
  475. var componentString = TypeConverter.ToString(arguments.At(0));
  476. return Decode(componentString, null);
  477. }
  478. private string Decode(string uriString, HashSet<char>? reservedSet)
  479. {
  480. var strLen = uriString.Length;
  481. _stringBuilder.EnsureCapacity(strLen);
  482. _stringBuilder.Clear();
  483. var octets = System.Array.Empty<byte>();
  484. for (var k = 0; k < strLen; k++)
  485. {
  486. var C = uriString[k];
  487. if (C != '%')
  488. {
  489. _stringBuilder.Append(C);
  490. }
  491. else
  492. {
  493. var start = k;
  494. if (k + 2 >= strLen)
  495. {
  496. ExceptionHelper.ThrowUriError(_realm);
  497. }
  498. if (!IsValidHexaChar(uriString[k + 1]) || !IsValidHexaChar(uriString[k + 2]))
  499. {
  500. ExceptionHelper.ThrowUriError(_realm);
  501. }
  502. var B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16);
  503. k += 2;
  504. if ((B & 0x80) == 0)
  505. {
  506. C = (char)B;
  507. if (reservedSet == null || !reservedSet.Contains(C))
  508. {
  509. _stringBuilder.Append(C);
  510. }
  511. else
  512. {
  513. _stringBuilder.Append(uriString, start, k - start + 1);
  514. }
  515. }
  516. else
  517. {
  518. var n = 0;
  519. for (; ((B << n) & 0x80) != 0; n++) ;
  520. if (n == 1 || n > 4)
  521. {
  522. ExceptionHelper.ThrowUriError(_realm);
  523. }
  524. octets = octets.Length == n
  525. ? octets
  526. : new byte[n];
  527. octets[0] = B;
  528. if (k + (3 * (n - 1)) >= strLen)
  529. {
  530. ExceptionHelper.ThrowUriError(_realm);
  531. }
  532. for (var j = 1; j < n; j++)
  533. {
  534. k++;
  535. if (uriString[k] != '%')
  536. {
  537. ExceptionHelper.ThrowUriError(_realm);
  538. }
  539. if (!IsValidHexaChar(uriString[k + 1]) || !IsValidHexaChar(uriString[k + 2]))
  540. {
  541. ExceptionHelper.ThrowUriError(_realm);
  542. }
  543. B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16);
  544. // B & 11000000 != 10000000
  545. if ((B & 0xC0) != 0x80)
  546. {
  547. ExceptionHelper.ThrowUriError(_realm);
  548. }
  549. k += 2;
  550. octets[j] = B;
  551. }
  552. _stringBuilder.Append(Encoding.UTF8.GetString(octets, 0, octets.Length));
  553. }
  554. }
  555. }
  556. return _stringBuilder.ToString();
  557. }
  558. /// <summary>
  559. /// http://www.ecma-international.org/ecma-262/5.1/#sec-B.2.1
  560. /// </summary>
  561. public JsValue Escape(JsValue thisObject, JsValue[] arguments)
  562. {
  563. const string whiteList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./";
  564. var uriString = TypeConverter.ToString(arguments.At(0));
  565. var strLen = uriString.Length;
  566. _stringBuilder.EnsureCapacity(strLen);
  567. _stringBuilder.Clear();
  568. for (var k = 0; k < strLen; k++)
  569. {
  570. var c = uriString[k];
  571. if (whiteList.IndexOf(c) != -1)
  572. {
  573. _stringBuilder.Append(c);
  574. }
  575. else if (c < 256)
  576. {
  577. _stringBuilder.Append($"%{((int) c):X2}");
  578. }
  579. else
  580. {
  581. _stringBuilder.Append($"%u{((int) c):X4}");
  582. }
  583. }
  584. return _stringBuilder.ToString();
  585. }
  586. /// <summary>
  587. /// http://www.ecma-international.org/ecma-262/5.1/#sec-B.2.2
  588. /// </summary>
  589. public JsValue Unescape(JsValue thisObject, JsValue[] arguments)
  590. {
  591. var uriString = TypeConverter.ToString(arguments.At(0));
  592. var strLen = uriString.Length;
  593. _stringBuilder.EnsureCapacity(strLen);
  594. _stringBuilder.Clear();
  595. for (var k = 0; k < strLen; k++)
  596. {
  597. var c = uriString[k];
  598. if (c == '%')
  599. {
  600. if (k <= strLen - 6
  601. && uriString[k + 1] == 'u'
  602. && uriString.Skip(k + 2).Take(4).All(IsValidHexaChar))
  603. {
  604. c = (char)int.Parse(
  605. string.Join(string.Empty, uriString.Skip(k + 2).Take(4)),
  606. NumberStyles.AllowHexSpecifier);
  607. k += 5;
  608. }
  609. else if (k <= strLen - 3
  610. && uriString.Skip(k + 1).Take(2).All(IsValidHexaChar))
  611. {
  612. c = (char)int.Parse(
  613. string.Join(string.Empty, uriString.Skip(k + 1).Take(2)),
  614. NumberStyles.AllowHexSpecifier);
  615. k += 2;
  616. }
  617. }
  618. _stringBuilder.Append(c);
  619. }
  620. return _stringBuilder.ToString();
  621. }
  622. // optimized versions with string parameter and without virtual dispatch for global environment usage
  623. internal bool HasProperty(Key property)
  624. {
  625. return GetOwnProperty(property) != PropertyDescriptor.Undefined;
  626. }
  627. internal PropertyDescriptor GetProperty(Key property) => GetOwnProperty(property);
  628. internal bool DefinePropertyOrThrow(Key property, PropertyDescriptor desc)
  629. {
  630. if (!DefineOwnProperty(property, desc))
  631. {
  632. ExceptionHelper.ThrowTypeError(_realm);
  633. }
  634. return true;
  635. }
  636. internal bool DefineOwnProperty(Key property, PropertyDescriptor desc)
  637. {
  638. var current = GetOwnProperty(property);
  639. if (current == desc)
  640. {
  641. return true;
  642. }
  643. // check fast path
  644. if ((current._flags & PropertyFlag.MutableBinding) != 0)
  645. {
  646. current._value = desc.Value;
  647. return true;
  648. }
  649. return ValidateAndApplyPropertyDescriptor(this, new JsString(property), true, desc, current);
  650. }
  651. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  652. internal PropertyDescriptor GetOwnProperty(Key property)
  653. {
  654. Properties!.TryGetValue(property, out var descriptor);
  655. return descriptor ?? PropertyDescriptor.Undefined;
  656. }
  657. internal bool SetFromMutableBinding(Key property, JsValue value, bool strict)
  658. {
  659. // here we are called only from global environment record context
  660. // we can take some shortcuts to be faster
  661. if (!_properties!.TryGetValue(property, out var existingDescriptor))
  662. {
  663. if (strict)
  664. {
  665. ExceptionHelper.ThrowReferenceNameError(_realm, property.Name);
  666. }
  667. _properties[property] = new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding);
  668. return true;
  669. }
  670. if (existingDescriptor.IsDataDescriptor())
  671. {
  672. if (!existingDescriptor.Writable || existingDescriptor.IsAccessorDescriptor())
  673. {
  674. return false;
  675. }
  676. // check fast path
  677. if ((existingDescriptor._flags & PropertyFlag.MutableBinding) != 0)
  678. {
  679. existingDescriptor._value = value;
  680. return true;
  681. }
  682. // slow path
  683. return DefineOwnProperty(property, new PropertyDescriptor(value, PropertyFlag.None));
  684. }
  685. if (existingDescriptor.Set is not ICallable setter)
  686. {
  687. return false;
  688. }
  689. setter.Call(this, new[] {value});
  690. return true;
  691. }
  692. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  693. internal void SetOwnProperty(Key property, PropertyDescriptor desc)
  694. {
  695. SetProperty(property, desc);
  696. }
  697. }
  698. }