Serenity Operating System
at master 475 lines 22 kB view raw
1/* 2 * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> 3 * Copyright (c) 2022, Linus Groh <linusg@serenityos.org> 4 * Copyright (c) 2022, networkException <networkexception@serenityos.org> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <LibWeb/Bindings/MainThreadVM.h> 10#include <LibWeb/DOM/Document.h> 11#include <LibWeb/HTML/PromiseRejectionEvent.h> 12#include <LibWeb/HTML/Scripting/Environments.h> 13#include <LibWeb/HTML/Scripting/ExceptionReporter.h> 14#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h> 15#include <LibWeb/HTML/Window.h> 16#include <LibWeb/HTML/WorkerGlobalScope.h> 17#include <LibWeb/Page/Page.h> 18#include <LibWeb/SecureContexts/AbstractOperations.h> 19 20namespace Web::HTML { 21 22EnvironmentSettingsObject::EnvironmentSettingsObject(NonnullOwnPtr<JS::ExecutionContext> realm_execution_context) 23 : m_realm_execution_context(move(realm_execution_context)) 24{ 25 m_realm_execution_context->context_owner = this; 26 27 // Register with the responsible event loop so we can perform step 4 of "perform a microtask checkpoint". 28 responsible_event_loop().register_environment_settings_object({}, *this); 29} 30 31EnvironmentSettingsObject::~EnvironmentSettingsObject() 32{ 33 responsible_event_loop().unregister_environment_settings_object({}, *this); 34} 35 36void EnvironmentSettingsObject::visit_edges(Cell::Visitor& visitor) 37{ 38 Base::visit_edges(visitor); 39 visitor.visit(target_browsing_context); 40} 41 42JS::ExecutionContext& EnvironmentSettingsObject::realm_execution_context() 43{ 44 // NOTE: All environment settings objects are created with a realm execution context, so it's stored and returned here in the base class. 45 return *m_realm_execution_context; 46} 47 48ModuleMap& EnvironmentSettingsObject::module_map() 49{ 50 return m_module_map; 51} 52 53// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object%27s-realm 54JS::Realm& EnvironmentSettingsObject::realm() 55{ 56 // An environment settings object's realm execution context's Realm component is the environment settings object's Realm. 57 return *realm_execution_context().realm; 58} 59 60// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-global 61JS::Object& EnvironmentSettingsObject::global_object() 62{ 63 // An environment settings object's Realm then has a [[GlobalObject]] field, which contains the environment settings object's global object. 64 return realm().global_object(); 65} 66 67// https://html.spec.whatwg.org/multipage/webappapis.html#responsible-event-loop 68EventLoop& EnvironmentSettingsObject::responsible_event_loop() 69{ 70 // An environment settings object's responsible event loop is its global object's relevant agent's event loop. 71 // This is here in case the realm that is holding onto this ESO is destroyed before the ESO is. The responsible event loop pointer is needed in the ESO destructor to deregister from the event loop. 72 // FIXME: Figure out why the realm can be destroyed before the ESO, as the realm is holding onto this with an OwnPtr, but the heap block deallocator calls the ESO destructor directly instead of through the realm destructor. 73 if (m_responsible_event_loop) 74 return *m_responsible_event_loop; 75 76 auto& vm = global_object().vm(); 77 auto& event_loop = verify_cast<Bindings::WebEngineCustomData>(vm.custom_data())->event_loop; 78 m_responsible_event_loop = &event_loop; 79 return event_loop; 80} 81 82// https://html.spec.whatwg.org/multipage/webappapis.html#check-if-we-can-run-script 83RunScriptDecision EnvironmentSettingsObject::can_run_script() 84{ 85 // 1. If the global object specified by settings is a Window object whose Document object is not fully active, then return "do not run". 86 if (is<HTML::Window>(global_object()) && !verify_cast<HTML::Window>(global_object()).associated_document().is_fully_active()) 87 return RunScriptDecision::DoNotRun; 88 89 // 2. If scripting is disabled for settings, then return "do not run". 90 if (is_scripting_disabled()) 91 return RunScriptDecision::DoNotRun; 92 93 // 3. Return "run". 94 return RunScriptDecision::Run; 95} 96 97// https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-script 98void EnvironmentSettingsObject::prepare_to_run_script() 99{ 100 // 1. Push settings's realm execution context onto the JavaScript execution context stack; it is now the running JavaScript execution context. 101 global_object().vm().push_execution_context(realm_execution_context()); 102 103 // FIXME: 2. Add settings to the currently running task's script evaluation environment settings object set. 104} 105 106// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script 107void EnvironmentSettingsObject::clean_up_after_running_script() 108{ 109 auto& vm = global_object().vm(); 110 111 // 1. Assert: settings's realm execution context is the running JavaScript execution context. 112 VERIFY(&realm_execution_context() == &vm.running_execution_context()); 113 114 // 2. Remove settings's realm execution context from the JavaScript execution context stack. 115 vm.pop_execution_context(); 116 117 // 3. If the JavaScript execution context stack is now empty, perform a microtask checkpoint. (If this runs scripts, these algorithms will be invoked reentrantly.) 118 if (vm.execution_context_stack().is_empty()) 119 responsible_event_loop().perform_a_microtask_checkpoint(); 120} 121 122static JS::ExecutionContext* top_most_script_having_execution_context(JS::VM& vm) 123{ 124 // Here, the topmost script-having execution context is the topmost entry of the JavaScript execution context stack that has a non-null ScriptOrModule component, 125 // or null if there is no such entry in the JavaScript execution context stack. 126 auto execution_context = vm.execution_context_stack().last_matching([&](JS::ExecutionContext* context) { 127 return !context->script_or_module.has<Empty>(); 128 }); 129 130 if (!execution_context.has_value()) 131 return nullptr; 132 133 return execution_context.value(); 134} 135 136// https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-a-callback 137void EnvironmentSettingsObject::prepare_to_run_callback() 138{ 139 auto& vm = global_object().vm(); 140 141 // 1. Push settings onto the backup incumbent settings object stack. 142 // NOTE: The spec doesn't say which event loop's stack to put this on. However, all the examples of the incumbent settings object use iframes and cross browsing context communication to demonstrate the concept. 143 // This means that it must rely on some global state that can be accessed by all browsing contexts, which is the main thread event loop. 144 HTML::main_thread_event_loop().push_onto_backup_incumbent_settings_object_stack({}, *this); 145 146 // 2. Let context be the topmost script-having execution context. 147 auto* context = top_most_script_having_execution_context(vm); 148 149 // 3. If context is not null, increment context's skip-when-determining-incumbent counter. 150 if (context) 151 context->skip_when_determining_incumbent_counter++; 152} 153 154// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#parse-a-url 155AK::URL EnvironmentSettingsObject::parse_url(StringView url) 156{ 157 // 1. Let encoding be document's character encoding, if document was given, and environment settings object's API URL character encoding otherwise. 158 // FIXME: Pass in environment settings object's API URL character encoding. 159 160 // 2. Let baseURL be document's base URL, if document was given, and environment settings object's API base URL otherwise. 161 auto base_url = api_base_url(); 162 163 // 3. Let urlRecord be the result of applying the URL parser to url, with baseURL and encoding. 164 // 4. If urlRecord is failure, then return failure. 165 // 5. Let urlString be the result of applying the URL serializer to urlRecord. 166 // 6. Return urlString as the resulting URL string and urlRecord as the resulting URL record. 167 return base_url.complete_url(url); 168} 169 170// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-a-callback 171void EnvironmentSettingsObject::clean_up_after_running_callback() 172{ 173 auto& vm = global_object().vm(); 174 175 // 1. Let context be the topmost script-having execution context. 176 auto* context = top_most_script_having_execution_context(vm); 177 178 // 2. If context is not null, decrement context's skip-when-determining-incumbent counter. 179 if (context) 180 context->skip_when_determining_incumbent_counter--; 181 182 // 3. Assert: the topmost entry of the backup incumbent settings object stack is settings. 183 auto& event_loop = HTML::main_thread_event_loop(); 184 VERIFY(&event_loop.top_of_backup_incumbent_settings_object_stack() == this); 185 186 // 4. Remove settings from the backup incumbent settings object stack. 187 event_loop.pop_backup_incumbent_settings_object_stack({}); 188} 189 190void EnvironmentSettingsObject::push_onto_outstanding_rejected_promises_weak_set(JS::Promise* promise) 191{ 192 m_outstanding_rejected_promises_weak_set.append(promise); 193} 194 195bool EnvironmentSettingsObject::remove_from_outstanding_rejected_promises_weak_set(JS::Promise* promise) 196{ 197 return m_outstanding_rejected_promises_weak_set.remove_first_matching([&](JS::Promise* promise_in_set) { 198 return promise == promise_in_set; 199 }); 200} 201 202void EnvironmentSettingsObject::push_onto_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr<JS::Promise> promise) 203{ 204 m_about_to_be_notified_rejected_promises_list.append(JS::make_handle(promise)); 205} 206 207bool EnvironmentSettingsObject::remove_from_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr<JS::Promise> promise) 208{ 209 return m_about_to_be_notified_rejected_promises_list.remove_first_matching([&](auto& promise_in_list) { 210 return promise == promise_in_list; 211 }); 212} 213 214// https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises 215void EnvironmentSettingsObject::notify_about_rejected_promises(Badge<EventLoop>) 216{ 217 // 1. Let list be a copy of settings object's about-to-be-notified rejected promises list. 218 auto list = m_about_to_be_notified_rejected_promises_list; 219 220 // 2. If list is empty, return. 221 if (list.is_empty()) 222 return; 223 224 // 3. Clear settings object's about-to-be-notified rejected promises list. 225 m_about_to_be_notified_rejected_promises_list.clear(); 226 227 // 4. Let global be settings object's global object. 228 auto& global = global_object(); 229 230 // 5. Queue a global task on the DOM manipulation task source given global to run the following substep: 231 queue_global_task(Task::Source::DOMManipulation, global, [this, &global, list = move(list)] { 232 // 1. For each promise p in list: 233 for (auto promise : list) { 234 235 // 1. If p's [[PromiseIsHandled]] internal slot is true, continue to the next iteration of the loop. 236 if (promise->is_handled()) 237 continue; 238 239 // 2. Let notHandled be the result of firing an event named unhandledrejection at global, using PromiseRejectionEvent, with the cancelable attribute initialized to true, 240 // the promise attribute initialized to p, and the reason attribute initialized to the value of p's [[PromiseResult]] internal slot. 241 PromiseRejectionEventInit event_init { 242 { 243 .bubbles = false, 244 .cancelable = true, 245 .composed = false, 246 }, 247 // Sadly we can't use .promise and .reason here, as we can't use the designator on the initialization of DOM::EventInit above. 248 /* .promise = */ JS::make_handle(*promise), 249 /* .reason = */ promise->result(), 250 }; 251 // FIXME: This currently assumes that global is a WindowObject. 252 auto& window = verify_cast<HTML::Window>(global); 253 254 auto promise_rejection_event = PromiseRejectionEvent::create(window.realm(), String::from_deprecated_string(HTML::EventNames::unhandledrejection).release_value_but_fixme_should_propagate_errors(), event_init).release_value_but_fixme_should_propagate_errors(); 255 256 bool not_handled = window.dispatch_event(*promise_rejection_event); 257 258 // 3. If notHandled is false, then the promise rejection is handled. Otherwise, the promise rejection is not handled. 259 260 // 4. If p's [[PromiseIsHandled]] internal slot is false, add p to settings object's outstanding rejected promises weak set. 261 if (!promise->is_handled()) 262 m_outstanding_rejected_promises_weak_set.append(promise); 263 264 // This algorithm results in promise rejections being marked as handled or not handled. These concepts parallel handled and not handled script errors. 265 // If a rejection is still not handled after this, then the rejection may be reported to a developer console. 266 if (not_handled) 267 HTML::report_exception_to_console(promise->result(), realm(), ErrorInPromise::Yes); 268 } 269 }); 270} 271 272// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-script 273bool EnvironmentSettingsObject::is_scripting_enabled() const 274{ 275 // Scripting is enabled for an environment settings object settings when all of the following conditions are true: 276 // The user agent supports scripting. 277 // NOTE: This is always true in LibWeb :^) 278 279 // FIXME: Do the right thing for workers. 280 if (!is<HTML::Window>(m_realm_execution_context->realm->global_object())) 281 return true; 282 283 // The user has not disabled scripting for settings at this time. (User agents may provide users with the option to disable scripting globally, or in a finer-grained manner, e.g., on a per-origin basis, down to the level of individual environment settings objects.) 284 auto document = const_cast<EnvironmentSettingsObject&>(*this).responsible_document(); 285 VERIFY(document); 286 if (!document->page() || !document->page()->is_scripting_enabled()) 287 return false; 288 289 // FIXME: Either settings's global object is not a Window object, or settings's global object's associated Document's active sandboxing flag set does not have its sandboxed scripts browsing context flag set. 290 291 return true; 292} 293 294// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-noscript 295bool EnvironmentSettingsObject::is_scripting_disabled() const 296{ 297 // Scripting is disabled for an environment settings object when scripting is not enabled for it, i.e., when any of the above conditions are false. 298 return !is_scripting_enabled(); 299} 300 301// https://html.spec.whatwg.org/multipage/webappapis.html#module-type-allowed 302bool EnvironmentSettingsObject::module_type_allowed(AK::DeprecatedString const& module_type) const 303{ 304 // 1. If moduleType is not "javascript", "css", or "json", then return false. 305 if (module_type != "javascript"sv && module_type != "css"sv && module_type != "json"sv) 306 return false; 307 308 // FIXME: 2. If moduleType is "css" and the CSSStyleSheet interface is not exposed in settings's Realm, then return false. 309 310 // 3. Return true. 311 return true; 312} 313 314// https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps 315void EnvironmentSettingsObject::disallow_further_import_maps() 316{ 317 // 1. Let global be settingsObject's global object. 318 auto& global = global_object(); 319 320 // 2. If global does not implement Window, then return. 321 if (!is<Window>(global)) 322 return; 323 324 // 3. Set global's import maps allowed to false. 325 verify_cast<Window>(global).set_import_maps_allowed(false); 326} 327 328// https://html.spec.whatwg.org/multipage/webappapis.html#incumbent-settings-object 329EnvironmentSettingsObject& incumbent_settings_object() 330{ 331 auto& event_loop = HTML::main_thread_event_loop(); 332 auto& vm = event_loop.vm(); 333 334 // 1. Let context be the topmost script-having execution context. 335 auto* context = top_most_script_having_execution_context(vm); 336 337 // 2. If context is null, or if context's skip-when-determining-incumbent counter is greater than zero, then: 338 if (!context || context->skip_when_determining_incumbent_counter > 0) { 339 // 1. Assert: the backup incumbent settings object stack is not empty. 340 // NOTE: If this assertion fails, it's because the incumbent settings object was used with no involvement of JavaScript. 341 VERIFY(!event_loop.is_backup_incumbent_settings_object_stack_empty()); 342 343 // 2. Return the topmost entry of the backup incumbent settings object stack. 344 return event_loop.top_of_backup_incumbent_settings_object_stack(); 345 } 346 347 // 3. Return context's Realm component's settings object. 348 return Bindings::host_defined_environment_settings_object(*context->realm); 349} 350 351// https://html.spec.whatwg.org/multipage/webappapis.html#concept-incumbent-realm 352JS::Realm& incumbent_realm() 353{ 354 // Then, the incumbent Realm is the Realm of the incumbent settings object. 355 return incumbent_settings_object().realm(); 356} 357 358// https://html.spec.whatwg.org/multipage/webappapis.html#concept-incumbent-global 359JS::Object& incumbent_global_object() 360{ 361 // Similarly, the incumbent global object is the global object of the incumbent settings object. 362 return incumbent_settings_object().global_object(); 363} 364 365// https://html.spec.whatwg.org/multipage/webappapis.html#current-settings-object 366EnvironmentSettingsObject& current_settings_object() 367{ 368 auto& event_loop = HTML::main_thread_event_loop(); 369 auto& vm = event_loop.vm(); 370 371 // Then, the current settings object is the environment settings object of the current Realm Record. 372 return Bindings::host_defined_environment_settings_object(*vm.current_realm()); 373} 374 375// https://html.spec.whatwg.org/multipage/webappapis.html#current-global-object 376JS::Object& current_global_object() 377{ 378 auto& event_loop = HTML::main_thread_event_loop(); 379 auto& vm = event_loop.vm(); 380 381 // Similarly, the current global object is the global object of the current Realm Record. 382 return vm.current_realm()->global_object(); 383} 384 385// https://html.spec.whatwg.org/multipage/webappapis.html#concept-relevant-realm 386JS::Realm& relevant_realm(JS::Object const& object) 387{ 388 // The relevant Realm for a platform object is the value of its [[Realm]] field. 389 return object.shape().realm(); 390} 391 392// https://html.spec.whatwg.org/multipage/webappapis.html#relevant-settings-object 393EnvironmentSettingsObject& relevant_settings_object(JS::Object const& object) 394{ 395 // Then, the relevant settings object for a platform object o is the environment settings object of the relevant Realm for o. 396 return Bindings::host_defined_environment_settings_object(relevant_realm(object)); 397} 398 399EnvironmentSettingsObject& relevant_settings_object(DOM::Node const& node) 400{ 401 // Then, the relevant settings object for a platform object o is the environment settings object of the relevant Realm for o. 402 return const_cast<DOM::Document&>(node.document()).relevant_settings_object(); 403} 404 405// https://html.spec.whatwg.org/multipage/webappapis.html#concept-relevant-global 406JS::Object& relevant_global_object(JS::Object const& object) 407{ 408 // Similarly, the relevant global object for a platform object o is the global object of the relevant Realm for o. 409 return relevant_realm(object).global_object(); 410} 411 412// https://html.spec.whatwg.org/multipage/webappapis.html#concept-entry-realm 413JS::Realm& entry_realm() 414{ 415 auto& event_loop = HTML::main_thread_event_loop(); 416 auto& vm = event_loop.vm(); 417 418 // With this in hand, we define the entry execution context to be the most recently pushed item in the JavaScript execution context stack that is a realm execution context. 419 // The entry realm is the entry execution context's Realm component. 420 // NOTE: Currently all execution contexts in LibJS are realm execution contexts 421 return *vm.running_execution_context().realm; 422} 423 424// https://html.spec.whatwg.org/multipage/webappapis.html#entry-settings-object 425EnvironmentSettingsObject& entry_settings_object() 426{ 427 // Then, the entry settings object is the environment settings object of the entry realm. 428 return Bindings::host_defined_environment_settings_object(entry_realm()); 429} 430 431// https://html.spec.whatwg.org/multipage/webappapis.html#entry-global-object 432JS::Object& entry_global_object() 433{ 434 // Similarly, the entry global object is the global object of the entry realm. 435 return entry_realm().global_object(); 436} 437 438// https://html.spec.whatwg.org/multipage/webappapis.html#secure-context 439bool is_secure_context(Environment const& environment) 440{ 441 // 1. If environment is an environment settings object, then: 442 if (is<EnvironmentSettingsObject>(environment)) { 443 // 1. Let global be environment's global object. 444 // FIXME: Add a const global_object() getter to ESO 445 auto& global = static_cast<EnvironmentSettingsObject&>(const_cast<Environment&>(environment)).global_object(); 446 447 // 2. If global is a WorkerGlobalScope, then: 448 if (is<WorkerGlobalScope>(global)) { 449 // FIXME: 1. If global's owner set[0]'s relevant settings object is a secure context, then return true. 450 // NOTE: We only need to check the 0th item since they will necessarily all be consistent. 451 452 // 2. Return false. 453 return false; 454 } 455 456 // FIXME: 3. If global is a WorkletGlobalScope, then return true. 457 // NOTE: Worklets can only be created in secure contexts. 458 } 459 460 // 2. If the result of Is url potentially trustworthy? given environment's top-level creation URL is "Potentially Trustworthy", then return true. 461 if (SecureContexts::is_url_potentially_trustworthy(environment.top_level_creation_url) == SecureContexts::Trustworthiness::PotentiallyTrustworthy) 462 return true; 463 464 // 3. Return false. 465 return false; 466} 467 468// https://html.spec.whatwg.org/multipage/webappapis.html#non-secure-context 469bool is_non_secure_context(Environment const& environment) 470{ 471 // An environment is a non-secure context if it is not a secure context. 472 return !is_secure_context(environment); 473} 474 475}