LibWeb: Make global prototype chains immutable
Per https://webidl.spec.whatwg.org/#interface-prototype-object any global platform object should reject prototype changes (besides from ShadowRealmGlobalScope), and interface prototype objects on global prototype chains must be immutable. We already handled parts of this on the globals themselves, but not the full chain. Also align some spec comments to the latest WebIDL spec.
This commit is contained in:
parent
ce4861156b
commit
379461e047
6 changed files with 93 additions and 2 deletions
|
|
@ -37,6 +37,16 @@ WorkerGlobalScope::WorkerGlobalScope(JS::Realm& realm, GC::Ref<Web::Page> page)
|
|||
|
||||
WorkerGlobalScope::~WorkerGlobalScope() = default;
|
||||
|
||||
// https://webidl.spec.whatwg.org/#platform-object-setprototypeof
|
||||
JS::ThrowCompletionOr<bool> WorkerGlobalScope::internal_set_prototype_of(JS::Object* prototype)
|
||||
{
|
||||
// 1. If O’s associated realm’s is global prototype chain mutable is true, return ? OrdinarySetPrototypeOf(O, V).
|
||||
// NB: This is never the case for WorkerGlobalScope.
|
||||
|
||||
// 2. Return ? SetImmutablePrototype(O, V).
|
||||
return set_immutable_prototype(prototype);
|
||||
}
|
||||
|
||||
void WorkerGlobalScope::initialize_web_interfaces_impl()
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public:
|
|||
GC::Ref<WorkerGlobalScope const> self() const { return *this; }
|
||||
|
||||
virtual Optional<URL::Origin> extract_an_origin() const override { return window_or_worker_global_scope_extract_an_origin(); }
|
||||
virtual JS::ThrowCompletionOr<bool> internal_set_prototype_of(JS::Object* prototype) override;
|
||||
|
||||
GC::Ref<WorkerLocation> location() const;
|
||||
GC::Ref<WorkerNavigator> navigator() const;
|
||||
|
|
|
|||
|
|
@ -3731,7 +3731,10 @@ JS::ThrowCompletionOr<bool> @named_properties_class@::internal_delete(JS::Proper
|
|||
// https://webidl.spec.whatwg.org/#named-properties-object-setprototypeof
|
||||
JS::ThrowCompletionOr<bool> @named_properties_class@::internal_set_prototype_of(JS::Object* prototype)
|
||||
{
|
||||
// 1. Return ? SetImmutablePrototype(O, V).
|
||||
// 1. If O’s associated realm’s is global prototype chain mutable is true, return ? OrdinarySetPrototypeOf(O, V).
|
||||
// NB: This is only ever true for ShadowRealms.
|
||||
|
||||
// 2. Return ? SetImmutablePrototype(O, V).
|
||||
return set_immutable_prototype(prototype);
|
||||
}
|
||||
|
||||
|
|
@ -3751,6 +3754,17 @@ void @named_properties_class@::visit_edges(Visitor& visitor)
|
|||
)~~~");
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#interface-prototype-object
|
||||
static bool interface_prototype_has_immutable_prototype(IDL::Interface const& interface)
|
||||
{
|
||||
// 9. Otherwise, if interface is declared with the [Global] extended attribute, or interface is in the set of
|
||||
// inherited interfaces of an interface that is declared with the [Global] extended attribute, then:
|
||||
// NB: This currently assumes only Workers and Window can be globals.
|
||||
return interface.extended_attributes.contains("Global")
|
||||
|| interface.name == "WorkerGlobalScope"sv
|
||||
|| interface.name == "EventTarget"sv;
|
||||
}
|
||||
|
||||
enum class GenerateUnforgeables {
|
||||
No,
|
||||
Yes,
|
||||
|
|
@ -5943,6 +5957,7 @@ void generate_prototype_header(IDL::Interface const& interface, StringBuilder& b
|
|||
SourceGenerator generator { builder };
|
||||
|
||||
generator.set("prototype_class", interface.prototype_class);
|
||||
auto has_immutable_prototype = interface_prototype_has_immutable_prototype(interface);
|
||||
|
||||
generator.append(R"~~~(
|
||||
#pragma once
|
||||
|
|
@ -5960,9 +5975,15 @@ public:
|
|||
explicit @prototype_class@(JS::Realm&);
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual ~@prototype_class@() override;
|
||||
private:
|
||||
)~~~");
|
||||
|
||||
if (has_immutable_prototype) {
|
||||
generator.append(R"~~~(
|
||||
private:
|
||||
virtual JS::ThrowCompletionOr<bool> internal_set_prototype_of(JS::Object* prototype) override;
|
||||
)~~~");
|
||||
}
|
||||
|
||||
// Generate an empty prototype object for global interfaces.
|
||||
auto is_global_interface = interface.extended_attributes.contains("Global");
|
||||
if (is_global_interface) {
|
||||
|
|
@ -5988,6 +6009,7 @@ void generate_prototype_implementation(IDL::Interface const& interface, StringBu
|
|||
generator.set("parent_name", interface.parent_name);
|
||||
generator.set("prototype_class", interface.prototype_class);
|
||||
generator.set("prototype_base_class", interface.prototype_base_class);
|
||||
auto has_immutable_prototype = interface_prototype_has_immutable_prototype(interface);
|
||||
|
||||
generator.append(R"~~~(
|
||||
#include <AK/Function.h>
|
||||
|
|
@ -6101,6 +6123,17 @@ GC_DEFINE_ALLOCATOR(@prototype_class@);
|
|||
}
|
||||
)~~~");
|
||||
|
||||
if (has_immutable_prototype) {
|
||||
generator.append(R"~~~(
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-interface-prototype-object
|
||||
JS::ThrowCompletionOr<bool> @prototype_class@::internal_set_prototype_of(JS::Object* prototype)
|
||||
{
|
||||
return set_immutable_prototype(prototype);
|
||||
}
|
||||
)~~~");
|
||||
}
|
||||
|
||||
// Generate a mostly empty prototype object for global interfaces.
|
||||
auto is_global_interface = interface.extended_attributes.contains("Global");
|
||||
if (is_global_interface) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 2 tests
|
||||
|
||||
2 Pass
|
||||
Pass Setting to a different prototype
|
||||
Pass Setting to the same prototype
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>Immutability of the global prototype chain</title>
|
||||
<script>
|
||||
self.GLOBAL = {
|
||||
isWindow: function() { return true; },
|
||||
isWorker: function() { return false; },
|
||||
isShadowRealm: function() { return false; },
|
||||
};
|
||||
</script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
|
||||
<div id=log></div>
|
||||
<script src="../../webidl/ecmascript-binding/global-immutable-prototype.any.js"></script>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// META: global=window,worker
|
||||
// META: title=Immutability of the global prototype chain
|
||||
|
||||
const objects = [];
|
||||
setup(() => {
|
||||
for (let object = self; object; object = Object.getPrototypeOf(object)) {
|
||||
objects.push(object);
|
||||
}
|
||||
});
|
||||
|
||||
test(() => {
|
||||
for (const object of objects) {
|
||||
assert_throws_js(TypeError, () => {
|
||||
Object.setPrototypeOf(object, {});
|
||||
});
|
||||
}
|
||||
}, "Setting to a different prototype");
|
||||
|
||||
test(() => {
|
||||
for (const object of objects) {
|
||||
const expected = Object.getPrototypeOf(object);
|
||||
Object.setPrototypeOf(object, expected);
|
||||
assert_equals(Object.getPrototypeOf(object), expected);
|
||||
}
|
||||
}, "Setting to the same prototype");
|
||||
Loading…
Reference in a new issue