Serenity Operating System
at master 263 lines 12 kB view raw
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}