Serenity Operating System
1/*
2 * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Optional.h>
8#include <LibJS/Heap/MarkedVector.h>
9#include <LibJS/Runtime/Completion.h>
10#include <LibJS/Runtime/GlobalObject.h>
11#include <LibJS/Runtime/PropertyDescriptor.h>
12#include <LibJS/Runtime/PropertyKey.h>
13#include <LibWeb/HTML/CrossOrigin/AbstractOperations.h>
14#include <LibWeb/HTML/CrossOrigin/Reporting.h>
15#include <LibWeb/HTML/Scripting/Environments.h>
16#include <LibWeb/HTML/Window.h>
17#include <LibWeb/HTML/WindowProxy.h>
18#include <LibWeb/WebIDL/DOMException.h>
19
20namespace Web::HTML {
21
22// 7.4 The WindowProxy exotic object, https://html.spec.whatwg.org/multipage/window-object.html#the-windowproxy-exotic-object
23WindowProxy::WindowProxy(JS::Realm& realm)
24 : JS::Object(realm, nullptr)
25{
26}
27
28// 7.4.1 [[GetPrototypeOf]] ( ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getprototypeof
29JS::ThrowCompletionOr<JS::Object*> WindowProxy::internal_get_prototype_of() const
30{
31 // 1. Let W be the value of the [[Window]] internal slot of this.
32
33 // 2. If IsPlatformObjectSameOrigin(W) is true, then return ! OrdinaryGetPrototypeOf(W).
34 if (is_platform_object_same_origin(*m_window))
35 return MUST(m_window->internal_get_prototype_of());
36
37 // 3. Return null.
38 return nullptr;
39}
40
41// 7.4.2 [[SetPrototypeOf]] ( V ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-setprototypeof
42JS::ThrowCompletionOr<bool> WindowProxy::internal_set_prototype_of(Object* prototype)
43{
44 // 1. Return ! SetImmutablePrototype(this, V).
45 return MUST(set_immutable_prototype(prototype));
46}
47
48// 7.4.3 [[IsExtensible]] ( ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-isextensible
49JS::ThrowCompletionOr<bool> WindowProxy::internal_is_extensible() const
50{
51 // 1. Return true.
52 return true;
53}
54
55// 7.4.4 [[PreventExtensions]] ( ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-preventextensions
56JS::ThrowCompletionOr<bool> WindowProxy::internal_prevent_extensions()
57{
58 // 1. Return false.
59 return false;
60}
61
62// 7.4.5 [[GetOwnProperty]] ( P ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
63JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> WindowProxy::internal_get_own_property(JS::PropertyKey const& property_key) const
64{
65 auto& vm = this->vm();
66
67 // 1. Let W be the value of the [[Window]] internal slot of this.
68
69 // 2. If P is an array index property name, then:
70 if (property_key.is_number()) {
71 // 1. Let index be ! ToUint32(P).
72 auto index = property_key.as_number();
73
74 // 2. Let maxProperties be the number of document-tree child browsing contexts of W.
75 auto max_properties = m_window->document_tree_child_browsing_context_count();
76
77 // 3. Let value be undefined.
78 Optional<JS::Value> value;
79
80 // 4. If maxProperties is greater than 0 and index is less than maxProperties, then set value to the WindowProxy object of the indexth document-tree child browsing context of W's browsing context, sorted in the order that their browsing context container elements were most recently inserted into W's associated Document, the WindowProxy object of the most recently inserted browsing context container's nested browsing context being last.
81 if (max_properties > 0 && index < max_properties) {
82 // FIXME: Implement this.
83 }
84
85 // 5. If value is undefined, then:
86 if (!value.has_value()) {
87 // 1. If IsPlatformObjectSameOrigin(W) is true, then return undefined.
88 if (is_platform_object_same_origin(*m_window))
89 return Optional<JS::PropertyDescriptor> {};
90
91 // 2. Throw a "SecurityError" DOMException.
92 return throw_completion(WebIDL::SecurityError::create(m_window->realm(), DeprecatedString::formatted("Can't access property '{}' on cross-origin object", property_key)));
93 }
94
95 // 6. Return PropertyDescriptor{ [[Value]]: value, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: true }.
96 return JS::PropertyDescriptor { .value = move(value), .writable = false, .enumerable = true, .configurable = true };
97 }
98
99 // 3. If IsPlatformObjectSameOrigin(W) is true, then return ! OrdinaryGetOwnProperty(W, P).
100 // NOTE: This is a willful violation of the JavaScript specification's invariants of the essential internal methods to maintain compatibility with existing web content. See tc39/ecma262 issue #672 for more information.
101 if (is_platform_object_same_origin(*m_window))
102 return m_window->internal_get_own_property(property_key);
103
104 // 4. Let property be CrossOriginGetOwnPropertyHelper(W, P).
105 auto property = cross_origin_get_own_property_helper(const_cast<Window*>(m_window.ptr()), property_key);
106
107 // 5. If property is not undefined, then return property.
108 if (property.has_value())
109 return property;
110
111 // FIXME: 6. If property is undefined and P is in W's document-tree child browsing context name property set, then:
112 if (false) {
113 // FIXME: 1. Let value be the WindowProxy object of the named object of W with the name P.
114 auto value = JS::js_undefined();
115
116 // 2. Return PropertyDescriptor{ [[Value]]: value, [[Enumerable]]: false, [[Writable]]: false, [[Configurable]]: true }.
117 // NOTE: The reason the property descriptors are non-enumerable, despite this mismatching the same-origin behavior, is for compatibility with existing web content. See issue #3183 for details.
118 return JS::PropertyDescriptor { .value = value, .writable = false, .enumerable = false, .configurable = true };
119 }
120
121 // 7. Return ? CrossOriginPropertyFallback(P).
122 return TRY(cross_origin_property_fallback(vm, property_key));
123}
124
125// 7.4.6 [[DefineOwnProperty]] ( P, Desc ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-defineownproperty
126JS::ThrowCompletionOr<bool> WindowProxy::internal_define_own_property(JS::PropertyKey const& property_key, JS::PropertyDescriptor const& descriptor)
127{
128 // 1. Let W be the value of the [[Window]] internal slot of this.
129
130 // 2. If IsPlatformObjectSameOrigin(W) is true, then:
131 if (is_platform_object_same_origin(*m_window)) {
132 // 1. If P is an array index property name, return false.
133 if (property_key.is_number())
134 return false;
135
136 // 2. Return ? OrdinaryDefineOwnProperty(W, P, Desc).
137 // NOTE: This is a willful violation of the JavaScript specification's invariants of the essential internal methods to maintain compatibility with existing web content. See tc39/ecma262 issue #672 for more information.
138 return m_window->internal_define_own_property(property_key, descriptor);
139 }
140
141 // 3. Throw a "SecurityError" DOMException.
142 return throw_completion(WebIDL::SecurityError::create(m_window->realm(), DeprecatedString::formatted("Can't define property '{}' on cross-origin object", property_key)));
143}
144
145// 7.4.7 [[Get]] ( P, Receiver ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-get
146JS::ThrowCompletionOr<JS::Value> WindowProxy::internal_get(JS::PropertyKey const& property_key, JS::Value receiver) const
147{
148 auto& vm = this->vm();
149
150 // 1. Let W be the value of the [[Window]] internal slot of this.
151
152 // 2. Check if an access between two browsing contexts should be reported, given the current global object's browsing context, W's browsing context, P, and the current settings object.
153 check_if_access_between_two_browsing_contexts_should_be_reported(*verify_cast<Window>(current_global_object()).browsing_context(), *m_window->browsing_context(), property_key, current_settings_object());
154
155 // 3. If IsPlatformObjectSameOrigin(W) is true, then return ? OrdinaryGet(this, P, Receiver).
156 // NOTE: this is passed rather than W as OrdinaryGet and CrossOriginGet will invoke the [[GetOwnProperty]] internal method.
157 if (is_platform_object_same_origin(*m_window))
158 return JS::Object::internal_get(property_key, receiver);
159
160 // 4. Return ? CrossOriginGet(this, P, Receiver).
161 // NOTE: this is passed rather than W as OrdinaryGet and CrossOriginGet will invoke the [[GetOwnProperty]] internal method.
162 return cross_origin_get(vm, *this, property_key, receiver);
163}
164
165// 7.4.8 [[Set]] ( P, V, Receiver ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set
166JS::ThrowCompletionOr<bool> WindowProxy::internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver)
167{
168 auto& vm = this->vm();
169
170 // 1. Let W be the value of the [[Window]] internal slot of this.
171
172 // 2. Check if an access between two browsing contexts should be reported, given the current global object's browsing context, W's browsing context, P, and the current settings object.
173 check_if_access_between_two_browsing_contexts_should_be_reported(*verify_cast<Window>(current_global_object()).browsing_context(), *m_window->browsing_context(), property_key, current_settings_object());
174
175 // 3. If IsPlatformObjectSameOrigin(W) is true, then:
176 if (is_platform_object_same_origin(*m_window)) {
177 // 1. If P is an array index property name, then return false.
178 if (property_key.is_number())
179 return false;
180
181 // 2. Return ? OrdinarySet(W, P, V, Receiver).
182 return m_window->internal_set(property_key, value, receiver);
183 }
184
185 // 4. Return ? CrossOriginSet(this, P, V, Receiver).
186 // NOTE: this is passed rather than W as CrossOriginSet will invoke the [[GetOwnProperty]] internal method.
187 return cross_origin_set(vm, *this, property_key, value, receiver);
188}
189
190// 7.4.9 [[Delete]] ( P ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete
191JS::ThrowCompletionOr<bool> WindowProxy::internal_delete(JS::PropertyKey const& property_key)
192{
193 // 1. Let W be the value of the [[Window]] internal slot of this.
194
195 // 2. If IsPlatformObjectSameOrigin(W) is true, then:
196 if (is_platform_object_same_origin(*m_window)) {
197 // 1. If P is an array index property name, then:
198 if (property_key.is_number()) {
199 // 2. Let desc be ! this.[[GetOwnProperty]](P).
200 auto descriptor = MUST(internal_get_own_property(property_key));
201
202 // 2. If desc is undefined, then return true.
203 if (!descriptor.has_value())
204 return true;
205
206 // 3. Return false.
207 return false;
208 }
209
210 // 2. Return ? OrdinaryDelete(W, P).
211 return m_window->internal_delete(property_key);
212 }
213
214 // 3. Throw a "SecurityError" DOMException.
215 return throw_completion(WebIDL::SecurityError::create(m_window->realm(), DeprecatedString::formatted("Can't delete property '{}' on cross-origin object", property_key)));
216}
217
218// 7.4.10 [[OwnPropertyKeys]] ( ), https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys
219JS::ThrowCompletionOr<JS::MarkedVector<JS::Value>> WindowProxy::internal_own_property_keys() const
220{
221 auto& event_loop = main_thread_event_loop();
222 auto& vm = event_loop.vm();
223
224 // 1. Let W be the value of the [[Window]] internal slot of this.
225
226 // 2. Let keys be a new empty List.
227 auto keys = JS::MarkedVector<JS::Value> { vm.heap() };
228
229 // 3. Let maxProperties be the number of document-tree child browsing contexts of W.
230 auto max_properties = m_window->document_tree_child_browsing_context_count();
231
232 // 4. Let index be 0.
233 // 5. Repeat while index < maxProperties,
234 for (size_t i = 0; i < max_properties; ++i) {
235 // 1. Add ! ToString(index) as the last element of keys.
236 keys.append(JS::PrimitiveString::create(vm, DeprecatedString::number(i)));
237
238 // 2. Increment index by 1.
239 }
240
241 // 6. If IsPlatformObjectSameOrigin(W) is true, then return the concatenation of keys and OrdinaryOwnPropertyKeys(W).
242 if (is_platform_object_same_origin(*m_window)) {
243 keys.extend(MUST(m_window->internal_own_property_keys()));
244 return keys;
245 }
246
247 // 7. Return the concatenation of keys and ! CrossOriginOwnPropertyKeys(W).
248 keys.extend(cross_origin_own_property_keys(m_window.ptr()));
249 return keys;
250}
251
252void WindowProxy::visit_edges(JS::Cell::Visitor& visitor)
253{
254 Base::visit_edges(visitor);
255 visitor.visit(m_window.ptr());
256}
257
258void WindowProxy::set_window(Badge<BrowsingContext>, JS::NonnullGCPtr<Window> window)
259{
260 m_window = window;
261}
262
263}