Przeglądaj źródła

LibJS: Add the HostEnsureCanAddPrivateElement hook

This hook allows us to reject private elements on certain exotic
objects like the window object in browser.
Note that per the spec we should only call this hook if the host is a
web browser, however because LibJS has no way of knowing whether it is
in a web browser environment we just always call the host hook.
davidot 2 lat temu
rodzic
commit
c4f3d44be1

+ 26 - 0
Userland/Libraries/LibJS/Runtime/Object.cpp

@@ -462,23 +462,49 @@ PrivateElement* Object::private_element_find(PrivateName const& name)
 // 7.3.28 PrivateFieldAdd ( O, P, value ), https://tc39.es/ecma262/#sec-privatefieldadd
 // 7.3.28 PrivateFieldAdd ( O, P, value ), https://tc39.es/ecma262/#sec-privatefieldadd
 ThrowCompletionOr<void> Object::private_field_add(PrivateName const& name, Value value)
 ThrowCompletionOr<void> Object::private_field_add(PrivateName const& name, Value value)
 {
 {
+    // 1. If the host is a web browser, then
+    //    a. Perform ? HostEnsureCanAddPrivateElement(O).
+    // NOTE: Since LibJS has no way of knowing whether it is in a browser we just always call the hook.
+    TRY(vm().host_ensure_can_add_private_element(*this));
+
+    // 2. Let entry be PrivateElementFind(O, P).
+    // 3. If entry is not empty, throw a TypeError exception.
     if (auto* entry = private_element_find(name); entry)
     if (auto* entry = private_element_find(name); entry)
         return vm().throw_completion<TypeError>(global_object(), ErrorType::PrivateFieldAlreadyDeclared, name.description);
         return vm().throw_completion<TypeError>(global_object(), ErrorType::PrivateFieldAlreadyDeclared, name.description);
+
     if (!m_private_elements)
     if (!m_private_elements)
         m_private_elements = make<Vector<PrivateElement>>();
         m_private_elements = make<Vector<PrivateElement>>();
+
+    // 4. Append PrivateElement { [[Key]]: P, [[Kind]]: field, [[Value]]: value } to O.[[PrivateElements]].
     m_private_elements->empend(name, PrivateElement::Kind::Field, value);
     m_private_elements->empend(name, PrivateElement::Kind::Field, value);
+
+    // 5. Return unused.
     return {};
     return {};
 }
 }
 
 
 // 7.3.29 PrivateMethodOrAccessorAdd ( O, method ), https://tc39.es/ecma262/#sec-privatemethodoraccessoradd
 // 7.3.29 PrivateMethodOrAccessorAdd ( O, method ), https://tc39.es/ecma262/#sec-privatemethodoraccessoradd
 ThrowCompletionOr<void> Object::private_method_or_accessor_add(PrivateElement element)
 ThrowCompletionOr<void> Object::private_method_or_accessor_add(PrivateElement element)
 {
 {
+    // 1. Assert: method.[[Kind]] is either method or accessor.
     VERIFY(element.kind == PrivateElement::Kind::Method || element.kind == PrivateElement::Kind::Accessor);
     VERIFY(element.kind == PrivateElement::Kind::Method || element.kind == PrivateElement::Kind::Accessor);
+
+    // 2. If the host is a web browser, then
+    //    a. Perform ? HostEnsureCanAddPrivateElement(O).
+    // NOTE: Since LibJS has no way of knowing whether it is in a browser we just always call the hook.
+    TRY(vm().host_ensure_can_add_private_element(*this));
+
+    // 3. Let entry be PrivateElementFind(O, method.[[Key]]).
+    // 4. If entry is not empty, throw a TypeError exception.
     if (auto* entry = private_element_find(element.key); entry)
     if (auto* entry = private_element_find(element.key); entry)
         return vm().throw_completion<TypeError>(global_object(), ErrorType::PrivateFieldAlreadyDeclared, element.key.description);
         return vm().throw_completion<TypeError>(global_object(), ErrorType::PrivateFieldAlreadyDeclared, element.key.description);
+
     if (!m_private_elements)
     if (!m_private_elements)
         m_private_elements = make<Vector<PrivateElement>>();
         m_private_elements = make<Vector<PrivateElement>>();
+
+    // 5. Append method to O.[[PrivateElements]].
     m_private_elements->append(move(element));
     m_private_elements->append(move(element));
+
+    // 6. Return unused.
     return {};
     return {};
 }
 }
 
 

+ 15 - 0
Userland/Libraries/LibJS/Runtime/VM.cpp

@@ -124,6 +124,21 @@ VM::VM(OwnPtr<CustomData> custom_data)
         return {};
         return {};
     };
     };
 
 
+    host_ensure_can_add_private_element = [](Object&) -> ThrowCompletionOr<void> {
+        // The host-defined abstract operation HostEnsureCanAddPrivateElement takes argument O (an Object)
+        // and returns either a normal completion containing unused or a throw completion.
+        // It allows host environments to prevent the addition of private elements to particular host-defined exotic objects.
+        // An implementation of HostEnsureCanAddPrivateElement must conform to the following requirements:
+        // - If O is not a host-defined exotic object, this abstract operation must return NormalCompletion(unused) and perform no other steps.
+        // - Any two calls of this abstract operation with the same argument must return the same kind of Completion Record.
+        // The default implementation of HostEnsureCanAddPrivateElement is to return NormalCompletion(unused).
+        return {};
+
+        // This abstract operation is only invoked by ECMAScript hosts that are web browsers.
+        // NOTE: Since LibJS has no way of knowing whether the current environment is a browser we always
+        //       call HostEnsureCanAddPrivateElement when needed.
+    };
+
 #define __JS_ENUMERATE(SymbolName, snake_name) \
 #define __JS_ENUMERATE(SymbolName, snake_name) \
     m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
     m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
     JS_ENUMERATE_WELL_KNOWN_SYMBOLS
     JS_ENUMERATE_WELL_KNOWN_SYMBOLS

+ 1 - 0
Userland/Libraries/LibJS/Runtime/VM.h

@@ -229,6 +229,7 @@ public:
     Function<void(Function<ThrowCompletionOr<Value>()>, Realm*)> host_enqueue_promise_job;
     Function<void(Function<ThrowCompletionOr<Value>()>, Realm*)> host_enqueue_promise_job;
     Function<JobCallback(FunctionObject&)> host_make_job_callback;
     Function<JobCallback(FunctionObject&)> host_make_job_callback;
     Function<ThrowCompletionOr<void>(Realm&)> host_ensure_can_compile_strings;
     Function<ThrowCompletionOr<void>(Realm&)> host_ensure_can_compile_strings;
+    Function<ThrowCompletionOr<void>(Object&)> host_ensure_can_add_private_element;
 
 
 private:
 private:
     explicit VM(OwnPtr<CustomData>);
     explicit VM(OwnPtr<CustomData>);