LibWeb: Ignore window-forwarded document.body.onfoo in detached DOM

Normally, assigning to e.g document.body.onload will forward to
window.onload. However, in a detached DOM tree, there is no associated
window, so we have nowhere to forward to, making this a no-op.

The bulk of this change is making Document::window() return a nullable
pointer, as documents created by DOMParser or DOMImplementation do not
have an associated window object, and so must be able to return null
from here.
This commit is contained in:
Andreas Kling 2024-03-10 08:41:18 +01:00
parent 7139d5945f
commit b98a2be96b
Notes: sideshowbarker 2024-07-19 01:59:31 +09:00
28 changed files with 92 additions and 61 deletions

View file

@ -0,0 +1 @@
PASS (didn't crash)

View file

@ -0,0 +1,10 @@
<script src="../include.js"></script>
<script>
test(() => {
var parser = new DOMParser();
var doc = parser.parseFromString("", "text/html");
var body = doc.createElement("body");
body.onblur = null;
println("PASS (didn't crash)");
});
</script>

View file

@ -19,7 +19,7 @@ JS_DEFINE_ALLOCATOR(DocumentTimeline);
JS::NonnullGCPtr<DocumentTimeline> DocumentTimeline::create(JS::Realm& realm, DOM::Document& document, HighResolutionTime::DOMHighResTimeStamp origin_time)
{
auto timeline = realm.heap().allocate<DocumentTimeline>(realm, realm, document, origin_time);
timeline->set_current_time(document.window().performance()->now());
timeline->set_current_time(document.window()->performance()->now());
return timeline;
}

View file

@ -60,9 +60,13 @@ bool MediaQueryList::matches() const
bool MediaQueryList::evaluate()
{
auto window = m_document->window();
if (!window)
return false;
bool now_matches = false;
for (auto& media : m_media) {
now_matches = now_matches || media->evaluate(m_document->window());
now_matches = now_matches || media->evaluate(*window);
}
return now_matches;

View file

@ -56,7 +56,9 @@ URL ParsingContext::complete_url(StringView relative_url) const
HTML::Window const* ParsingContext::window() const
{
return m_document && m_document->default_view() ? &m_document->window() : nullptr;
if (!m_document)
return nullptr;
return m_document->window();
}
}

View file

@ -638,7 +638,7 @@ WebIDL::ExceptionOr<JS::GCPtr<HTML::WindowProxy>> Document::open(StringView url,
return WebIDL::InvalidAccessError::create(realm(), "Cannot perform open on a document that isn't fully active."_fly_string);
// 2. Return the result of running the window open steps with url, name, and features.
return window().open_impl(url, name, features);
return window()->open_impl(url, name, features);
}
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#closing-the-input-stream
@ -2273,7 +2273,7 @@ JS::GCPtr<HTML::Location> Document::location()
if (!is_fully_active())
return nullptr;
return window().location();
return window()->location();
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-document-hidden
@ -2330,7 +2330,7 @@ void Document::run_the_resize_steps()
return;
m_last_viewport_size = viewport_size;
window().dispatch_event(DOM::Event::create(realm(), UIEvents::EventNames::resize));
window()->dispatch_event(DOM::Event::create(realm(), UIEvents::EventNames::resize));
schedule_layout_update();
}
@ -2401,9 +2401,13 @@ void Document::evaluate_media_queries_and_report_changes()
void Document::evaluate_media_rules()
{
auto window = this->window();
if (!window)
return;
bool any_media_queries_changed_match_state = false;
for (auto& style_sheet : style_sheets().sheets()) {
if (style_sheet->evaluate_media_queries(window()))
if (style_sheet->evaluate_media_queries(*window))
any_media_queries_changed_match_state = true;
}
@ -3305,6 +3309,10 @@ void Document::unregister_resize_observer(Badge<ResizeObserver::ResizeObserver>,
// https://www.w3.org/TR/intersection-observer/#queue-an-intersection-observer-task
void Document::queue_intersection_observer_task()
{
auto window = this->window();
if (!window)
return;
// 1. If documents IntersectionObserverTaskQueued flag is set to true, return.
if (m_intersection_observer_task_queued)
return;
@ -3313,7 +3321,7 @@ void Document::queue_intersection_observer_task()
m_intersection_observer_task_queued = true;
// 3. Queue a task on the IntersectionObserver task source associated with the document's event loop to notify intersection observers.
HTML::queue_global_task(HTML::Task::Source::IntersectionObserver, window(), [this]() {
HTML::queue_global_task(HTML::Task::Source::IntersectionObserver, *window, [this]() {
auto& realm = this->realm();
// https://www.w3.org/TR/intersection-observer/#notify-intersection-observers
@ -4039,7 +4047,7 @@ void Document::ensure_animation_timer()
return;
}
update_animations_and_send_events(window().performance()->now());
update_animations_and_send_events(window()->performance()->now());
for (auto& timeline : m_associated_animation_timelines) {
for (auto& animation : timeline->associated_animations())

View file

@ -317,7 +317,7 @@ public:
HTML::DocumentReadyState readiness() const { return m_readiness; }
void update_readiness(HTML::DocumentReadyState);
HTML::Window& window() const { return const_cast<HTML::Window&>(*m_window); }
[[nodiscard]] JS::GCPtr<HTML::Window> window() const { return m_window; }
void set_window(HTML::Window&);
@ -616,7 +616,7 @@ protected:
private:
// ^HTML::GlobalEventHandlers
virtual EventTarget& global_event_handlers_to_event_target(FlyString const&) final { return *this; }
virtual JS::GCPtr<EventTarget> global_event_handlers_to_event_target(FlyString const&) final { return *this; }
void tear_down_layout_tree();

View file

@ -327,7 +327,7 @@ static EventTarget* determine_target_of_event_handler(EventTarget& event_target,
return nullptr;
// 4. Return eventTarget's node document's relevant global object.
return &event_target_element.document().window();
return &verify_cast<EventTarget>(HTML::relevant_global_object(event_target_element.document()));
}
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:event-handler-idl-attributes-2
@ -807,8 +807,8 @@ bool EventTarget::dispatch_event(Event& event)
static_cast<HTML::Window*>(this)->set_last_activation_timestamp(current_time);
} else if (is<DOM::Element>(this)) {
auto const* element = static_cast<DOM::Element const*>(this);
auto& window = element->document().window();
window.set_last_activation_timestamp(current_time);
if (auto window = element->document().window())
window->set_last_activation_timestamp(current_time);
}
}

View file

@ -55,7 +55,7 @@ static void run_focus_update_steps(Vector<JS::Handle<DOM::Node>> old_chain, Vect
blur_event_target = entry.ptr();
} else if (is<DOM::Document>(*entry)) {
// If entry is a Document object, let blur event target be that Document object's relevant global object.
blur_event_target = &static_cast<DOM::Document&>(*entry).window();
blur_event_target = static_cast<DOM::Document&>(*entry).window();
}
// 3. If entry is the last entry in old chain, and entry is an Element,
@ -105,7 +105,7 @@ static void run_focus_update_steps(Vector<JS::Handle<DOM::Node>> old_chain, Vect
focus_event_target = entry.ptr();
} else if (is<DOM::Document>(*entry)) {
// If entry is a Document object, let focus event target be that Document object's relevant global object.
focus_event_target = &static_cast<DOM::Document&>(*entry).window();
focus_event_target = static_cast<DOM::Document&>(*entry).window();
}
// 3. If entry is the last entry in new chain, and entry is an Element,

View file

@ -12,14 +12,17 @@
namespace Web::HTML {
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void GlobalEventHandlers::set_##attribute_name(WebIDL::CallbackType* value) \
{ \
global_event_handlers_to_event_target(event_name).set_event_handler_attribute(event_name, value); \
} \
WebIDL::CallbackType* GlobalEventHandlers::attribute_name() \
{ \
return global_event_handlers_to_event_target(event_name).event_handler_attribute(event_name); \
#define __ENUMERATE(attribute_name, event_name) \
void GlobalEventHandlers::set_##attribute_name(WebIDL::CallbackType* value) \
{ \
if (auto event_target = global_event_handlers_to_event_target(event_name)) \
event_target->set_event_handler_attribute(event_name, value); \
} \
WebIDL::CallbackType* GlobalEventHandlers::attribute_name() \
{ \
if (auto event_target = global_event_handlers_to_event_target(event_name)) \
return event_target->event_handler_attribute(event_name); \
return nullptr; \
}
ENUMERATE_GLOBAL_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View file

@ -94,7 +94,7 @@ public:
#undef __ENUMERATE
protected:
virtual DOM::EventTarget& global_event_handlers_to_event_target(FlyString const& event_name) = 0;
virtual JS::GCPtr<DOM::EventTarget> global_event_handlers_to_event_target(FlyString const& event_name) = 0;
};
}

View file

@ -94,7 +94,7 @@ void HTMLBodyElement::attribute_changed(FlyString const& name, Optional<String>
#undef __ENUMERATE
}
DOM::EventTarget& HTMLBodyElement::global_event_handlers_to_event_target(FlyString const& event_name)
JS::GCPtr<DOM::EventTarget> HTMLBodyElement::global_event_handlers_to_event_target(FlyString const& event_name)
{
// NOTE: This is a little weird, but IIUC document.body.onload actually refers to window.onload
// NOTE: document.body can return either a HTMLBodyElement or HTMLFrameSetElement, so both these elements must support this mapping.
@ -104,7 +104,7 @@ DOM::EventTarget& HTMLBodyElement::global_event_handlers_to_event_target(FlyStri
return *this;
}
DOM::EventTarget& HTMLBodyElement::window_event_handlers_to_event_target()
JS::GCPtr<DOM::EventTarget> HTMLBodyElement::window_event_handlers_to_event_target()
{
// All WindowEventHandlers on HTMLFrameSetElement (e.g. document.body.onrejectionhandled) are mapped to window.on{event}.
// NOTE: document.body can return either a HTMLBodyElement or HTMLFrameSetElement, so both these elements must support this mapping.

View file

@ -38,10 +38,10 @@ private:
virtual void initialize(JS::Realm&) override;
// ^HTML::GlobalEventHandlers
virtual EventTarget& global_event_handlers_to_event_target(FlyString const& event_name) override;
virtual JS::GCPtr<DOM::EventTarget> global_event_handlers_to_event_target(FlyString const& event_name) override;
// ^HTML::WindowEventHandlers
virtual EventTarget& window_event_handlers_to_event_target() override;
virtual JS::GCPtr<DOM::EventTarget> window_event_handlers_to_event_target() override;
RefPtr<CSS::ImageStyleValue> m_background_style_value;
};

View file

@ -82,7 +82,7 @@ private:
virtual bool is_html_element() const final { return true; }
// ^HTML::GlobalEventHandlers
virtual DOM::EventTarget& global_event_handlers_to_event_target(FlyString const&) override { return *this; }
virtual JS::GCPtr<DOM::EventTarget> global_event_handlers_to_event_target(FlyString const&) override { return *this; }
virtual void did_receive_focus() override;
JS::GCPtr<DOMStringMap> m_dataset;

View file

@ -38,7 +38,7 @@ void HTMLFrameSetElement::attribute_changed(FlyString const& name, Optional<Stri
#undef __ENUMERATE
}
DOM::EventTarget& HTMLFrameSetElement::global_event_handlers_to_event_target(FlyString const& event_name)
JS::GCPtr<DOM::EventTarget> HTMLFrameSetElement::global_event_handlers_to_event_target(FlyString const& event_name)
{
// NOTE: This is a little weird, but IIUC document.body.onload actually refers to window.onload
// NOTE: document.body can return either a HTMLBodyElement or HTMLFrameSetElement, so both these elements must support this mapping.
@ -48,7 +48,7 @@ DOM::EventTarget& HTMLFrameSetElement::global_event_handlers_to_event_target(Fly
return *this;
}
DOM::EventTarget& HTMLFrameSetElement::window_event_handlers_to_event_target()
JS::GCPtr<DOM::EventTarget> HTMLFrameSetElement::window_event_handlers_to_event_target()
{
// All WindowEventHandlers on HTMLFrameSetElement (e.g. document.body.onrejectionhandled) are mapped to window.on{event}.
// NOTE: document.body can return either a HTMLBodyElement or HTMLFrameSetElement, so both these elements must support this mapping.

View file

@ -28,10 +28,10 @@ private:
virtual void attribute_changed(FlyString const&, Optional<String> const&) override;
// ^HTML::GlobalEventHandlers
virtual EventTarget& global_event_handlers_to_event_target(FlyString const& event_name) override;
virtual JS::GCPtr<EventTarget> global_event_handlers_to_event_target(FlyString const& event_name) override;
// ^HTML::WindowEventHandlers
virtual EventTarget& window_event_handlers_to_event_target() override;
virtual JS::GCPtr<EventTarget> window_event_handlers_to_event_target() override;
};
}

View file

@ -941,7 +941,7 @@ static void update_the_source_set(DOM::Element& element)
if (child->has_attribute(HTML::AttributeNames::media)) {
auto media_query = parse_media_query(CSS::Parser::ParsingContext { element.document() },
child->get_attribute_value(HTML::AttributeNames::media));
if (!media_query || !media_query->evaluate(element.document().window())) {
if (!media_query || !element.document().window() || !media_query->evaluate(*element.document().window())) {
continue;
}
}

View file

@ -64,7 +64,7 @@ void HTMLMetaElement::inserted()
auto media = attribute(AttributeNames::media);
if (media.has_value()) {
auto query = parse_media_query(context, media.value());
if (!query->evaluate(document().window()))
if (document().window() && !query->evaluate(*document().window()))
return;
}

View file

@ -328,7 +328,7 @@ void HTMLParser::the_end(JS::NonnullGCPtr<DOM::Document> document, JS::GCPtr<HTM
return;
// 3. Let window be the Document's relevant global object.
JS::NonnullGCPtr<Window> window = document->window();
auto& window = verify_cast<Window>(relevant_global_object(*document));
// 4. Set the Document's load timing info's load event start time to the current high resolution time given window.
document->load_timing_info().load_event_start_time = HighResolutionTime::unsafe_shared_current_time();
@ -336,7 +336,7 @@ void HTMLParser::the_end(JS::NonnullGCPtr<DOM::Document> document, JS::GCPtr<HTM
// 5. Fire an event named load at window, with legacy target override flag set.
// FIXME: The legacy target override flag is currently set by a virtual override of dispatch_event()
// We should reorganize this so that the flag appears explicitly here instead.
window->dispatch_event(DOM::Event::create(document->realm(), HTML::EventNames::load));
window.dispatch_event(DOM::Event::create(document->realm(), HTML::EventNames::load));
// FIXME: 6. Invoke WebDriver BiDi load complete with the Document's browsing context, and a new WebDriver BiDi navigation status whose id is the Document object's navigation id, status is "complete", and url is the Document object's URL.
@ -352,7 +352,7 @@ void HTMLParser::the_end(JS::NonnullGCPtr<DOM::Document> document, JS::GCPtr<HTM
document->set_page_showing(true);
// 11. Fire a page transition event named pageshow at window with false.
window->fire_a_page_transition_event(HTML::EventNames::pageshow, false);
window.fire_a_page_transition_event(HTML::EventNames::pageshow, false);
// 12. Completely finish loading the Document.
document->completely_finish_loading();

View file

@ -79,7 +79,7 @@ JS_DEFINE_ALLOCATOR(Window);
void run_animation_frame_callbacks(DOM::Document& document, double now)
{
// FIXME: Bring this closer to the spec.
document.window().animation_frame_callback_driver().run(now);
document.window()->animation_frame_callback_driver().run(now);
}
class IdleCallback : public RefCounted<IdleCallback> {

View file

@ -218,10 +218,10 @@ private:
virtual void finalize() override;
// ^HTML::GlobalEventHandlers
virtual DOM::EventTarget& global_event_handlers_to_event_target(FlyString const&) override { return *this; }
virtual JS::GCPtr<DOM::EventTarget> global_event_handlers_to_event_target(FlyString const&) override { return *this; }
// ^HTML::WindowEventHandlers
virtual DOM::EventTarget& window_event_handlers_to_event_target() override { return *this; }
virtual JS::GCPtr<DOM::EventTarget> window_event_handlers_to_event_target() override { return *this; }
void invoke_idle_callbacks();

View file

@ -11,14 +11,17 @@
namespace Web::HTML {
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void WindowEventHandlers::set_##attribute_name(WebIDL::CallbackType* value) \
{ \
window_event_handlers_to_event_target().set_event_handler_attribute(event_name, value); \
} \
WebIDL::CallbackType* WindowEventHandlers::attribute_name() \
{ \
return window_event_handlers_to_event_target().event_handler_attribute(event_name); \
#define __ENUMERATE(attribute_name, event_name) \
void WindowEventHandlers::set_##attribute_name(WebIDL::CallbackType* value) \
{ \
if (auto event_target = window_event_handlers_to_event_target()) \
event_target->set_event_handler_attribute(event_name, value); \
} \
WebIDL::CallbackType* WindowEventHandlers::attribute_name() \
{ \
if (auto event_target = window_event_handlers_to_event_target()) \
return event_target->event_handler_attribute(event_name); \
return nullptr; \
}
ENUMERATE_WINDOW_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View file

@ -41,7 +41,7 @@ public:
#undef __ENUMERATE
protected:
virtual DOM::EventTarget& window_event_handlers_to_event_target() = 0;
virtual JS::GCPtr<DOM::EventTarget> window_event_handlers_to_event_target() = 0;
};
}

View file

@ -29,7 +29,7 @@ public:
void blur();
protected:
virtual DOM::EventTarget& global_event_handlers_to_event_target(FlyString const&) override { return *this; }
virtual JS::GCPtr<DOM::EventTarget> global_event_handlers_to_event_target(FlyString const&) override { return *this; }
private:
MathMLElement(DOM::Document&, DOM::QualifiedName);

View file

@ -44,7 +44,7 @@ JS::NonnullGCPtr<ResizeObserverSize> ResizeObserverSize::calculate_box_size(JS::
computed_size->set_block_size(paintable_box.content_height().to_double());
break;
case Bindings::ResizeObserverBoxOptions::DevicePixelContentBox: {
auto device_pixel_ratio = target.document().window().device_pixel_ratio();
auto device_pixel_ratio = target.document().window()->device_pixel_ratio();
// 1. Set computedSizes inlineSize attribute to targets content area inline length, in integral device pixels.
computed_size->set_inline_size(paintable_box.border_box_width().to_double() * device_pixel_ratio);
// 2. Set computedSizes blockSize attribute to targets content area block length, in integral device pixels.

View file

@ -227,15 +227,15 @@ static JS::ThrowCompletionOr<JS::Value> execute_a_function_body(Web::Page& page,
// 1. Let window be the associated window of the current browsing contexts active document.
// FIXME: This will need adjusting when WebDriver supports frames.
auto& window = page.top_level_browsing_context().active_document()->window();
auto window = page.top_level_browsing_context().active_document()->window();
// 2. Let environment settings be the environment settings object for window.
auto& environment_settings = Web::HTML::relevant_settings_object(window);
auto& environment_settings = Web::HTML::relevant_settings_object(*window);
// 3. Let global scope be environment settings realms global environment.
auto& global_scope = environment_settings.realm().global_environment();
auto& realm = window.realm();
auto& realm = window->realm();
bool contains_direct_call_to_eval = false;
auto source_text = ByteString::formatted("function() {{ {} }}", body);
@ -271,7 +271,7 @@ static JS::ThrowCompletionOr<JS::Value> execute_a_function_body(Web::Page& page,
// 9. Let completion be Function.[[Call]](window, parameters) with function as the this value.
// NOTE: This is not entirely clear, but I don't think they mean actually passing `function` as
// the this value argument, but using it as the object [[Call]] is executed on.
auto completion = function->internal_call(&window, move(parameters));
auto completion = function->internal_call(window, move(parameters));
// 10. Clean up after running a callback with environment settings.
environment_settings.clean_up_after_running_callback();

View file

@ -54,7 +54,7 @@ Response capture_element_screenshot(Painter const& painter, Page& page, DOM::Ele
{
Optional<Response> encoded_string_or_error;
element.document().window().animation_frame_callback_driver().add([&](auto) {
element.document().window()->animation_frame_callback_driver().add([&](auto) {
auto viewport_rect = page.top_level_traversable()->viewport_rect();
rect.intersect(page.enclosing_device_rect(viewport_rect).to_type<int>());

View file

@ -436,7 +436,7 @@ void ConnectionFromClient::debug_request(u64 page_id, ByteString const& request,
if (request == "dump-local-storage") {
if (auto* document = page.page().top_level_browsing_context().active_document())
document->window().local_storage().release_value_but_fixme_should_propagate_errors()->dump();
document->window()->local_storage().release_value_but_fixme_should_propagate_errors()->dump();
return;
}
@ -1086,7 +1086,7 @@ Messages::WebContentServer::GetLocalStorageEntriesResponse ConnectionFromClient:
auto& page = maybe_page.release_value();
auto* document = page.page().top_level_browsing_context().active_document();
auto local_storage = document->window().local_storage().release_value_but_fixme_should_propagate_errors();
auto local_storage = document->window()->local_storage().release_value_but_fixme_should_propagate_errors();
return local_storage->map();
}
@ -1100,7 +1100,7 @@ Messages::WebContentServer::GetSessionStorageEntriesResponse ConnectionFromClien
auto& page = maybe_page.release_value();
auto* document = page.page().top_level_browsing_context().active_document();
auto session_storage = document->window().session_storage().release_value_but_fixme_should_propagate_errors();
auto session_storage = document->window()->session_storage().release_value_but_fixme_should_propagate_errors();
return session_storage->map();
}