diff --git a/lib/Runtime/Library/JavascriptBuiltInFunctionList.h b/lib/Runtime/Library/JavascriptBuiltInFunctionList.h
index f2690171248..c794e5d1e8a 100644
--- a/lib/Runtime/Library/JavascriptBuiltInFunctionList.h
+++ b/lib/Runtime/Library/JavascriptBuiltInFunctionList.h
@@ -177,6 +177,8 @@ BUILTIN(JavascriptObject, LookupGetter, EntryLookupGetter, FunctionInfo::ErrorOn
BUILTIN(JavascriptObject, LookupSetter, EntryLookupSetter, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptObject, Is, EntryIs, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptObject, Assign, EntryAssign, FunctionInfo::ErrorOnNew)
+BUILTIN(JavascriptObject, Values, EntryValues, FunctionInfo::ErrorOnNew)
+BUILTIN(JavascriptObject, Entries, EntryEntries, FunctionInfo::ErrorOnNew)
BUILTIN(ObjectPrototypeObject, __proto__getter, Entry__proto__getter, FunctionInfo::ErrorOnNew | FunctionInfo::DoNotProfile)
BUILTIN(ObjectPrototypeObject, __proto__setter, Entry__proto__setter, FunctionInfo::ErrorOnNew | FunctionInfo::DoNotProfile)
BUILTIN(JavascriptRegExp, NewInstance, NewInstance, FunctionInfo::SkipDefaultNewObject)
diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp
index 46ebc174d23..f0d89fe59d1 100644
--- a/lib/Runtime/Library/JavascriptLibrary.cpp
+++ b/lib/Runtime/Library/JavascriptLibrary.cpp
@@ -3602,6 +3602,14 @@ namespace Js
library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::assign, &JavascriptObject::EntryInfo::Assign, 2));
}
+ if (scriptContext->GetConfig()->IsES7BuiltinsEnabled())
+ {
+ scriptContext->SetBuiltInLibraryFunction(JavascriptObject::EntryInfo::Values.GetOriginalEntryPoint(),
+ library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::values, &JavascriptObject::EntryInfo::Values, 1));
+ scriptContext->SetBuiltInLibraryFunction(JavascriptObject::EntryInfo::Entries.GetOriginalEntryPoint(),
+ library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::entries, &JavascriptObject::EntryInfo::Entries, 1));
+ }
+
objectConstructor->SetHasNoEnumerableProperties(true);
}
@@ -6183,6 +6191,12 @@ namespace Js
REG_OBJECTS_LIB_FUNC(assign, JavascriptObject::EntryAssign);
}
+ if (config.IsES7BuiltinsEnabled())
+ {
+ REG_OBJECTS_LIB_FUNC(values, JavascriptObject::EntryValues);
+ REG_OBJECTS_LIB_FUNC(entries, JavascriptObject::EntryEntries);
+ }
+
return hr;
}
diff --git a/lib/Runtime/Library/JavascriptObject.cpp b/lib/Runtime/Library/JavascriptObject.cpp
index e23423385b9..35b590bf3fd 100644
--- a/lib/Runtime/Library/JavascriptObject.cpp
+++ b/lib/Runtime/Library/JavascriptObject.cpp
@@ -1096,6 +1096,92 @@ namespace Js
return JavascriptOperators::GetOwnEnumerablePropertyNames(object, scriptContext);
}
+ Var JavascriptObject::GetValuesOrEntries(RecyclableObject* object, bool valuesToReturn, ScriptContext* scriptContext)
+ {
+ Assert(object != nullptr);
+ Assert(scriptContext != nullptr);
+ JavascriptArray* valuesArray = scriptContext->GetLibrary()->CreateArray(0);
+
+ Var ownKeysVar = JavascriptOperators::GetOwnPropertyNames(object, scriptContext);
+ JavascriptArray* ownKeysResult = nullptr;
+ if (JavascriptArray::Is(ownKeysVar))
+ {
+ ownKeysResult = JavascriptArray::FromVar(ownKeysVar);
+ }
+ else
+ {
+ return valuesArray;
+ }
+
+ uint32 length = ownKeysResult->GetLength();
+
+ Var nextKey;
+ const PropertyRecord* propertyRecord = nullptr;
+ PropertyId propertyId;
+ for (uint32 i = 0, index = 0; i < length; i++)
+ {
+ nextKey = ownKeysResult->DirectGetItem(i);
+ Assert(JavascriptString::Is(nextKey));
+
+ PropertyDescriptor propertyDescriptor;
+
+ BOOL propertyKeyResult = JavascriptConversion::ToPropertyKey(nextKey, scriptContext, &propertyRecord);
+ Assert(propertyKeyResult);
+ propertyId = propertyRecord->GetPropertyId();
+ Assert(propertyId != Constants::NoProperty);
+ if (JavascriptOperators::GetOwnPropertyDescriptor(object, propertyId, scriptContext, &propertyDescriptor))
+ {
+ if (propertyDescriptor.IsEnumerable())
+ {
+ Var value = JavascriptOperators::GetProperty(object, propertyId, scriptContext);
+ if (!valuesToReturn)
+ {
+ // For Object.entries each entry is key, value pair
+ JavascriptArray* entry = scriptContext->GetLibrary()->CreateArray(2);
+ entry->DirectSetItemAt(0, CrossSite::MarshalVar(scriptContext, nextKey));
+ entry->DirectSetItemAt(1, CrossSite::MarshalVar(scriptContext, value));
+ value = entry;
+ }
+ valuesArray->DirectSetItemAt(index++, CrossSite::MarshalVar(scriptContext, value));
+ }
+ }
+ }
+
+ return valuesArray;
+ }
+
+ Var JavascriptObject::EntryValues(RecyclableObject* function, CallInfo callInfo, ...)
+ {
+ PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
+
+ ARGUMENTS(args, callInfo);
+ ScriptContext* scriptContext = function->GetScriptContext();
+
+ Assert(!(callInfo.Flags & CallFlags_New));
+ CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(ObjectValuesCount);
+
+ Var tempVar = args.Info.Count < 2 ? scriptContext->GetLibrary()->GetUndefined() : args[1];
+ RecyclableObject *object = RecyclableObject::FromVar(JavascriptOperators::ToObject(tempVar, scriptContext));
+
+ return GetValuesOrEntries(object, true /*valuesToReturn*/, scriptContext);
+ }
+
+ Var JavascriptObject::EntryEntries(RecyclableObject* function, CallInfo callInfo, ...)
+ {
+ PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
+
+ ARGUMENTS(args, callInfo);
+ ScriptContext* scriptContext = function->GetScriptContext();
+
+ Assert(!(callInfo.Flags & CallFlags_New));
+ CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(ObjectEntriesCount);
+
+ Var tempVar = args.Info.Count < 2 ? scriptContext->GetLibrary()->GetUndefined() : args[1];
+ RecyclableObject *object = RecyclableObject::FromVar(JavascriptOperators::ToObject(tempVar, scriptContext));
+
+ return GetValuesOrEntries(object, false /*valuesToReturn*/, scriptContext);
+ }
+
Var JavascriptObject::CreateOwnSymbolPropertiesHelper(RecyclableObject* object, ScriptContext* scriptContext)
{
return CreateKeysHelper(object, scriptContext, TRUE, true /*includeSymbolsOnly */, false, true /*includeSpecialProperties*/);
diff --git a/lib/Runtime/Library/JavascriptObject.h b/lib/Runtime/Library/JavascriptObject.h
index 8757863cb92..c7abddc8520 100644
--- a/lib/Runtime/Library/JavascriptObject.h
+++ b/lib/Runtime/Library/JavascriptObject.h
@@ -47,6 +47,8 @@ namespace Js
static FunctionInfo LookupSetter;
static FunctionInfo Is;
static FunctionInfo Assign;
+ static FunctionInfo Values;
+ static FunctionInfo Entries;
};
static Var NewInstance(RecyclableObject* function, CallInfo callInfo, ...);
@@ -77,7 +79,8 @@ namespace Js
static Var EntryLookupSetter(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryIs(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryAssign(RecyclableObject* function, CallInfo callInfo, ...);
-
+ static Var EntryValues(RecyclableObject* function, CallInfo callInfo, ...);
+ static Var EntryEntries(RecyclableObject* function, CallInfo callInfo, ...);
static Var GetPrototypeOf(RecyclableObject* obj, ScriptContext* scriptContext);
static BOOL ChangePrototype(RecyclableObject* object, RecyclableObject* newPrototype, bool validate, ScriptContext* scriptContext);
@@ -91,6 +94,9 @@ namespace Js
static Var GetOwnPropertyDescriptorHelper(RecyclableObject* obj, Var propertyKey, ScriptContext* scriptContext);
static BOOL GetOwnPropertyDescriptorHelper(RecyclableObject* obj, PropertyId propertyId, ScriptContext* scriptContext, PropertyDescriptor& propertyDescriptor);
+ // Param valuesToReturn should be set to true when we are looking for values from an object otherwise entries will be returned
+ static Var GetValuesOrEntries(RecyclableObject* object, bool valuesToReturn, ScriptContext* scriptContext);
+
// Presently used in the projection as a mechanism of calling the general object prototype toString.
static JavascriptString* ToStringInternal(Var thisArg, ScriptContext* scriptContext)
{
diff --git a/test/es7/rlexe.xml b/test/es7/rlexe.xml
index d69918d067d..1f206e6da01 100644
--- a/test/es7/rlexe.xml
+++ b/test/es7/rlexe.xml
@@ -38,4 +38,11 @@
-es7asyncawait -es6tostringtag -args summary -endargs
+
+
+ valuesAndEntries.js
+ -ES7Builtins -args summary -endargs
+ BugFix
+
+
diff --git a/test/es7/valuesAndEntries.js b/test/es7/valuesAndEntries.js
new file mode 100644
index 00000000000..5dd28bff24a
--- /dev/null
+++ b/test/es7/valuesAndEntries.js
@@ -0,0 +1,226 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+
+var tests = [
+ {
+ name: "Object.values/entries should exist and constructed properly",
+ body: function () {
+ assert.isTrue(Object.hasOwnProperty('values'), "Object should have a values method");
+ assert.isTrue(Object.hasOwnProperty('entries'), "Object should have a entries method");
+ assert.areEqual(1, Object.values.length, "values method takes one argument");
+ assert.areEqual(1, Object.entries.length, "entries method takes one argument");
+ assert.areEqual("values", Object.values.name, "values.name is 'values'");
+ assert.areEqual("entries", Object.entries.name, "entries.name is 'entries'");
+
+ var descriptor = Object.getOwnPropertyDescriptor(Object, 'values');
+ assert.isTrue(descriptor.writable, "writable(values) must be true");
+ assert.isFalse(descriptor.enumerable, "enumerable(values) must be false");
+ assert.isTrue(descriptor.configurable, "configurable(values) must be true");
+
+ descriptor = Object.getOwnPropertyDescriptor(Object, 'entries');
+ assert.isTrue(descriptor.writable, "writable(entries) must be true");
+ assert.isFalse(descriptor.enumerable, "enumerable(entries) must be false");
+ assert.isTrue(descriptor.configurable, "configurable(entries) must be true");
+ }
+ },
+ {
+ name: "Object.values/entries basic syntax",
+ body: function () {
+ assert.throws(function () { eval("Object.values();"); }, TypeError, "Calling values method without any arguments throws an error", "Object expected");
+ assert.throws(function () { eval("Object.values(undefined);"); }, TypeError, "Calling values method on undefined throws an error", "Object expected");
+ assert.throws(function () { eval("Object.values(null);"); }, TypeError, "Calling values method on null throws an error", "Object expected");
+ assert.isTrue(Array.isArray(Object.values({})), "calling values method on an object returns an array");
+ assert.areEqual(0, Object.values({}).length, "calling values method on an empty object returns an empty array");
+
+ assert.throws(function () { eval("Object.entries();"); }, TypeError, "Calling entries method without any arguments throws an error", "Object expected");
+ assert.throws(function () { eval("Object.entries(undefined);"); }, TypeError, "Calling entries method on undefined throws an error", "Object expected");
+ assert.throws(function () { eval("Object.entries(null);"); }, TypeError, "Calling entries method on null throws an error", "Object expected");
+ assert.isTrue(Array.isArray(Object.entries({})), "calling entries method on an object returns an array");
+ assert.areEqual(0, Object.entries({}).length, "calling entries method on an empty object returns an empty array");
+ }
+ },
+ {
+ name: "Object.values/entries functionality",
+ body: function () {
+ var a1 = {prop1:10, prop2:20};
+ var values = Object.values(a1);
+ assert.areEqual(2, values.length, "calling values on an object with two properties will returned an array of 2 elements");
+ assert.areEqual(10, values[0], "First element of the returned values array should be 10");
+ assert.areEqual(20, values[1], "Second element of the returned values array should be 20");
+
+ var entries = Object.entries(a1);
+ assert.areEqual(2, entries.length, "calling entries on an object with two properties will returned an array of 2 elements");
+ assert.isTrue(Array.isArray(entries[0]) && Array.isArray(entries[1]), "each element itself an array of key, value pair");
+ assert.areEqual(["prop1", 10], entries[0], "First element of the returned entry array should be ['prop1', 10]");
+ assert.areEqual(["prop2", 20], entries[1], "Second element of the returned entry array should be ['prop2', 20]");
+
+ var a2 = {prop3 : 30};
+ a2[2] = 40;
+ a2["prop4"] = 50;
+ Object.defineProperty(a2, "prop5", { value: 60, enumerable: true});
+ Object.defineProperty(a2, "prop6", { value: 70, enumerable: false});
+ Object.defineProperty(a2, 'prop7', { enumerable: true, get: function () { return 80;}});
+ var sym = Symbol('prop8');
+ a2[sym] = 90;
+
+ values = Object.values(a2);
+ assert.areEqual(5, values.length, "values method returns an array of 5 elements, symbol and non-enumerable should be excluded");
+ assert.areEqual([40,30,50,60,80], values, "values method returns an array and matches correctly");
+
+ entries = Object.entries(a2);
+ assert.areEqual(5, entries.length, "entries method returns an array of 5 elements, symbol and non-enumerable should be excluded");
+ assert.areEqual("2,40,prop3,30,prop4,50,prop5,60,prop7,80", entries.toString(), "entries method returns an array and matches correctly");
+ }
+ },
+ {
+ name: "Object.values/entries with proxy",
+ body: function () {
+ var obj1 = {prop1:10};
+ var proxy1 = new Proxy(obj1, { });
+ var values = Object.values(proxy1);
+ assert.areEqual(1, values.length, "values - Proxy object on an object with one property returns an array of 1 element");
+ assert.areEqual(10, values[0], "values - Proxy object on an object with one property returns correct element");
+
+ var entries = Object.entries(proxy1);
+ assert.areEqual(1, entries.length, "entries - Proxy object on an object with one property returns an array of 1 element");
+ assert.areEqual(["prop1", 10], entries[0], "entries - Proxy object on an object with one property returns correct element");
+
+ var obj2 = {};
+ Object.defineProperty(obj2, "prop2", { value: 20, enumerable: true });
+ Object.defineProperty(obj2, "prop3", { get: function () { return 30; }, enumerable: true });
+ var proxy2 = new Proxy(obj2, {
+ getOwnPropertyDescriptor: function (target, propert) {
+ return Reflect.getOwnPropertyDescriptor(target, propert);
+ }
+ });
+
+ values = Object.values(proxy2);
+ assert.areEqual(2, values.length, "values - exhibiting a Proxy trapping getOwnPropertyDescriptor returns an aray to 2 elements");
+ assert.areEqual(20, values[0], "values - a Proxy trapping getOwnPropertyDescriptor matching the first element");
+ assert.areEqual(30, values[1], "values - a Proxy trapping getOwnPropertyDescriptor matching the second element");
+
+ entries = Object.entries(proxy2);
+ assert.areEqual(2, entries.length, "values - exhibiting a Proxy trapping getOwnPropertyDescriptor returns an aray to 2 elements");
+ assert.areEqual(["prop2", 20], entries[0], "entries - a Proxy trapping getOwnPropertyDescriptor matching the first element");
+ assert.areEqual(["prop3", 30], entries[1], "entries - a Proxy trapping getOwnPropertyDescriptor matching the second element");
+
+ var obj3 = {};
+ var count = 0;
+ var proxy3 = new Proxy(obj3, {
+ get: function (target, property, receiver) {
+ return count++ * 5;
+ },
+ getOwnPropertyDescriptor: function (target, property) {
+ return {configurable: true, enumerable: true};
+ },
+
+ ownKeys: function (target) {
+ return ["prop0", "prop1", Symbol("prop2"), Symbol("prop5")];
+ }
+ });
+
+ values = Object.values(proxy3);
+ assert.areEqual(2, values.length, "values - exhibiting a Proxy with get and ownKeys traps - returns 2 elements");
+ assert.areEqual(0, values[0], "values - exhibiting a Proxy with get and ownKeys traps - matching first element");
+ assert.areEqual(5, values[1], "values - exhibiting a Proxy with get and ownKeys traps - matching second element");
+
+ entries = Object.entries(proxy3);
+ assert.areEqual(2, entries.length, "entries - exhibiting a Proxy with get and ownKeys trap - returns 2 elements");
+ assert.areEqual(["prop0", 10], entries[0], "entries - exhibiting a Proxy with get and ownKeys trap - matching first element");
+ assert.areEqual(["prop1", 15], entries[1], "entries - exhibiting a Proxy with get and ownKeys trap - matching second element");
+
+ }
+ },
+ {
+ name: "Object.values - deleting or making non-enumerable during enumeration",
+ body: function () {
+ var aDeletesB = {
+ get a() {
+ delete this.b;
+ return 1;
+ },
+ b: 2
+ };
+
+ var values = Object.values(aDeletesB);
+ assert.areEqual(1, values.length, "deleting next key during enumeration is excluded in the result");
+ assert.areEqual(1, values[0], "deleting next key - first element is 1");
+
+ var aRemovesB = {
+ get a() {
+ Object.defineProperty(this, 'b', {
+ enumerable: false
+ });
+ return 1;
+ },
+ b: 2
+ };
+
+ values = Object.values(aRemovesB);
+ assert.areEqual(1, values.length, "making the next key non-enumerable is excluded in the result");
+ assert.areEqual(1, values[0], "making next nonenumerable - first element is 1");
+ }
+ },
+ {
+ name: "Object.entries - deleting or making non-enumerable during enumeration",
+ body: function () {
+ var aDeletesB = {
+ get a() {
+ delete this.b;
+ return 1;
+ },
+ b: 2
+ };
+
+ var entries = Object.entries(aDeletesB);
+ assert.areEqual(1, entries.length, "deleting next key during enumeration is excluded in the result");
+ assert.areEqual(['a', 1], entries[0], "deleting next key - first element is 1");
+
+ var aRemovesB = {
+ get a() {
+ Object.defineProperty(this, 'b', {
+ enumerable: false
+ });
+ return 1;
+ },
+ b: 2
+ };
+
+ entries = Object.entries(aRemovesB);
+ assert.areEqual(1, entries.length, "making the next key non-enumerable is excluded in the result");
+ assert.areEqual(['a', 1], entries[0], "making next nonenumerable - first element is 1");
+ }
+ },
+ {
+ name: "Object.values - making non-enumerable to enumerable during enumeration",
+ body: function () {
+ var aAddsB = {};
+ Object.defineProperty(aAddsB, "a", { get: function () { Object.defineProperty(this, 'b', { enumerable: true }); return 1; }, enumerable: true });
+ Object.defineProperty(aAddsB, "b", { value: 2, configurable:true, enumerable: false});
+
+ var values = Object.values(aAddsB);
+ assert.areEqual(2, values.length, "making the second non-enumerable key to enumerable is included in the result");
+ assert.areEqual(1, values[0], "non-enumerable to enumerable - first element is 1");
+ assert.areEqual(2, values[1], "non-enumerable to enumerable - second element is 2");
+ }
+ },
+ {
+ name: "Object.entries - making non-enumerable to enumerable during enumeration",
+ body: function () {
+ var aAddsB = {};
+ Object.defineProperty(aAddsB, "a", { get: function () { Object.defineProperty(this, 'b', { enumerable:true }); return 1; }, enumerable: true });
+ Object.defineProperty(aAddsB, "b", { value: 2, configurable:true, enumerable: false});
+
+ var entries = Object.entries(aAddsB);
+ assert.areEqual(2, entries.length, "making the second non-enumerable key to enumerable is included in the result");
+ assert.areEqual(['a', 1], entries[0], "non-enumerable to enumerable - first element is 1");
+ assert.areEqual(['b', 2], entries[1], "non-enumerable to enumerable - second element is 2");
+ }
+ }
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });