diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index bd91b3d827..09a1ffacfc 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -152,6 +152,12 @@ "the type of each result list element is either String or Symbol") \ M(ProxyOwnPropertyKeysDuplicates, "Proxy handler's ownKeys trap violates invariant: " \ "the result list may not contain duplicate elements") \ + M(ProxyOwnPropertyKeysSkippedNonconfigurableProperty, "Proxy handler's ownKeys trap violates invariant: " \ + "cannot skip non-configurable property '{}'") \ + M(ProxyOwnPropertyKeysNonExtensibleSkippedProperty, "Proxy handler's ownKeys trap violates invariant: " \ + "cannot skip property '{}' of non-extensible object") \ + M(ProxyOwnPropertyKeysNonExtensibleNewProperty, "Proxy handler's ownKeys trap violates invariant: " \ + "cannot report new property '{}' of non-extensible object") \ M(ProxyPreventExtensionsReturn, "Proxy handler's preventExtensions trap violates " \ "invariant: cannot return true if the target object is extensible") \ M(ProxyRevoked, "An operation was performed on a revoked Proxy object") \ diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp index 2a47c2909f..1390ebb6c9 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -728,7 +728,7 @@ ThrowCompletionOr ProxyObject::internal_own_property_keys() con for (auto& key : target_nonconfigurable_keys) { // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. if (!unchecked_result_keys.contains_slow(key)) - return vm.throw_completion(global_object, ErrorType::FixmeAddAnErrorString); + return vm.throw_completion(global_object, ErrorType::ProxyOwnPropertyKeysSkippedNonconfigurableProperty, key.to_string_without_side_effects()); // b. Remove key from uncheckedResultKeys. unchecked_result_keys.remove_first_matching([&](auto& value) { @@ -744,7 +744,7 @@ ThrowCompletionOr ProxyObject::internal_own_property_keys() con for (auto& key : target_configurable_keys) { // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. if (!unchecked_result_keys.contains_slow(key)) - return vm.throw_completion(global_object, ErrorType::FixmeAddAnErrorString); + return vm.throw_completion(global_object, ErrorType::ProxyOwnPropertyKeysNonExtensibleSkippedProperty, key.to_string_without_side_effects()); // b. Remove key from uncheckedResultKeys. unchecked_result_keys.remove_first_matching([&](auto& value) { @@ -754,7 +754,7 @@ ThrowCompletionOr ProxyObject::internal_own_property_keys() con // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. if (!unchecked_result_keys.is_empty()) - return vm.throw_completion(global_object, ErrorType::FixmeAddAnErrorString); + return vm.throw_completion(global_object, ErrorType::ProxyOwnPropertyKeysNonExtensibleNewProperty, unchecked_result_keys[0].to_string_without_side_effects()); // 23. Return trapResult. return { move(trap_result) }; diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-ownKeys.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-ownKeys.js new file mode 100644 index 0000000000..6dba73c337 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-ownKeys.js @@ -0,0 +1,56 @@ +// TODO: Add "[[OwnPropertyKeys]] trap normal behavior" tests + +describe("[[OwnPropertyKeys]] invariants", () => { + // TODO: Add tests for other [[OwnPropertyKeys]] trap invariants + + test("cannot report new property of non-extensible object", () => { + const target = Object.preventExtensions({}); + const handler = { + ownKeys() { + return ["foo", "bar", "baz"]; + }, + }; + const proxy = new Proxy(target, handler); + + expect(() => { + Reflect.ownKeys(proxy); + }).toThrowWithMessage( + TypeError, + "Proxy handler's ownKeys trap violates invariant: cannot report new property 'foo' of non-extensible object" + ); + }); + + test("cannot skip property of non-extensible object", () => { + const target = Object.preventExtensions({ foo: null }); + const handler = { + ownKeys() { + return ["bar", "baz"]; + }, + }; + const proxy = new Proxy(target, handler); + + expect(() => { + Reflect.ownKeys(proxy); + }).toThrowWithMessage( + TypeError, + "Proxy handler's ownKeys trap violates invariant: cannot skip property 'foo' of non-extensible object" + ); + }); + + test("cannot skip non-configurable property", () => { + const target = Object.defineProperty({}, "foo", { configurable: false }); + const handler = { + ownKeys() { + return ["bar", "baz"]; + }, + }; + const proxy = new Proxy(target, handler); + + expect(() => { + Reflect.ownKeys(proxy); + }).toThrowWithMessage( + TypeError, + "Proxy handler's ownKeys trap violates invariant: cannot skip non-configurable property 'foo'" + ); + }); +});