ObjectPrototype.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue
  2. using Jint.Native.Array;
  3. using Jint.Native.Symbol;
  4. using Jint.Runtime;
  5. using Jint.Runtime.Descriptors;
  6. using Jint.Runtime.Descriptors.Specialized;
  7. using Jint.Runtime.Interop;
  8. namespace Jint.Native.Object;
  9. public sealed class ObjectPrototype : Prototype
  10. {
  11. private readonly ObjectConstructor _constructor;
  12. internal ObjectChangeFlags _objectChangeFlags;
  13. internal ObjectPrototype(
  14. Engine engine,
  15. Realm realm,
  16. ObjectConstructor constructor) : base(engine, realm)
  17. {
  18. _constructor = constructor;
  19. }
  20. protected override void Initialize()
  21. {
  22. const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
  23. const PropertyFlag LengthFlags = PropertyFlag.Configurable;
  24. var properties = new PropertyDictionary(12, checkExistingKeys: false)
  25. {
  26. ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlags),
  27. ["__defineGetter__"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "__defineGetter__", prototype.DefineGetter, 2, LengthFlags), PropertyFlags),
  28. ["__defineSetter__"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "__defineSetter__", prototype.DefineSetter, 2, LengthFlags), PropertyFlags),
  29. ["__lookupGetter__"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "__lookupGetter__", prototype.LookupGetter, 1, LengthFlags), PropertyFlags),
  30. ["__lookupSetter__"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "__lookupSetter__", prototype.LookupSetter, 1, LengthFlags), PropertyFlags),
  31. ["__proto__"] = new GetSetPropertyDescriptor(
  32. new ClrFunction(Engine, "get __proto__", (thisObject, _) => TypeConverter.ToObject(_realm, thisObject).GetPrototypeOf() ?? Null, 0, LengthFlags),
  33. new ClrFunction(Engine, "set __proto__", (thisObject, arguments) =>
  34. {
  35. TypeConverter.RequireObjectCoercible(_engine, thisObject);
  36. var proto = arguments.At(0);
  37. if (!proto.IsObject() && !proto.IsNull() || thisObject is not ObjectInstance objectInstance)
  38. {
  39. return Undefined;
  40. }
  41. if (!objectInstance.SetPrototypeOf(proto))
  42. {
  43. Throw.TypeError(_realm, "Invalid prototype");
  44. }
  45. return Undefined;
  46. }, 0, LengthFlags),
  47. enumerable: false, configurable: true),
  48. ["toString"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "toString", prototype.ToObjectString, 0, LengthFlags), PropertyFlags),
  49. ["toLocaleString"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "toLocaleString", prototype.ToLocaleString, 0, LengthFlags), PropertyFlags),
  50. ["valueOf"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "valueOf", prototype.ValueOf, 0, LengthFlags), PropertyFlags),
  51. ["hasOwnProperty"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "hasOwnProperty", prototype.HasOwnProperty, 1, LengthFlags), PropertyFlags),
  52. ["isPrototypeOf"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "isPrototypeOf", prototype.IsPrototypeOf, 1, LengthFlags), PropertyFlags),
  53. ["propertyIsEnumerable"] = new LazyPropertyDescriptor<ObjectPrototype>(this, static prototype => new ClrFunction(prototype._engine, "propertyIsEnumerable", prototype.PropertyIsEnumerable, 1, LengthFlags), PropertyFlags)
  54. };
  55. SetProperties(properties);
  56. }
  57. internal override bool SetPrototypeOf(JsValue value)
  58. {
  59. return SameValue(value, _prototype ?? Null);
  60. }
  61. public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
  62. {
  63. TrackChanges(property);
  64. return base.DefineOwnProperty(property, desc);
  65. }
  66. protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
  67. {
  68. TrackChanges(property);
  69. base.SetOwnProperty(property, desc);
  70. }
  71. private void TrackChanges(JsValue property)
  72. {
  73. EnsureInitialized();
  74. if (ArrayInstance.IsArrayIndex(property, out _))
  75. {
  76. _objectChangeFlags |= ObjectChangeFlags.ArrayIndex;
  77. }
  78. else
  79. {
  80. _objectChangeFlags |= property.IsSymbol() ? ObjectChangeFlags.Symbol : ObjectChangeFlags.Property;
  81. }
  82. }
  83. /// <summary>
  84. /// https://tc39.es/ecma262/#sec-object.prototype.__defineGetter__
  85. /// </summary>
  86. private JsValue DefineGetter(JsValue thisObject, JsCallArguments arguments)
  87. {
  88. var o = TypeConverter.ToObject(_realm, thisObject);
  89. var p = arguments.At(0);
  90. var getter = arguments.At(1);
  91. if (!getter.IsCallable)
  92. {
  93. Throw.TypeError(_realm, "Target is not callable");
  94. }
  95. var desc = new GetSetPropertyDescriptor(getter, null, enumerable: true, configurable: true);
  96. var key = TypeConverter.ToPropertyKey(p);
  97. o.DefinePropertyOrThrow(key, desc);
  98. return Undefined;
  99. }
  100. /// <summary>
  101. /// https://tc39.es/ecma262/#sec-object.prototype.__defineSetter__
  102. /// </summary>
  103. private JsValue DefineSetter(JsValue thisObject, JsCallArguments arguments)
  104. {
  105. var o = TypeConverter.ToObject(_realm, thisObject);
  106. var p = arguments.At(0);
  107. var setter = arguments.At(1);
  108. if (!setter.IsCallable)
  109. {
  110. Throw.TypeError(_realm, "Target is not callable");
  111. }
  112. var desc = new GetSetPropertyDescriptor(null, setter, enumerable: true, configurable: true);
  113. var key = TypeConverter.ToPropertyKey(p);
  114. o.DefinePropertyOrThrow(key, desc);
  115. return Undefined;
  116. }
  117. /// <summary>
  118. /// https://tc39.es/ecma262/#sec-object.prototype.__lookupGetter__
  119. /// </summary>
  120. private JsValue LookupGetter(JsValue thisObject, JsCallArguments arguments)
  121. {
  122. var o = TypeConverter.ToObject(_realm, thisObject);
  123. var key = TypeConverter.ToPropertyKey(arguments.At(0));
  124. while (true)
  125. {
  126. var desc = o.GetOwnProperty(key);
  127. if (!ReferenceEquals(desc, PropertyDescriptor.Undefined))
  128. {
  129. if (desc.IsAccessorDescriptor())
  130. {
  131. return desc.Get ?? Undefined;
  132. }
  133. return Undefined;
  134. }
  135. o = o.GetPrototypeOf();
  136. if (o is null)
  137. {
  138. return Undefined;
  139. }
  140. }
  141. }
  142. /// <summary>
  143. /// https://tc39.es/ecma262/#sec-object.prototype.__lookupSetter__
  144. /// </summary>
  145. private JsValue LookupSetter(JsValue thisObject, JsCallArguments arguments)
  146. {
  147. var o = TypeConverter.ToObject(_realm, thisObject);
  148. var key = TypeConverter.ToPropertyKey(arguments.At(0));
  149. while (true)
  150. {
  151. var desc = o.GetOwnProperty(key);
  152. if (!ReferenceEquals(desc, PropertyDescriptor.Undefined))
  153. {
  154. if (desc.IsAccessorDescriptor())
  155. {
  156. return desc.Set ?? Undefined;
  157. }
  158. return Undefined;
  159. }
  160. o = o.GetPrototypeOf();
  161. if (o is null)
  162. {
  163. return Undefined;
  164. }
  165. }
  166. }
  167. private JsValue PropertyIsEnumerable(JsValue thisObject, JsCallArguments arguments)
  168. {
  169. var p = TypeConverter.ToPropertyKey(arguments[0]);
  170. var o = TypeConverter.ToObject(_realm, thisObject);
  171. var desc = o.GetOwnProperty(p);
  172. if (desc == PropertyDescriptor.Undefined)
  173. {
  174. return JsBoolean.False;
  175. }
  176. return desc.Enumerable;
  177. }
  178. private JsValue ValueOf(JsValue thisObject, JsCallArguments arguments)
  179. {
  180. var o = TypeConverter.ToObject(_realm, thisObject);
  181. return o;
  182. }
  183. private JsValue IsPrototypeOf(JsValue thisObject, JsCallArguments arguments)
  184. {
  185. var arg = arguments[0];
  186. if (!arg.IsObject())
  187. {
  188. return JsBoolean.False;
  189. }
  190. var v = arg.AsObject();
  191. var o = TypeConverter.ToObject(_realm, thisObject);
  192. while (true)
  193. {
  194. v = v.Prototype;
  195. if (v is null)
  196. {
  197. return JsBoolean.False;
  198. }
  199. if (ReferenceEquals(o, v))
  200. {
  201. return JsBoolean.True;
  202. }
  203. }
  204. }
  205. /// <summary>
  206. /// https://tc39.es/ecma262/#sec-object.prototype.tolocalestring
  207. /// </summary>
  208. private JsValue ToLocaleString(JsValue thisObject, JsCallArguments arguments)
  209. {
  210. return Invoke(thisObject, "toString", []);
  211. }
  212. /// <summary>
  213. /// https://tc39.es/ecma262/#sec-object.prototype.tostring
  214. /// </summary>
  215. internal JsValue ToObjectString(JsValue thisObject, JsCallArguments arguments)
  216. {
  217. if (thisObject.IsUndefined())
  218. {
  219. return "[object Undefined]";
  220. }
  221. if (thisObject.IsNull())
  222. {
  223. return "[object Null]";
  224. }
  225. var o = TypeConverter.ToObject(_realm, thisObject);
  226. var isArray = o.IsArray();
  227. var tag = o.Get(GlobalSymbolRegistry.ToStringTag);
  228. if (!tag.IsString())
  229. {
  230. if (isArray)
  231. {
  232. tag = "Array";
  233. }
  234. else if (o.IsCallable)
  235. {
  236. tag = "Function";
  237. }
  238. else
  239. {
  240. tag = (o is JsProxy ? ObjectClass.Object : o.Class).ToString();
  241. }
  242. }
  243. return "[object " + tag + "]";
  244. }
  245. /// <summary>
  246. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.5
  247. /// </summary>
  248. private JsValue HasOwnProperty(JsValue thisObject, JsCallArguments arguments)
  249. {
  250. var p = TypeConverter.ToPropertyKey(arguments[0]);
  251. var o = TypeConverter.ToObject(_realm, thisObject);
  252. var desc = o.GetOwnProperty(p);
  253. return desc != PropertyDescriptor.Undefined;
  254. }
  255. }