Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/HTML/CrossOrigin/AbstractOperations.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/Variant.h>
8
#include <AK/Vector.h>
9
#include <LibJS/Runtime/AbstractOperations.h>
10
#include <LibJS/Runtime/Completion.h>
11
#include <LibJS/Runtime/GlobalObject.h>
12
#include <LibJS/Runtime/NativeFunction.h>
13
#include <LibJS/Runtime/Object.h>
14
#include <LibJS/Runtime/PropertyDescriptor.h>
15
#include <LibJS/Runtime/PropertyKey.h>
16
#include <LibWeb/Bindings/MainThreadVM.h>
17
#include <LibWeb/HTML/CrossOrigin/AbstractOperations.h>
18
#include <LibWeb/HTML/Location.h>
19
#include <LibWeb/HTML/Scripting/Environments.h>
20
#include <LibWeb/HTML/Window.h>
21
#include <LibWeb/WebIDL/DOMException.h>
22
23
namespace Web::HTML {
24
25
// 7.2.3.1 CrossOriginProperties ( O ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginproperties-(-o-)
26
Vector<CrossOriginProperty> cross_origin_properties(Variant<HTML::Location const*, HTML::Window const*> const& object)
27
0
{
28
    // 1. Assert: O is a Location or Window object.
29
30
0
    return object.visit(
31
        // 2. If O is a Location object, then return « { [[Property]]: "href", [[NeedsGet]]: false, [[NeedsSet]]: true }, { [[Property]]: "replace" } ».
32
0
        [](HTML::Location const*) -> Vector<CrossOriginProperty> {
33
0
            return {
34
0
                { .property = "href"_string, .needs_get = false, .needs_set = true },
35
0
                { .property = "replace"_string },
36
0
            };
37
0
        },
38
        // 3. Return « { [[Property]]: "window", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "self", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "location", [[NeedsGet]]: true, [[NeedsSet]]: true }, { [[Property]]: "close" }, { [[Property]]: "closed", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "focus" }, { [[Property]]: "blur" }, { [[Property]]: "frames", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "length", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "top", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "opener", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "parent", [[NeedsGet]]: true, [[NeedsSet]]: false }, { [[Property]]: "postMessage" } ».
39
0
        [](HTML::Window const*) -> Vector<CrossOriginProperty> {
40
0
            return {
41
0
                { .property = "window"_string, .needs_get = true, .needs_set = false },
42
0
                { .property = "self"_string, .needs_get = true, .needs_set = false },
43
0
                { .property = "location"_string, .needs_get = true, .needs_set = true },
44
0
                { .property = "close"_string },
45
0
                { .property = "closed"_string, .needs_get = true, .needs_set = false },
46
0
                { .property = "focus"_string },
47
0
                { .property = "blur"_string },
48
0
                { .property = "frames"_string, .needs_get = true, .needs_set = false },
49
0
                { .property = "length"_string, .needs_get = true, .needs_set = false },
50
0
                { .property = "top"_string, .needs_get = true, .needs_set = false },
51
0
                { .property = "opener"_string, .needs_get = true, .needs_set = false },
52
0
                { .property = "parent"_string, .needs_get = true, .needs_set = false },
53
0
                { .property = "postMessage"_string },
54
0
            };
55
0
        });
56
0
}
57
58
// https://html.spec.whatwg.org/multipage/browsers.html#cross-origin-accessible-window-property-name
59
bool is_cross_origin_accessible_window_property_name(JS::PropertyKey const& property_key)
60
0
{
61
    // A JavaScript property name P is a cross-origin accessible window property name if it is "window", "self", "location", "close", "closed", "focus", "blur", "frames", "length", "top", "opener", "parent", "postMessage", or an array index property name.
62
0
    static Array<DeprecatedFlyString, 13> property_names {
63
0
        "window"sv, "self"sv, "location"sv, "close"sv, "closed"sv, "focus"sv, "blur"sv, "frames"sv, "length"sv, "top"sv, "opener"sv, "parent"sv, "postMessage"sv
64
0
    };
65
0
    return (property_key.is_string() && any_of(property_names, [&](auto const& name) { return property_key.as_string() == name; })) || property_key.is_number();
66
0
}
67
68
// 7.2.3.2 CrossOriginPropertyFallback ( P ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginpropertyfallback-(-p-)
69
JS::ThrowCompletionOr<JS::PropertyDescriptor> cross_origin_property_fallback(JS::VM& vm, JS::PropertyKey const& property_key)
70
0
{
71
    // 1. If P is "then", @@toStringTag, @@hasInstance, or @@isConcatSpreadable, then return PropertyDescriptor{ [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
72
0
    auto property_key_is_then = property_key.is_string() && property_key.as_string() == vm.names.then.as_string();
73
0
    auto property_key_is_allowed_symbol = property_key.is_symbol()
74
0
        && (property_key.as_symbol() == vm.well_known_symbol_to_string_tag()
75
0
            || property_key.as_symbol() == vm.well_known_symbol_has_instance()
76
0
            || property_key.as_symbol() == vm.well_known_symbol_is_concat_spreadable());
77
0
    if (property_key_is_then || property_key_is_allowed_symbol)
78
0
        return JS::PropertyDescriptor { .value = JS::js_undefined(), .writable = false, .enumerable = false, .configurable = true };
79
80
    // 2. Throw a "SecurityError" DOMException.
81
0
    return throw_completion(WebIDL::SecurityError::create(*vm.current_realm(), MUST(String::formatted("Can't access property '{}' on cross-origin object", property_key))));
82
0
}
83
84
// 7.2.3.3 IsPlatformObjectSameOrigin ( O ), https://html.spec.whatwg.org/multipage/browsers.html#isplatformobjectsameorigin-(-o-)
85
bool is_platform_object_same_origin(JS::Object const& object)
86
0
{
87
    // 1. Return true if the current settings object's origin is same origin-domain with O's relevant settings object's origin, and false otherwise.
88
0
    return HTML::current_settings_object().origin().is_same_origin_domain(HTML::relevant_settings_object(object).origin());
89
0
}
90
91
// 7.2.3.4 CrossOriginGetOwnPropertyHelper ( O, P ), https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)
92
Optional<JS::PropertyDescriptor> cross_origin_get_own_property_helper(Variant<HTML::Location*, HTML::Window*> const& object, JS::PropertyKey const& property_key)
93
0
{
94
0
    auto& realm = *Bindings::main_thread_vm().current_realm();
95
0
    auto const* object_ptr = object.visit([](auto* o) { return static_cast<JS::Object const*>(o); });
Unexecuted instantiation: AbstractOperations.cpp:auto Web::HTML::cross_origin_get_own_property_helper(AK::Variant<Web::HTML::Location*, Web::HTML::Window*> const&, JS::PropertyKey const&)::$_0::operator()<Web::HTML::Location>(Web::HTML::Location*) const
Unexecuted instantiation: AbstractOperations.cpp:auto Web::HTML::cross_origin_get_own_property_helper(AK::Variant<Web::HTML::Location*, Web::HTML::Window*> const&, JS::PropertyKey const&)::$_0::operator()<Web::HTML::Window>(Web::HTML::Window*) const
96
0
    auto const object_const_variant = object.visit([](auto* o) { return Variant<HTML::Location const*, HTML::Window const*> { o }; });
Unexecuted instantiation: AbstractOperations.cpp:auto Web::HTML::cross_origin_get_own_property_helper(AK::Variant<Web::HTML::Location*, Web::HTML::Window*> const&, JS::PropertyKey const&)::$_1::operator()<Web::HTML::Location>(Web::HTML::Location*) const
Unexecuted instantiation: AbstractOperations.cpp:auto Web::HTML::cross_origin_get_own_property_helper(AK::Variant<Web::HTML::Location*, Web::HTML::Window*> const&, JS::PropertyKey const&)::$_1::operator()<Web::HTML::Window>(Web::HTML::Window*) const
97
98
    // 1. Let crossOriginKey be a tuple consisting of the current settings object, O's relevant settings object, and P.
99
0
    auto cross_origin_key = CrossOriginKey {
100
0
        .current_settings_object = (FlatPtr)&HTML::current_settings_object(),
101
0
        .relevant_settings_object = (FlatPtr)&HTML::relevant_settings_object(*object_ptr),
102
0
        .property_key = property_key,
103
0
    };
104
105
    // SameValue(e.[[Property]], P) can never be true at step 2.1 if P is not a string due to the different type, so we can return early.
106
0
    if (!property_key.is_string()) {
107
0
        return {};
108
0
    }
109
0
    auto const& property_key_string = MUST(FlyString::from_deprecated_fly_string(property_key.as_string()));
110
111
    // 2. For each e of CrossOriginProperties(O):
112
0
    for (auto const& entry : cross_origin_properties(object_const_variant)) {
113
0
        if (entry.property != property_key_string)
114
0
            continue;
115
        // 1. If SameValue(e.[[Property]], P) is true, then:
116
0
        auto& cross_origin_property_descriptor_map = object.visit([](auto* o) -> CrossOriginPropertyDescriptorMap& { return o->cross_origin_property_descriptor_map(); });
Unexecuted instantiation: AbstractOperations.cpp:AK::HashMap<Web::HTML::CrossOriginKey, JS::PropertyDescriptor, AK::Traits<Web::HTML::CrossOriginKey>, AK::Traits<JS::PropertyDescriptor>, false>& Web::HTML::cross_origin_get_own_property_helper(AK::Variant<Web::HTML::Location*, Web::HTML::Window*> const&, JS::PropertyKey const&)::$_2::operator()<Web::HTML::Location>(Web::HTML::Location*) const
Unexecuted instantiation: AbstractOperations.cpp:AK::HashMap<Web::HTML::CrossOriginKey, JS::PropertyDescriptor, AK::Traits<Web::HTML::CrossOriginKey>, AK::Traits<JS::PropertyDescriptor>, false>& Web::HTML::cross_origin_get_own_property_helper(AK::Variant<Web::HTML::Location*, Web::HTML::Window*> const&, JS::PropertyKey const&)::$_2::operator()<Web::HTML::Window>(Web::HTML::Window*) const
117
118
        // 1. If the value of the [[CrossOriginPropertyDescriptorMap]] internal slot of O contains an entry whose key is crossOriginKey, then return that entry's value.
119
0
        auto it = cross_origin_property_descriptor_map.find(cross_origin_key);
120
0
        if (it != cross_origin_property_descriptor_map.end())
121
0
            return it->value;
122
123
        // 2. Let originalDesc be OrdinaryGetOwnProperty(O, P).
124
0
        auto original_descriptor = MUST((object_ptr->JS::Object::internal_get_own_property)(property_key));
125
126
        // 3. Let crossOriginDesc be undefined.
127
0
        auto cross_origin_descriptor = JS::PropertyDescriptor {};
128
129
        // 4. If e.[[NeedsGet]] and e.[[NeedsSet]] are absent, then:
130
0
        if (!entry.needs_get.has_value() && !entry.needs_set.has_value()) {
131
            // 1. Let value be originalDesc.[[Value]].
132
0
            auto value = original_descriptor->value;
133
134
            // 2. If IsCallable(value) is true, then set value to an anonymous built-in function, created in the current Realm Record, that performs the same steps as the IDL operation P on object O.
135
0
            if (value->is_function()) {
136
0
                value = JS::NativeFunction::create(
137
0
                    realm, [function = JS::make_handle(*value)](auto& vm) {
138
0
                        return JS::call(vm, function.value(), JS::js_undefined(), vm.running_execution_context().arguments.span());
139
0
                    },
140
0
                    0, "");
141
0
            }
142
143
            // 3. Set crossOriginDesc to PropertyDescriptor{ [[Value]]: value, [[Enumerable]]: false, [[Writable]]: false, [[Configurable]]: true }.
144
0
            cross_origin_descriptor = JS::PropertyDescriptor { .value = value, .writable = false, .enumerable = false, .configurable = true };
145
0
        }
146
        // 5. Otherwise:
147
0
        else {
148
            // 1. Let crossOriginGet be undefined.
149
0
            Optional<JS::GCPtr<JS::FunctionObject>> cross_origin_get;
150
151
            // 2. If e.[[NeedsGet]] is true, then set crossOriginGet to an anonymous built-in function, created in the current Realm Record, that performs the same steps as the getter of the IDL attribute P on object O.
152
0
            if (*entry.needs_get) {
153
0
                cross_origin_get = JS::NativeFunction::create(
154
0
                    realm, [object_ptr, getter = JS::make_handle(*original_descriptor->get)](auto& vm) {
155
0
                        return JS::call(vm, getter.cell(), object_ptr, vm.running_execution_context().arguments.span());
156
0
                    },
157
0
                    0, "");
158
0
            }
159
160
            // 3. Let crossOriginSet be undefined.
161
0
            Optional<JS::GCPtr<JS::FunctionObject>> cross_origin_set;
162
163
            // If e.[[NeedsSet]] is true, then set crossOriginSet to an anonymous built-in function, created in the current Realm Record, that performs the same steps as the setter of the IDL attribute P on object O.
164
0
            if (*entry.needs_set) {
165
0
                cross_origin_set = JS::NativeFunction::create(
166
0
                    realm, [object_ptr, setter = JS::make_handle(*original_descriptor->set)](auto& vm) {
167
0
                        return JS::call(vm, setter.cell(), object_ptr, vm.running_execution_context().arguments.span());
168
0
                    },
169
0
                    0, "");
170
0
            }
171
172
            // 5. Set crossOriginDesc to PropertyDescriptor{ [[Get]]: crossOriginGet, [[Set]]: crossOriginSet, [[Enumerable]]: false, [[Configurable]]: true }.
173
0
            cross_origin_descriptor = JS::PropertyDescriptor { .get = cross_origin_get, .set = cross_origin_set, .enumerable = false, .configurable = true };
174
0
        }
175
176
        // 6. Create an entry in the value of the [[CrossOriginPropertyDescriptorMap]] internal slot of O with key crossOriginKey and value crossOriginDesc.
177
0
        cross_origin_property_descriptor_map.set(cross_origin_key, cross_origin_descriptor);
178
179
        // 7. Return crossOriginDesc.
180
0
        return cross_origin_descriptor;
181
0
    }
182
183
    // 3. Return undefined.
184
0
    return {};
185
0
}
186
187
// 7.2.3.5 CrossOriginGet ( O, P, Receiver ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginget-(-o,-p,-receiver-)
188
JS::ThrowCompletionOr<JS::Value> cross_origin_get(JS::VM& vm, JS::Object const& object, JS::PropertyKey const& property_key, JS::Value receiver)
189
0
{
190
    // 1. Let desc be ? O.[[GetOwnProperty]](P).
191
0
    auto descriptor = TRY(object.internal_get_own_property(property_key));
192
193
    // 2. Assert: desc is not undefined.
194
0
    VERIFY(descriptor.has_value());
195
196
    // 3. If IsDataDescriptor(desc) is true, then return desc.[[Value]].
197
0
    if (descriptor->is_data_descriptor())
198
0
        return *descriptor->value;
199
200
    // 4. Assert: IsAccessorDescriptor(desc) is true.
201
0
    VERIFY(descriptor->is_accessor_descriptor());
202
203
    // 5. Let getter be desc.[[Get]].
204
0
    auto& getter = descriptor->get;
205
206
    // 6. If getter is undefined, then throw a "SecurityError" DOMException.
207
0
    if (!getter.has_value())
208
0
        return throw_completion(WebIDL::SecurityError::create(*vm.current_realm(), MUST(String::formatted("Can't get property '{}' on cross-origin object", property_key))));
209
210
    // 7. Return ? Call(getter, Receiver).
211
0
    return JS::call(vm, *getter, receiver);
212
0
}
213
214
// 7.2.3.6 CrossOriginSet ( O, P, V, Receiver ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginset-(-o,-p,-v,-receiver-)
215
JS::ThrowCompletionOr<bool> cross_origin_set(JS::VM& vm, JS::Object& object, JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver)
216
0
{
217
    // 1. Let desc be ? O.[[GetOwnProperty]](P).
218
0
    auto descriptor = TRY(object.internal_get_own_property(property_key));
219
220
    // 2. Assert: desc is not undefined.
221
0
    VERIFY(descriptor.has_value());
222
223
    // 3. If desc.[[Set]] is present and its value is not undefined, then:
224
0
    if (descriptor->set.has_value() && *descriptor->set) {
225
        // FIXME: Spec issue, `setter` isn't being defined.
226
        // 1. Perform ? Call(setter, Receiver, «V»).
227
0
        TRY(JS::call(vm, *descriptor->set, receiver, value));
228
229
        // 2. Return true.
230
0
        return true;
231
0
    }
232
233
    // 4. Throw a "SecurityError" DOMException.
234
0
    return throw_completion(WebIDL::SecurityError::create(*vm.current_realm(), MUST(String::formatted("Can't set property '{}' on cross-origin object", property_key))));
235
0
}
236
237
// 7.2.3.7 CrossOriginOwnPropertyKeys ( O ), https://html.spec.whatwg.org/multipage/browsers.html#crossoriginownpropertykeys-(-o-)
238
JS::MarkedVector<JS::Value> cross_origin_own_property_keys(Variant<HTML::Location const*, HTML::Window const*> const& object)
239
0
{
240
0
    auto& event_loop = HTML::main_thread_event_loop();
241
0
    auto& vm = event_loop.vm();
242
243
    // 1. Let keys be a new empty List.
244
0
    auto keys = JS::MarkedVector<JS::Value> { vm.heap() };
245
246
    // 2. For each e of CrossOriginProperties(O), append e.[[Property]] to keys.
247
0
    for (auto& entry : cross_origin_properties(object))
248
0
        keys.append(JS::PrimitiveString::create(vm, move(entry.property)));
249
250
    // 3. Return the concatenation of keys and « "then", @@toStringTag, @@hasInstance, @@isConcatSpreadable ».
251
0
    keys.append(JS::PrimitiveString::create(vm, vm.names.then.as_string()));
252
0
    keys.append(vm.well_known_symbol_to_string_tag());
253
0
    keys.append(vm.well_known_symbol_has_instance());
254
0
    keys.append(vm.well_known_symbol_is_concat_spreadable());
255
0
    return keys;
256
0
}
257
258
}