Files
ladybird/Libraries/LibWeb/HTML/Scripting/Environments.h
Aliaksandr Kalenik 737691c43a LibWeb: Keep worker startup reachable until script load completes
Fixes flakiness in worker tests that create a Worker or SharedWorker
with a missing script URL and only attach an error handler to it.
Once the test callback returns, nothing keeps the worker rooted from
JavaScript. If GC ran before the WebWorker process reported the
script fetch failure, the Worker/WorkerAgentParent cycle could be
collected and the error event never delivered, leaving the test hung
until timeout.

Hold startup-pending WorkerAgentParents from the outside
EnvironmentSettingsObject and release that edge once the script load
succeeds, fails, or the worker closes. The worker now survives long
enough to deliver its first script-load result.
2026-04-27 18:02:49 +02:00

240 lines
11 KiB
C++

/*
* Copyright (c) 2021-2025, Luke Wilde <luke@ladybird.org>
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Forward.h>
#include <LibURL/Origin.h>
#include <LibURL/URL.h>
#include <LibWeb/Export.h>
#include <LibWeb/Fetch/Infrastructure/FetchRecord.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/Scripting/ModuleMap.h>
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
#include <LibWeb/ServiceWorker/Registration.h>
namespace Web::HTML {
class UniversalGlobalScopeMixin;
// https://html.spec.whatwg.org/multipage/webappapis.html#environment
struct WEB_API Environment : public JS::Cell {
GC_CELL(Environment, JS::Cell);
GC_DECLARE_ALLOCATOR(Environment);
public:
virtual ~Environment() override;
// An id https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-id
String id;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-creation-url
URL::URL creation_url;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-creation-url
// Null or a URL that represents the creation URL of the "top-level" environment. It is null for workers and worklets.
Optional<URL::URL> top_level_creation_url;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin
// A for now implementation-defined value, null, or an origin. For a "top-level" potential execution environment it is null
// (i.e., when there is no response yet); otherwise it is the "top-level" environment's origin. For a dedicated worker or worklet
// it is the top-level origin of its creator. For a shared or service worker it is an implementation-defined value.
Optional<URL::Origin> top_level_origin;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-target-browsing-context
GC::Ptr<BrowsingContext> target_browsing_context;
// FIXME: An active service worker https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-active-service-worker
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-execution-ready-flag
bool execution_ready { false };
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-discarding-steps
virtual void discard_environment() { }
protected:
Environment() = default;
Environment(String id, URL::URL creation_url, Optional<URL::URL> top_level_creation_url, Optional<URL::Origin> top_level_origin, GC::Ptr<BrowsingContext> target_browsing_context)
: id(move(id))
, creation_url(move(creation_url))
, top_level_creation_url(move(top_level_creation_url))
, top_level_origin(move(top_level_origin))
, target_browsing_context(move(target_browsing_context))
{
}
virtual void visit_edges(Cell::Visitor&) override;
};
enum class RunScriptDecision {
Run,
DoNotRun,
};
// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
struct WEB_API EnvironmentSettingsObject : public Environment {
GC_CELL(EnvironmentSettingsObject, Environment);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual void finalize() override;
virtual void initialize(JS::Realm&) override;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-target-browsing-context
JS::ExecutionContext& realm_execution_context();
JS::ExecutionContext const& realm_execution_context() const;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-module-map
ModuleMap& module_map();
// https://html.spec.whatwg.org/multipage/webappapis.html#responsible-document
virtual GC::Ptr<DOM::Document> responsible_document() = 0;
// https://html.spec.whatwg.org/multipage/webappapis.html#api-base-url
virtual URL::URL api_base_url() const = 0;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin
virtual URL::Origin origin() const = 0;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-has-cross-site-ancestor
virtual bool has_cross_site_ancestor() const = 0;
// A policy container https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-policy-container
virtual GC::Ref<PolicyContainer> policy_container() const = 0;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-cross-origin-isolated-capability
virtual CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() const = 0;
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-time-origin
virtual double time_origin() const = 0;
Optional<URL::URL> parse_url(StringView);
Optional<URL::URL> encoding_parse_url(StringView);
Optional<String> encoding_parse_and_serialize_url(StringView);
JS::Realm& realm();
JS::Object& global_object();
JS::Object const& global_object() const { return const_cast<EnvironmentSettingsObject*>(this)->global_object(); }
UniversalGlobalScopeMixin& universal_global_scope();
UniversalGlobalScopeMixin const& universal_global_scope() const { return const_cast<EnvironmentSettingsObject*>(this)->universal_global_scope(); }
EventLoop& responsible_event_loop();
// https://fetch.spec.whatwg.org/#concept-fetch-group
auto& fetch_group() { return m_fetch_group; }
SerializedEnvironmentSettingsObject serialize();
GC::Ref<StorageAPI::StorageManager> storage_manager();
// https://w3c.github.io/ServiceWorker/#get-the-service-worker-registration-object
GC::Ref<ServiceWorker::ServiceWorkerRegistration> get_service_worker_registration_object(ServiceWorker::Registration const&);
// https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
GC::Ref<ServiceWorker::ServiceWorker> get_service_worker_object(ServiceWorker::ServiceWorkerRecord*);
[[nodiscard]] bool discarded() const { return m_discarded; }
void set_discarded(bool b) { m_discarded = b; }
virtual void discard_environment() override;
void keep_worker_agent_alive_while_starting(WorkerAgentParent&);
void release_worker_agent_from_startup_keep_alive(WorkerAgentParent&);
// FIXME: This method below is from HighResolutionTime spec in section 3. Section for Specification Authors.
// The following other methods are currently not supported:
// `current relative timestamp` https://www.w3.org/TR/hr-time-3/#dfn-current-relative-timestamp
// `current monotonic time` https://www.w3.org/TR/hr-time-3/#dfn-current-monotonic-time
// `current coarsened wall time` https://www.w3.org/TR/hr-time-3/#dfn-current-wall-time
// https://w3c.github.io/hr-time/#dfn-eso-current-wall-time
HighResolutionTime::DOMHighResTimeStamp current_wall_time() const
{
// An environment settings object settingsObject's current wall time is the result of the following steps:
// 1. Let unsafeWallTime be the wall clock's unsafe current time.
auto unsafe_walltime = HighResolutionTime::wall_clock_unsafe_current_time();
// 2. Return the result of calling coarsen time with unsafeWallTime and settingsObject's cross-origin isolated capability.
return HighResolutionTime::coarsen_time(unsafe_walltime, cross_origin_isolated_capability());
}
protected:
explicit EnvironmentSettingsObject(NonnullOwnPtr<JS::ExecutionContext>);
virtual void visit_edges(Cell::Visitor&) override;
private:
NonnullOwnPtr<JS::ExecutionContext> m_realm_execution_context;
GC::Ptr<ModuleMap> m_module_map;
UniversalGlobalScopeMixin* m_universal_global_scope { nullptr };
GC::Ptr<EventLoop> m_responsible_event_loop;
// https://fetch.spec.whatwg.org/#concept-fetch-record
// A fetch group holds an ordered list of fetch records
Fetch::Infrastructure::FetchRecord::List m_fetch_group;
// https://storage.spec.whatwg.org/#api
// Each environment settings object has an associated StorageManager object.
GC::Ptr<StorageAPI::StorageManager> m_storage_manager;
// https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-registration-object-map
// An environment settings object has a service worker registration object map,
// a map where the keys are service worker registrations and the values are ServiceWorkerRegistration objects.
HashMap<ServiceWorker::RegistrationKey, GC::Ref<ServiceWorker::ServiceWorkerRegistration>> m_service_worker_registration_object_map;
// https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-object-map
// An environment settings object has a service worker object map,
// a map where the keys are service workers and the values are ServiceWorker objects.
HashMap<ServiceWorker::ServiceWorkerRecord*, GC::Ref<ServiceWorker::ServiceWorker>> m_service_worker_object_map;
// https://w3c.github.io/ServiceWorker/#service-worker-client-discarded-flag
// A service worker client has an associated discarded flag. It is initially unset.
bool m_discarded { false };
Vector<GC::Ref<WorkerAgentParent>> m_worker_agents_to_keep_alive_while_starting;
};
RunScriptDecision can_run_script(EnvironmentSettingsObject const&);
bool is_scripting_enabled(EnvironmentSettingsObject const&);
bool is_scripting_disabled(EnvironmentSettingsObject const&);
void prepare_to_run_script(EnvironmentSettingsObject&);
void clean_up_after_running_script(EnvironmentSettingsObject const&);
WEB_API void prepare_to_run_callback(EnvironmentSettingsObject&);
WEB_API void clean_up_after_running_callback(EnvironmentSettingsObject const&);
WEB_API bool module_type_allowed(EnvironmentSettingsObject const&, StringView module_type);
WEB_API void add_module_to_resolved_module_set(EnvironmentSettingsObject&, String const& serialized_base_url, String const& normalized_specifier, Optional<URL::URL> const& as_url);
WEB_API EnvironmentSettingsObject& incumbent_settings_object();
WEB_API JS::Realm& incumbent_realm();
JS::Object& incumbent_global_object();
EnvironmentSettingsObject& principal_realm_settings_object(JS::Realm&);
EnvironmentSettingsObject& current_settings_object();
WEB_API JS::Object& current_global_object();
WEB_API JS::Realm& relevant_realm(JS::Object const&);
WEB_API EnvironmentSettingsObject& relevant_settings_object(JS::Object const&);
EnvironmentSettingsObject& relevant_settings_object(DOM::Node const&);
WEB_API JS::Object& relevant_global_object(JS::Object const&);
JS::Realm& entry_realm();
EnvironmentSettingsObject& entry_settings_object();
JS::Object& entry_global_object();
[[nodiscard]] WEB_API bool is_secure_context(Environment const&);
[[nodiscard]] bool is_non_secure_context(Environment const&);
}