AbstractOperations.cpp 15 KB

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