All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.htmlunit.corejs.javascript.AbstractEcmaObjectOperations Maven / Gradle / Ivy

package org.htmlunit.corejs.javascript;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * Abstract Object Operations as defined by EcmaScript
 *
 * @see Abstract
 *     Operations - Operations on Objects
 *     

Notes *

    *
  • all methods are to deviate from the method signature defined in the EcmaScript * specification, by taking an additional 1st parameter of type Context: (downstream) * methods may need the Context object to read flags and we want to avoid having to look * up the current context (for performance reasons) *
  • all methods that implement an Abstract Operation as defined by EcmaScript are to be * package-scopes methods, to prevent them from being used directly by 3rd party code, * which would hamper evolving them over time to adept to newer EcmaScript specifications *
  • a link to the method specification of the specific (EcmaScript) version implemented * will be put in the JavaDoc of each method that implements an Abstract Operations *
*/ class AbstractEcmaObjectOperations { enum INTEGRITY_LEVEL { FROZEN, SEALED } /** * Implementation of Abstract Object operation HasOwnProperty as defined by EcmaScript * * @param cx * @param o * @param property * @return boolean * @see */ static boolean hasOwnProperty(Context cx, Object o, Object property) { Scriptable obj = ScriptableObject.ensureScriptable(o); boolean result; if (property instanceof Symbol) { result = ScriptableObject.ensureSymbolScriptable(o).has((Symbol) property, obj); } else { ScriptRuntime.StringIdOrIndex s = ScriptRuntime.toStringIdOrIndex(property); if (s.stringId == null) { result = obj.has(s.index, obj); } else { result = obj.has(s.stringId, obj); } } return result; } /** * Implementation of Abstract Object operation testIntegrityLevel as defined by EcmaScript * * @param cx * @param o * @param level * @return boolean * @see TestIntegrityLevel */ static boolean testIntegrityLevel(Context cx, Object o, INTEGRITY_LEVEL level) { ScriptableObject obj = ScriptableObject.ensureScriptableObject(o); if (obj.isExtensible()) return false; for (Object name : obj.getIds(true, true)) { ScriptableObject desc = obj.getOwnPropertyDescriptor(cx, name); if (Boolean.TRUE.equals(desc.get("configurable"))) return false; if (level == INTEGRITY_LEVEL.FROZEN && ScriptableObject.isDataDescriptor(desc) && Boolean.TRUE.equals(desc.get("writable"))) return false; } return true; } /** * Implementation of Abstract Object operation setIntegrityLevel as defined by EcmaScript * * @param cx * @param o * @param level * @return boolean * @see SetIntegrityLevel */ static boolean setIntegrityLevel(Context cx, Object o, INTEGRITY_LEVEL level) { /* 1. Assert: Type(O) is Object. 2. Assert: level is either sealed or frozen. 3. Let status be ? O.[[PreventExtensions]](). 4. If status is false, return false. 5. Let keys be ? O.[[OwnPropertyKeys]](). 6. If level is sealed, then a. For each element k of keys, do i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }). 7. Else, a. Assert: level is frozen. b. For each element k of keys, do i. Let currentDesc be ? O.[[GetOwnProperty]](k). ii. If currentDesc is not undefined, then 1. If IsAccessorDescriptor(currentDesc) is true, then a. Let desc be the PropertyDescriptor { [[Configurable]]: false }. 2. Else, a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }. 3. Perform ? DefinePropertyOrThrow(O, k, desc). 8. Return true. NOTES - While steps 6.a.i and 7.b.ii.3 call for the Abstract DefinePropertyOrThrow operation, the conditions under which a throw would occur aren't applicable when freezing or sealing an object, see https://262.ecma-international.org/11.0/#sec-validateandapplypropertydescriptor: 1. n/a 2. current cannot be undefined, because the logic operates only on existing properties 3. n/a 4. this code doesn't ever set configurable to true or modifies the enumerable property 5. n/a 6. as current and desc start out the same and the writable property is set only after checking if isDataDescriptor == true, this condition cannot occur 7. both conditions under which false would be returned cannot occur here 8. both conditions under which false would be returned cannot occur here */ ScriptableObject obj = ScriptableObject.ensureScriptableObject(o); if (!obj.preventExtensions()) { return false; } for (Object key : obj.getIds(true, true)) { ScriptableObject desc = obj.getOwnPropertyDescriptor(cx, key); if (level == INTEGRITY_LEVEL.SEALED) { if (Boolean.TRUE.equals(desc.get("configurable"))) { desc.put("configurable", desc, Boolean.FALSE); obj.defineOwnProperty(cx, key, desc, false); } } else { if (ScriptableObject.isDataDescriptor(desc) && Boolean.TRUE.equals(desc.get("writable"))) { desc.put("writable", desc, Boolean.FALSE); } if (Boolean.TRUE.equals(desc.get("configurable"))) { desc.put("configurable", desc, Boolean.FALSE); } obj.defineOwnProperty(cx, key, desc, false); } } return true; } /** * Implement the ECMAScript abstract operation "SpeciesConstructor" defined in section 7.2.33 of * ECMA262. * * @param cx context * @param s the object for which we will find the "species constructor" as per the spec * @param defaultConstructor as per the spec, the value that will be returned if there is no * constructor on "s" or if the "species" symbol is not set. * @see */ static Constructable speciesConstructor( Context cx, Scriptable s, Constructable defaultConstructor) { /* The abstract operation SpeciesConstructor takes arguments O (an Object) and defaultConstructor (a constructor). It is used to retrieve the constructor that should be used to create new objects that are derived from O. defaultConstructor is the constructor to use if a constructor @@species property cannot be found starting from O. It performs the following steps when called: 1. Assert: Type(O) is Object. 2. Let C be ? Get(O, "constructor"). 3. If C is undefined, return defaultConstructor. 4. If Type(C) is not Object, throw a TypeError exception. 5. Let S be ? Get(C, @@species). 6. If S is either undefined or null, return defaultConstructor. 7. If IsConstructor(S) is true, return S. 8. Throw a TypeError exception. */ Object constructor = ScriptableObject.getProperty(s, "constructor"); if (constructor == Scriptable.NOT_FOUND || Undefined.isUndefined(constructor)) { return defaultConstructor; } if (!ScriptRuntime.isObject(constructor)) { throw ScriptRuntime.typeErrorById( "msg.arg.not.object", ScriptRuntime.typeof(constructor)); } Object species = ScriptableObject.getProperty((Scriptable) constructor, SymbolKey.SPECIES); if (species == Scriptable.NOT_FOUND || species == null || Undefined.isUndefined(species)) { return defaultConstructor; } if (!(species instanceof Constructable)) { throw ScriptRuntime.typeErrorById("msg.not.ctor", ScriptRuntime.typeof(species)); } return (Constructable) species; } /** * Set ( O, P, V, Throw) * *

https://262.ecma-international.org/12.0/#sec-set-o-p-v-throw */ static void put(Context cx, Scriptable o, String p, Object v, boolean isThrow) { Scriptable base = ScriptableObject.getBase(o, p); if (base == null) base = o; if (base instanceof ScriptableObject) { if (((ScriptableObject) base).putImpl(p, 0, o, v, isThrow)) return; o.put(p, o, v); } else { base.put(p, o, v); } } /** * Set ( O, P, V, Throw) * *

https://262.ecma-international.org/12.0/#sec-set-o-p-v-throw */ static void put(Context cx, Scriptable o, int p, Object v, boolean isThrow) { Scriptable base = ScriptableObject.getBase(o, p); if (base == null) base = o; if (base instanceof ScriptableObject) { if (((ScriptableObject) base).putImpl(null, p, o, v, isThrow)) return; o.put(p, o, v); } else { base.put(p, o, v); } } /** * CreateListFromArrayLike ( obj [ , elementTypes ] ) * *

https://262.ecma-international.org/12.0/#sec-createlistfromarraylike */ static List createListFromArrayLike( Context cx, Scriptable o, Predicate elementTypesPredicate, String msg) { ScriptableObject obj = ScriptableObject.ensureScriptableObject(o); if (obj instanceof NativeArray) { Object[] arr = ((NativeArray) obj).toArray(); for (Object next : arr) { if (!elementTypesPredicate.test(next)) { throw ScriptRuntime.typeError(msg); } } return Arrays.asList(arr); } long len = lengthOfArrayLike(cx, obj); List list = new ArrayList<>(); long index = 0; while (index < len) { // String indexName = ScriptRuntime.toString(index); Object next = ScriptableObject.getProperty(obj, (int) index); if (!elementTypesPredicate.test(next)) { throw ScriptRuntime.typeError(msg); } list.add(next); index++; } return list; } /** * LengthOfArrayLike ( obj ) * *

https://262.ecma-international.org/12.0/#sec-lengthofarraylike */ static long lengthOfArrayLike(Context cx, Scriptable o) { Object value = ScriptableObject.getProperty(o, "length"); long len = ScriptRuntime.toLength(new Object[] {value}, 0); return len; } /** * IsCompatiblePropertyDescriptor ( Extensible, Desc, Current ) * *

https://262.ecma-international.org/12.0/#sec-iscompatiblepropertydescriptor */ static boolean isCompatiblePropertyDescriptor( boolean extensible, ScriptableObject desc, ScriptableObject current) { return validateAndApplyPropertyDescriptor( Undefined.SCRIPTABLE_UNDEFINED, Undefined.SCRIPTABLE_UNDEFINED, extensible, desc, current); } /** * ValidateAndApplyPropertyDescriptor ( O, P, extensible, Desc, current ) * *

https://262.ecma-international.org/12.0/#sec-validateandapplypropertydescriptor */ static boolean validateAndApplyPropertyDescriptor( Scriptable o, Scriptable p, boolean extensible, ScriptableObject desc, ScriptableObject current) { if (Undefined.isUndefined(current)) { if (!extensible) { return false; } if (ScriptableObject.isGenericDescriptor(desc) || ScriptableObject.isDataDescriptor(desc)) { /* i. i. If O is not undefined, create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]], and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. */ } else { /* ii. ii. If O is not undefined, create an own accessor property named P of object O whose [[Get]], [[Set]], [[Enumerable]], and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. */ } return true; } if (desc.getIds().length == 0) { return true; } if (Boolean.FALSE.equals(current.get("configurable"))) { if (Boolean.TRUE.equals(ScriptableObject.hasProperty(desc, "configurable")) && Boolean.TRUE.equals(desc.get("configurable"))) { return false; } if (Boolean.TRUE.equals(ScriptableObject.hasProperty(desc, "enumerable")) && !Objects.equals(desc.get("enumerable"), current.get("enumerable"))) { return false; } } // if (!ScriptableObject.isGenericDescriptor(desc)) { if (ScriptableObject.isGenericDescriptor(desc)) { return true; } else if (!Objects.equals( ScriptableObject.isGenericDescriptor(current), ScriptableObject.isGenericDescriptor(desc))) { if (Boolean.FALSE.equals(current.get("configurable"))) { return false; } if (ScriptableObject.isDataDescriptor(current)) { if (Boolean.FALSE.equals(current.get("configurable"))) { // i. i. If O is not undefined, convert the property named P of object O from a // data property to an accessor property. Preserve the existing values of the // converted property's [[Configurable]] and [[Enumerable]] attributes and set // the rest of the property's attributes to their default values. } else { // i. i. If O is not undefined, convert the property named P of object O from an // accessor property to a data property. Preserve the existing values of the // converted property's [[Configurable]] and [[Enumerable]] attributes and set // the rest of the property's attributes to their default values. } } } else if (ScriptableObject.isDataDescriptor(current) && ScriptableObject.isDataDescriptor(desc)) { if (Boolean.FALSE.equals(current.get("configurable")) && Boolean.FALSE.equals(current.get("writable"))) { if (Boolean.TRUE.equals(ScriptableObject.hasProperty(desc, "writable")) && Boolean.TRUE.equals(desc.get("writable"))) { return false; } if (Boolean.TRUE.equals(ScriptableObject.hasProperty(desc, "value")) && !Objects.equals(desc.get("value"), current.get("value"))) { return false; } return true; } } else { if (Boolean.FALSE.equals(current.get("configurable"))) { if (Boolean.TRUE.equals(ScriptableObject.hasProperty(desc, "set")) && !Objects.equals(desc.get("set"), current.get("set"))) { return false; } if (Boolean.TRUE.equals(ScriptableObject.hasProperty(desc, "get")) && !Objects.equals(desc.get("get"), current.get("get"))) { return false; } return true; } } return true; } }