/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 | | } |