diff --git a/src/org/mozilla/javascript/AbstractEcmaObjectOperations.java b/src/org/mozilla/javascript/AbstractEcmaObjectOperations.java index 40913ea25d..a06fd8d639 100644 --- a/src/org/mozilla/javascript/AbstractEcmaObjectOperations.java +++ b/src/org/mozilla/javascript/AbstractEcmaObjectOperations.java @@ -24,6 +24,32 @@ enum INTEGRITY_LEVEL { 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) { + ScriptableObject obj = ScriptableObject.ensureScriptableObject(o); + boolean result; + if (property instanceof Symbol) { + result = ScriptableObject.ensureSymbolScriptable(o).has((Symbol) property, obj); + } else { + ScriptRuntime.StringIdOrIndex s = ScriptRuntime.toStringIdOrIndex(cx, 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 * diff --git a/src/org/mozilla/javascript/NativeObject.java b/src/org/mozilla/javascript/NativeObject.java index bc85d57848..19feee39a5 100644 --- a/src/org/mozilla/javascript/NativeObject.java +++ b/src/org/mozilla/javascript/NativeObject.java @@ -51,6 +51,7 @@ protected void fillConstructorProperties(IdFunctionObject ctor) { addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_entries, "entries", 1); addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_fromEntries, "fromEntries", 1); addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_values, "values", 1); + addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_hasOwn, "hasOwn", 1); } addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_keys, "keys", 1); addIdFunctionProperty( @@ -203,19 +204,10 @@ public Object execIdCall( throw ScriptRuntime.typeErrorById( "msg." + (thisObj == null ? "null" : "undef") + ".to.object"); } - boolean result; + Object arg = args.length < 1 ? Undefined.instance : args[0]; - if (arg instanceof Symbol) { - result = ensureSymbolScriptable(thisObj).has((Symbol) arg, thisObj); - } else { - StringIdOrIndex s = ScriptRuntime.toStringIdOrIndex(cx, arg); - if (s.stringId == null) { - result = thisObj.has(s.index, thisObj); - } else { - result = thisObj.has(s.stringId, thisObj); - } - } - return ScriptRuntime.wrapBoolean(result); + + return AbstractEcmaObjectOperations.hasOwnProperty(cx, thisObj, arg); } case Id_propertyIsEnumerable: @@ -472,6 +464,12 @@ public Object execIdCall( } return cx.newArray(scope, ids); } + case ConstructorId_hasOwn: + { + Object arg = args.length < 1 ? Undefined.instance : args[0]; + Object propertyName = args.length < 2 ? Undefined.instance : args[1]; + return AbstractEcmaObjectOperations.hasOwnProperty(cx, arg, propertyName); + } case ConstructorId_getOwnPropertyNames: { Object arg = args.length < 1 ? Undefined.instance : args[0]; @@ -998,6 +996,7 @@ protected int findPrototypeId(String s) { ConstructorId_entries = -18, ConstructorId_fromEntries = -19, ConstructorId_values = -20, + ConstructorId_hasOwn = -21, Id_constructor = 1, Id_toString = 2, Id_toLocaleString = 3, diff --git a/testsrc/org/mozilla/javascript/tests/es2022/NativeObjectTest.java b/testsrc/org/mozilla/javascript/tests/es2022/NativeObjectTest.java new file mode 100644 index 0000000000..4ee42f8e24 --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/es2022/NativeObjectTest.java @@ -0,0 +1,150 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** Test for the Object.hasOwn */ +package org.mozilla.javascript.tests.es2022; + +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ScriptableObject; + +public class NativeObjectTest { + + private Context cx; + private ScriptableObject scope; + + @Before + public void setUp() { + cx = Context.enter(); + cx.setLanguageVersion(Context.VERSION_ES6); + scope = cx.initStandardObjects(); + } + + @After + public void tearDown() { + Context.exit(); + } + + @Test + public void testHasStringOwn() { + Object result = + cx.evaluateString( + scope, + "let result = Object.hasOwn({ test: '123' }, 'test');\n" + + "'result = ' + result", + "test", + 1, + null); + + assertEquals("result = true", result); + } + + @Test + public void testHasUndefinedOwn() { + Object result = + cx.evaluateString( + scope, + "let result = Object.hasOwn({ test: undefined }, 'test');\n" + + "'result = ' + result;", + "test", + 1, + null); + + assertEquals("result = true", result); + } + + @Test + public void testHasNullOwn() { + Object result = + cx.evaluateString( + scope, + "let result = Object.hasOwn({ test: null }, 'test');\n" + + "'result = ' + result;", + "test", + 1, + null); + + assertEquals("result = true", result); + } + + @Test + public void testHasArrayPropertyOwn() { + Object result = + cx.evaluateString( + scope, + "let dessert = [\"cake\", \"coffee\", \"chocolate\"];\n" + + "let result = Object.hasOwn(dessert, 2);\n" + + "'result = ' + result;", + "test", + 1, + null); + + assertEquals("result = true", result); + } + + @Test + public void testHasNoOwn() { + Object result = + cx.evaluateString( + scope, + "let result = Object.hasOwn({ cake: 123 }, 'test');\n" + + "'result = ' + result", + "test", + 1, + null); + + assertEquals("result = false", result); + } + + @Test + public void testCreateHasOwn() { + Object result = + cx.evaluateString( + scope, + "var foo = Object.create(null);\n" + + "foo.prop = 'test';\n" + + "var result = Object.hasOwn(foo, 'prop');\n" + + "'result = ' + result;", + "test", + 1, + null); + + assertEquals("result = true", result); + } + + @Test + public void testCreateNoHasOwn() { + Object result = + cx.evaluateString( + scope, + "var result = Object.hasOwn(Object.create({ q: 321 }), 'q');\n" + + "'result = ' + result; ", + "test", + 1, + null); + + assertEquals("result = false", result); + } + + @Test + public void testCalledTest() { + Object result = + cx.evaluateString( + scope, + "var called = false;\n" + + "try {\n" + + " Object.hasOwn(null, { toString() { called = true } });\n" + + "} catch (e) {}\n" + + "'called = ' + called;", + "test", + 1, + null); + + assertEquals("called = false", result); + } +}