LibWeb: Move event handling & cursor from BrowsingContext to Navigable

This was a long standing FIXME since the introduction of navigables.
This commit is contained in:
Andreas Kling 2024-04-26 16:59:04 +02:00
parent 9cd4a65071
commit 0ebfc0a4c4
Notes: sideshowbarker 2024-07-17 01:46:43 +09:00
26 changed files with 364 additions and 360 deletions

View file

@ -15,7 +15,7 @@ namespace Web::DOM {
class EditableTextNodeOwner {
public:
virtual ~EditableTextNodeOwner() = default;
virtual void did_edit_text_node(Badge<HTML::BrowsingContext>) = 0;
virtual void did_edit_text_node(Badge<HTML::Navigable>) = 0;
};
class Text

View file

@ -276,22 +276,7 @@ WebIDL::ExceptionOr<BrowsingContext::BrowsingContextAndDocument> BrowsingContext
BrowsingContext::BrowsingContext(JS::NonnullGCPtr<Page> page)
: m_page(page)
, m_event_handler({}, *this)
{
m_cursor_blink_timer = Core::Timer::create_repeating(500, [this] {
if (!is_focused_context())
return;
if (!m_cursor_position)
return;
auto node = m_cursor_position->node();
if (!node)
return;
node->document().update_layout();
if (node->paintable()) {
m_cursor_blink_state = !m_cursor_blink_state;
node->paintable()->set_needs_display();
}
});
}
BrowsingContext::~BrowsingContext() = default;
@ -301,7 +286,6 @@ void BrowsingContext::visit_edges(Cell::Visitor& visitor)
Base::visit_edges(visitor);
visitor.visit(m_page);
visitor.visit(m_cursor_position);
visitor.visit(m_window_proxy);
visitor.visit(m_group);
visitor.visit(m_parent);
@ -310,8 +294,6 @@ void BrowsingContext::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_next_sibling);
visitor.visit(m_previous_sibling);
visitor.visit(m_opener_browsing_context);
m_event_handler.visit_edges(visitor);
}
// https://html.spec.whatwg.org/multipage/document-sequences.html#bc-traversable
@ -324,25 +306,6 @@ JS::NonnullGCPtr<HTML::TraversableNavigable> BrowsingContext::top_level_traversa
return *traversable;
}
void BrowsingContext::did_edit(Badge<EditEventHandler>)
{
reset_cursor_blink_cycle();
if (m_cursor_position && is<DOM::Text>(*m_cursor_position->node())) {
auto& text_node = static_cast<DOM::Text&>(*m_cursor_position->node());
if (auto text_node_owner = text_node.editable_text_node_owner())
text_node_owner->did_edit_text_node({});
}
}
void BrowsingContext::reset_cursor_blink_cycle()
{
m_cursor_blink_state = true;
m_cursor_blink_timer->restart();
if (m_cursor_position && m_cursor_position->node()->paintable())
m_cursor_position->node()->paintable()->set_needs_display();
}
// https://html.spec.whatwg.org/multipage/browsers.html#top-level-browsing-context
bool BrowsingContext::is_top_level() const
{
@ -350,11 +313,6 @@ bool BrowsingContext::is_top_level() const
return !parent();
}
bool BrowsingContext::is_focused_context() const
{
return &m_page->focused_context() == this;
}
JS::GCPtr<BrowsingContext> BrowsingContext::top_level_browsing_context() const
{
auto const* start = this;
@ -376,127 +334,6 @@ JS::GCPtr<BrowsingContext> BrowsingContext::top_level_browsing_context() const
return navigable->active_browsing_context();
}
void BrowsingContext::set_cursor_position(JS::NonnullGCPtr<DOM::Position> position)
{
if (m_cursor_position && m_cursor_position->equals(position))
return;
if (m_cursor_position && m_cursor_position->node()->paintable())
m_cursor_position->node()->paintable()->set_needs_display();
m_cursor_position = position;
if (m_cursor_position && m_cursor_position->node()->paintable())
m_cursor_position->node()->paintable()->set_needs_display();
reset_cursor_blink_cycle();
}
static String visible_text_in_range(DOM::Range const& range)
{
// NOTE: This is an adaption of Range stringification, but we skip over DOM nodes that don't have a corresponding layout node.
StringBuilder builder;
if (range.start_container() == range.end_container() && is<DOM::Text>(*range.start_container())) {
if (!range.start_container()->layout_node())
return String {};
return MUST(static_cast<DOM::Text const&>(*range.start_container()).data().substring_from_byte_offset(range.start_offset(), range.end_offset() - range.start_offset()));
}
if (is<DOM::Text>(*range.start_container()) && range.start_container()->layout_node())
builder.append(static_cast<DOM::Text const&>(*range.start_container()).data().bytes_as_string_view().substring_view(range.start_offset()));
for (DOM::Node const* node = range.start_container(); node != range.end_container()->next_sibling(); node = node->next_in_pre_order()) {
if (is<DOM::Text>(*node) && range.contains_node(*node) && node->layout_node())
builder.append(static_cast<DOM::Text const&>(*node).data());
}
if (is<DOM::Text>(*range.end_container()) && range.end_container()->layout_node())
builder.append(static_cast<DOM::Text const&>(*range.end_container()).data().bytes_as_string_view().substring_view(0, range.end_offset()));
return MUST(builder.to_string());
}
String BrowsingContext::selected_text() const
{
auto const* document = active_document();
if (!document)
return String {};
auto selection = const_cast<DOM::Document&>(*document).get_selection();
auto range = selection->range();
if (!range)
return String {};
return visible_text_in_range(*range);
}
void BrowsingContext::select_all()
{
auto* document = active_document();
if (!document)
return;
auto* body = document->body();
if (!body)
return;
auto selection = document->get_selection();
if (!selection)
return;
(void)selection->select_all_children(*document->body());
}
void BrowsingContext::paste(String const& text)
{
auto* document = active_document();
if (!document)
return;
m_event_handler.handle_paste(text);
}
bool BrowsingContext::increment_cursor_position_offset()
{
if (!m_cursor_position->increment_offset())
return false;
reset_cursor_blink_cycle();
return true;
}
bool BrowsingContext::decrement_cursor_position_offset()
{
if (!m_cursor_position->decrement_offset())
return false;
reset_cursor_blink_cycle();
return true;
}
// https://html.spec.whatwg.org/multipage/interaction.html#currently-focused-area-of-a-top-level-browsing-context
JS::GCPtr<DOM::Node> BrowsingContext::currently_focused_area()
{
// 1. If topLevelBC does not have system focus, then return null.
if (!is_focused_context())
return nullptr;
// 2. Let candidate be topLevelBC's active document.
auto* candidate = active_document();
// 3. While candidate's focused area is a browsing context container with a non-null nested browsing context:
// set candidate to the active document of that browsing context container's nested browsing context.
while (candidate->focused_element()
&& is<HTML::NavigableContainer>(candidate->focused_element())
&& static_cast<HTML::NavigableContainer&>(*candidate->focused_element()).nested_browsing_context()) {
candidate = static_cast<HTML::NavigableContainer&>(*candidate->focused_element()).nested_browsing_context()->active_document();
}
// 4. If candidate's focused area is non-null, set candidate to candidate's focused area.
if (candidate->focused_element()) {
// NOTE: We return right away here instead of assigning to candidate,
// since that would require compromising type safety.
return candidate->focused_element();
}
// 5. Return candidate.
return candidate;
}
DOM::Document const* BrowsingContext::active_document() const
{
auto* window = active_window();

View file

@ -23,7 +23,6 @@
#include <LibWeb/HTML/SessionHistoryEntry.h>
#include <LibWeb/HTML/TokenizedFeatures.h>
#include <LibWeb/HTML/VisibilityState.h>
#include <LibWeb/Page/EventHandler.h>
#include <LibWeb/Platform/Timer.h>
#include <LibWeb/TreeNode.h>
@ -99,7 +98,6 @@ public:
}
bool is_top_level() const;
bool is_focused_context() const;
DOM::Document const* active_document() const;
DOM::Document* active_document();
@ -115,26 +113,8 @@ public:
Page& page() { return m_page; }
Page const& page() const { return m_page; }
Web::EventHandler& event_handler() { return m_event_handler; }
Web::EventHandler const& event_handler() const { return m_event_handler; }
JS::GCPtr<BrowsingContext> top_level_browsing_context() const;
JS::GCPtr<DOM::Position> cursor_position() const { return m_cursor_position; }
void set_cursor_position(JS::NonnullGCPtr<DOM::Position>);
bool increment_cursor_position_offset();
bool decrement_cursor_position_offset();
bool cursor_blink_state() const { return m_cursor_blink_state; }
String selected_text() const;
void select_all();
void paste(String const&);
void did_edit(Badge<EditEventHandler>);
JS::GCPtr<DOM::Node> currently_focused_area();
BrowsingContextGroup* group();
void set_group(BrowsingContextGroup*);
@ -156,13 +136,8 @@ private:
virtual void visit_edges(Cell::Visitor&) override;
void reset_cursor_blink_cycle();
JS::NonnullGCPtr<Page> m_page;
// FIXME: Move EventHandler to Navigable
Web::EventHandler m_event_handler;
// https://html.spec.whatwg.org/multipage/document-sequences.html#browsing-context
JS::GCPtr<HTML::WindowProxy> m_window_proxy;
@ -184,11 +159,6 @@ private:
// https://html.spec.whatwg.org/multipage/document-sequences.html#virtual-browsing-context-group-id
u64 m_virtual_browsing_context_group_id = { 0 };
// FIXME: Move cursor tracking to Navigable
JS::GCPtr<DOM::Position> m_cursor_position;
RefPtr<Core::Timer> m_cursor_blink_timer;
bool m_cursor_blink_state { false };
// https://html.spec.whatwg.org/multipage/browsers.html#tlbc-group
JS::GCPtr<BrowsingContextGroup> m_group;

View file

@ -209,13 +209,13 @@ void run_focusing_steps(DOM::Node* new_focus_target, DOM::Node* fallback_target,
// 5. If new focus target is the currently focused area of a top-level browsing context, then return.
if (!new_focus_target->document().browsing_context())
return;
auto top_level_browsing_context = new_focus_target->document().browsing_context()->top_level_browsing_context();
if (new_focus_target == top_level_browsing_context->currently_focused_area().ptr())
auto top_level_traversable = new_focus_target->document().browsing_context()->top_level_traversable();
if (new_focus_target == top_level_traversable->currently_focused_area().ptr())
return;
// 6. Let old chain be the current focus chain of the top-level browsing context in which
// new focus target finds itself.
auto old_chain = focus_chain(top_level_browsing_context->currently_focused_area());
auto old_chain = focus_chain(top_level_traversable->currently_focused_area());
// 7. Let new chain be the focus chain of new focus target.
auto new_chain = focus_chain(new_focus_target);
@ -243,8 +243,8 @@ void run_unfocusing_steps(DOM::Node* old_focus_target)
if (is_shadow_host(old_focus_target)) {
auto* shadow_root = static_cast<DOM::Element*>(old_focus_target)->shadow_root_internal();
if (shadow_root->delegates_focus()) {
auto top_level_browsing_context = old_focus_target->document().browsing_context()->top_level_browsing_context();
if (auto currently_focused_area = top_level_browsing_context->currently_focused_area()) {
auto top_level_traversable = old_focus_target->document().browsing_context()->top_level_traversable();
if (auto currently_focused_area = top_level_traversable->currently_focused_area()) {
if (shadow_root->is_shadow_including_ancestor_of(*currently_focused_area)) {
old_focus_target = currently_focused_area;
}
@ -261,10 +261,10 @@ void run_unfocusing_steps(DOM::Node* old_focus_target)
// NOTE: HTMLAreaElement is currently missing the shapes property
auto top_level_browsing_context = old_focus_target->document().browsing_context()->top_level_browsing_context();
auto top_level_traversable = old_focus_target->document().browsing_context()->top_level_traversable();
// 4. Let old chain be the current focus chain of the top-level browsing context in which old focus target finds itself.
auto old_chain = focus_chain(top_level_browsing_context->currently_focused_area());
auto old_chain = focus_chain(top_level_traversable->currently_focused_area());
// 5. If old focus target is not one of the entries in old chain, then return.
auto it = old_chain.find_if([&](auto const& node) { return old_focus_target == node; });

View file

@ -589,10 +589,10 @@ void HTMLElement::did_receive_focus()
{
if (m_content_editable_state != ContentEditableState::True)
return;
auto* browsing_context = document().browsing_context();
if (!browsing_context)
auto navigable = document().navigable();
if (!navigable)
return;
browsing_context->set_cursor_position(DOM::Position::create(realm(), *this, 0));
navigable->set_cursor_position(DOM::Position::create(realm(), *this, 0));
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel

View file

@ -379,7 +379,7 @@ WebIDL::ExceptionOr<void> HTMLInputElement::run_input_activation_behavior(DOM::E
return {};
}
void HTMLInputElement::did_edit_text_node(Badge<BrowsingContext>)
void HTMLInputElement::did_edit_text_node(Badge<Navigable>)
{
// An input element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the value.
m_value = value_sanitization_algorithm(m_text_node->data());
@ -543,8 +543,8 @@ WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
m_text_node->set_data(m_value);
update_placeholder_visibility();
if (auto* browsing_context = document().browsing_context())
browsing_context->set_cursor_position(DOM::Position::create(realm, *m_text_node, m_text_node->data().bytes().size()));
if (auto navigable = document().navigable())
navigable->set_cursor_position(DOM::Position::create(realm, *m_text_node, m_text_node->data().bytes().size()));
}
update_shadow_tree();
@ -1053,12 +1053,12 @@ void HTMLInputElement::update_slider_thumb_element()
void HTMLInputElement::did_receive_focus()
{
auto* browsing_context = document().browsing_context();
if (!browsing_context)
auto navigable = document().navigable();
if (!navigable)
return;
if (!m_text_node)
return;
browsing_context->set_cursor_position(DOM::Position::create(realm(), *m_text_node, 0));
navigable->set_cursor_position(DOM::Position::create(realm(), *m_text_node, 0));
}
void HTMLInputElement::did_lose_focus()

View file

@ -143,7 +143,7 @@ public:
WebIDL::ExceptionOr<void> show_picker();
// ^DOM::EditableTextNodeOwner
virtual void did_edit_text_node(Badge<BrowsingContext>) override;
virtual void did_edit_text_node(Badge<Navigable>) override;
// ^EventTarget
// https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute:the-input-element

View file

@ -66,12 +66,12 @@ void HTMLTextAreaElement::visit_edges(Cell::Visitor& visitor)
void HTMLTextAreaElement::did_receive_focus()
{
auto* browsing_context = document().browsing_context();
if (!browsing_context)
auto navigable = document().navigable();
if (!navigable)
return;
if (!m_text_node)
return;
browsing_context->set_cursor_position(DOM::Position::create(realm(), *m_text_node, 0));
navigable->set_cursor_position(DOM::Position::create(realm(), *m_text_node, 0));
}
void HTMLTextAreaElement::did_lose_focus()
@ -158,8 +158,8 @@ void HTMLTextAreaElement::set_value(String const& value)
m_text_node->set_data(m_raw_value);
update_placeholder_visibility();
if (auto* browsing_context = document().browsing_context())
browsing_context->set_cursor_position(DOM::Position::create(realm, *m_text_node, m_text_node->data().bytes().size()));
if (auto navigable = document().navigable())
navigable->set_cursor_position(DOM::Position::create(realm, *m_text_node, m_text_node->data().bytes().size()));
}
}
}
@ -215,8 +215,8 @@ WebIDL::UnsignedLong HTMLTextAreaElement::selection_start() const
// 2. If there is no selection, return the code unit offset within the relevant value to the character that
// immediately follows the text entry cursor.
if (auto const* browsing_context = document().browsing_context()) {
if (auto cursor = browsing_context->cursor_position())
if (auto navigable = document().navigable()) {
if (auto cursor = navigable->cursor_position())
return cursor->offset();
}
@ -244,8 +244,8 @@ WebIDL::UnsignedLong HTMLTextAreaElement::selection_end() const
// 2. If there is no selection, return the code unit offset within the relevant value to the character that
// immediately follows the text entry cursor.
if (auto const* browsing_context = document().browsing_context()) {
if (auto cursor = browsing_context->cursor_position())
if (auto navigable = document().navigable()) {
if (auto cursor = navigable->cursor_position())
return cursor->offset();
}
@ -430,7 +430,7 @@ void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString co
}
}
void HTMLTextAreaElement::did_edit_text_node(Badge<Web::HTML::BrowsingContext>)
void HTMLTextAreaElement::did_edit_text_node(Badge<Navigable>)
{
VERIFY(m_text_node);
set_raw_value(m_text_node->data());

View file

@ -37,7 +37,7 @@ public:
}
// ^DOM::EditableTextNodeOwner
virtual void did_edit_text_node(Badge<BrowsingContext>) override;
virtual void did_edit_text_node(Badge<Navigable>) override;
// ^EventTarget
// https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute:the-textarea-element

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022-2024, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -9,6 +9,7 @@
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentLoading.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/Range.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
@ -36,6 +37,7 @@
#include <LibWeb/Painting/Paintable.h>
#include <LibWeb/Painting/ViewportPaintable.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/Selection/Selection.h>
#include <LibWeb/XHR/FormData.h>
namespace Web::HTML {
@ -103,8 +105,24 @@ bool Navigable::is_ancestor_of(JS::NonnullGCPtr<Navigable> other) const
Navigable::Navigable(JS::NonnullGCPtr<Page> page)
: m_page(page)
, m_event_handler({}, *this)
{
all_navigables().set(this);
m_cursor_blink_timer = Core::Timer::create_repeating(500, [this] {
if (!is_focused())
return;
if (!m_cursor_position)
return;
auto node = m_cursor_position->node();
if (!node)
return;
node->document().update_layout();
if (node->paintable()) {
m_cursor_blink_state = !m_cursor_blink_state;
node->paintable()->set_needs_display();
}
});
}
Navigable::~Navigable()
@ -120,6 +138,8 @@ void Navigable::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_current_session_history_entry);
visitor.visit(m_active_session_history_entry);
visitor.visit(m_container);
visitor.visit(m_cursor_position);
m_event_handler.visit_edges(visitor);
}
void Navigable::set_delaying_load_events(bool value)
@ -2159,4 +2179,120 @@ UserNavigationInvolvement user_navigation_involvement(DOM::Event const& event)
return event.is_trusted() ? UserNavigationInvolvement::Activation : UserNavigationInvolvement::None;
}
void Navigable::did_edit(Badge<EditEventHandler>)
{
reset_cursor_blink_cycle();
if (m_cursor_position && is<DOM::Text>(*m_cursor_position->node())) {
auto& text_node = static_cast<DOM::Text&>(*m_cursor_position->node());
if (auto text_node_owner = text_node.editable_text_node_owner())
text_node_owner->did_edit_text_node({});
}
}
void Navigable::reset_cursor_blink_cycle()
{
m_cursor_blink_state = true;
m_cursor_blink_timer->restart();
if (m_cursor_position && m_cursor_position->node()->paintable())
m_cursor_position->node()->paintable()->set_needs_display();
}
bool Navigable::is_focused() const
{
return &m_page->focused_navigable() == this;
}
void Navigable::set_cursor_position(JS::NonnullGCPtr<DOM::Position> position)
{
if (m_cursor_position && m_cursor_position->equals(position))
return;
if (m_cursor_position && m_cursor_position->node()->paintable())
m_cursor_position->node()->paintable()->set_needs_display();
m_cursor_position = position;
if (m_cursor_position && m_cursor_position->node()->paintable())
m_cursor_position->node()->paintable()->set_needs_display();
reset_cursor_blink_cycle();
}
static String visible_text_in_range(DOM::Range const& range)
{
// NOTE: This is an adaption of Range stringification, but we skip over DOM nodes that don't have a corresponding layout node.
StringBuilder builder;
if (range.start_container() == range.end_container() && is<DOM::Text>(*range.start_container())) {
if (!range.start_container()->layout_node())
return String {};
return MUST(static_cast<DOM::Text const&>(*range.start_container()).data().substring_from_byte_offset(range.start_offset(), range.end_offset() - range.start_offset()));
}
if (is<DOM::Text>(*range.start_container()) && range.start_container()->layout_node())
builder.append(static_cast<DOM::Text const&>(*range.start_container()).data().bytes_as_string_view().substring_view(range.start_offset()));
for (DOM::Node const* node = range.start_container(); node != range.end_container()->next_sibling(); node = node->next_in_pre_order()) {
if (is<DOM::Text>(*node) && range.contains_node(*node) && node->layout_node())
builder.append(static_cast<DOM::Text const&>(*node).data());
}
if (is<DOM::Text>(*range.end_container()) && range.end_container()->layout_node())
builder.append(static_cast<DOM::Text const&>(*range.end_container()).data().bytes_as_string_view().substring_view(0, range.end_offset()));
return MUST(builder.to_string());
}
String Navigable::selected_text() const
{
auto document = const_cast<Navigable*>(this)->active_document();
if (!document)
return String {};
auto selection = const_cast<DOM::Document&>(*document).get_selection();
auto range = selection->range();
if (!range)
return String {};
return visible_text_in_range(*range);
}
void Navigable::select_all()
{
auto document = active_document();
if (!document)
return;
auto* body = document->body();
if (!body)
return;
auto selection = document->get_selection();
if (!selection)
return;
(void)selection->select_all_children(*document->body());
}
void Navigable::paste(String const& text)
{
auto document = active_document();
if (!document)
return;
m_event_handler.handle_paste(text);
}
bool Navigable::increment_cursor_position_offset()
{
if (!m_cursor_position->increment_offset())
return false;
reset_cursor_blink_cycle();
return true;
}
bool Navigable::decrement_cursor_position_offset()
{
if (!m_cursor_position->decrement_offset())
return false;
reset_cursor_blink_cycle();
return true;
}
}

View file

@ -21,6 +21,7 @@
#include <LibWeb/HTML/SourceSnapshotParams.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/HTML/TokenizedFeatures.h>
#include <LibWeb/Page/EventHandler.h>
#include <LibWeb/PixelUnits.h>
#include <LibWeb/XHR/FormDataEntry.h>
@ -44,7 +45,9 @@ struct TargetSnapshotParams {
};
// https://html.spec.whatwg.org/multipage/document-sequences.html#navigable
class Navigable : public JS::Cell {
class Navigable
: public JS::Cell
, public Weakable<Navigable> {
JS_CELL(Navigable, JS::Cell);
JS_DECLARE_ALLOCATOR(Navigable);
@ -93,6 +96,8 @@ public:
virtual bool is_top_level_traversable() const { return false; }
[[nodiscard]] bool is_focused() const;
enum class WindowType {
ExistingOrNone,
NewAndUnrestricted,
@ -186,6 +191,22 @@ public:
Page& page() { return m_page; }
Page const& page() const { return m_page; }
String selected_text() const;
void select_all();
void paste(String const&);
Web::EventHandler& event_handler() { return m_event_handler; }
Web::EventHandler const& event_handler() const { return m_event_handler; }
void did_edit(Badge<EditEventHandler>);
JS::GCPtr<DOM::Position> cursor_position() const { return m_cursor_position; }
void set_cursor_position(JS::NonnullGCPtr<DOM::Position>);
bool increment_cursor_position_offset();
bool decrement_cursor_position_offset();
bool cursor_blink_state() const { return m_cursor_blink_state; }
protected:
explicit Navigable(JS::NonnullGCPtr<Page>);
@ -198,6 +219,8 @@ protected:
TokenizedFeature::Popup m_is_popup { TokenizedFeature::Popup::No };
private:
void reset_cursor_blink_cycle();
void scroll_offset_did_change();
void inform_the_navigation_api_about_aborting_navigation();
@ -231,6 +254,12 @@ private:
CSSPixelPoint m_viewport_scroll_offset;
bool m_needs_repaint { false };
Web::EventHandler m_event_handler;
JS::GCPtr<DOM::Position> m_cursor_position;
RefPtr<Core::Timer> m_cursor_blink_timer;
bool m_cursor_blink_state { false };
};
HashTable<Navigable*>& all_navigables();

View file

@ -95,7 +95,7 @@ WebIDL::ExceptionOr<void> NavigableContainer::create_new_child_navigable(JS::Saf
document_state->set_about_base_url(document->about_base_url());
// 7. Let navigable be a new navigable.
JS::NonnullGCPtr<Navigable> navigable = *heap().allocate_without_realm<Navigable>();
JS::NonnullGCPtr<Navigable> navigable = *heap().allocate_without_realm<Navigable>(page);
// 8. Initialize the navigable navigable given documentState and parentNavigable.
TRY_OR_THROW_OOM(vm(), navigable->initialize_navigable(document_state, parent_navigable));

View file

@ -1138,4 +1138,33 @@ void TraversableNavigable::set_system_visibility_state(VisibilityState visibilit
}
}
// https://html.spec.whatwg.org/multipage/interaction.html#currently-focused-area-of-a-top-level-traversable
JS::GCPtr<DOM::Node> TraversableNavigable::currently_focused_area()
{
// 1. If traversable does not have system focus, then return null.
if (!is_focused())
return nullptr;
// 2. Let candidate be traversable's active document.
auto candidate = active_document();
// 3. While candidate's focused area is a navigable container with a non-null content navigable:
// set candidate to the active document of that navigable container's content navigable.
while (candidate->focused_element()
&& is<HTML::NavigableContainer>(candidate->focused_element())
&& static_cast<HTML::NavigableContainer&>(*candidate->focused_element()).content_navigable()) {
candidate = static_cast<HTML::NavigableContainer&>(*candidate->focused_element()).content_navigable()->active_document();
}
// 4. If candidate's focused area is non-null, set candidate to candidate's focused area.
if (candidate->focused_element()) {
// NOTE: We return right away here instead of assigning to candidate,
// since that would require compromising type safety.
return candidate->focused_element();
}
// 5. Return candidate.
return candidate;
}
}

View file

@ -82,6 +82,8 @@ public:
String window_handle() const { return m_window_handle; }
void set_window_handle(String window_handle) { m_window_handle = move(window_handle); }
[[nodiscard]] JS::GCPtr<DOM::Node> currently_focused_area();
private:
TraversableNavigable(JS::NonnullGCPtr<Page>);

View file

@ -33,7 +33,7 @@ void EditEventHandler::handle_delete_character_after(JS::NonnullGCPtr<DOM::Posit
builder.append(text.bytes_as_string_view().substring_view(*next_grapheme_offset));
node.set_data(MUST(builder.to_string()));
m_browsing_context->did_edit({});
m_navigable->did_edit({});
}
// This method is quite convoluted but this is necessary to make editing feel intuitive.
@ -89,7 +89,7 @@ void EditEventHandler::handle_delete(DOM::Range& range)
end->remove();
}
m_browsing_context->did_edit({});
m_navigable->did_edit({});
}
void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, u32 code_point)
@ -126,6 +126,6 @@ void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, S
position->set_offset(1);
}
m_browsing_context->did_edit({});
m_navigable->did_edit({});
}
}

View file

@ -7,14 +7,15 @@
#pragma once
#include <AK/Types.h>
#include <LibJS/Forward.h>
#include <LibWeb/Forward.h>
namespace Web {
class EditEventHandler {
public:
explicit EditEventHandler(HTML::BrowsingContext& browsing_context)
: m_browsing_context(browsing_context)
explicit EditEventHandler(HTML::Navigable& navigable)
: m_navigable(navigable)
{
}
@ -26,7 +27,7 @@ public:
virtual void handle_insert(JS::NonnullGCPtr<DOM::Position>, String);
private:
JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;
JS::NonnullGCPtr<HTML::Navigable> m_navigable;
};
}

View file

@ -129,9 +129,9 @@ static CSSPixelPoint compute_mouse_event_offset(CSSPixelPoint position, Layout::
};
}
EventHandler::EventHandler(Badge<HTML::BrowsingContext>, HTML::BrowsingContext& browsing_context)
: m_browsing_context(browsing_context)
, m_edit_event_handler(make<EditEventHandler>(browsing_context))
EventHandler::EventHandler(Badge<HTML::Navigable>, HTML::Navigable& navigable)
: m_navigable(navigable)
, m_edit_event_handler(make<EditEventHandler>(navigable))
{
}
@ -139,26 +139,26 @@ EventHandler::~EventHandler() = default;
Painting::PaintableBox* EventHandler::paint_root()
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return nullptr;
return m_browsing_context->active_document()->paintable_box();
return m_navigable->active_document()->paintable_box();
}
Painting::PaintableBox const* EventHandler::paint_root() const
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return nullptr;
return m_browsing_context->active_document()->paintable_box();
return m_navigable->active_document()->paintable_box();
}
bool EventHandler::handle_mousewheel(CSSPixelPoint position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers, int wheel_delta_x, int wheel_delta_y)
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return false;
if (!m_browsing_context->active_document()->is_fully_active())
if (!m_navigable->active_document()->is_fully_active())
return false;
m_browsing_context->active_document()->update_layout();
m_navigable->active_document()->update_layout();
if (!paint_root())
return false;
@ -191,7 +191,7 @@ bool EventHandler::handle_mousewheel(CSSPixelPoint position, CSSPixelPoint scree
if (is<HTML::HTMLIFrameElement>(*node)) {
auto& iframe = static_cast<HTML::HTMLIFrameElement&>(*node);
auto position_in_iframe = position.translated(compute_mouse_event_offset({}, paintable->layout_node()));
iframe.nested_browsing_context()->event_handler().handle_mousewheel(position_in_iframe, screen_position, button, buttons, modifiers, wheel_delta_x, wheel_delta_y);
iframe.content_navigable()->event_handler().handle_mousewheel(position_in_iframe, screen_position, button, buttons, modifiers, wheel_delta_x, wheel_delta_y);
return false;
}
@ -204,7 +204,7 @@ bool EventHandler::handle_mousewheel(CSSPixelPoint position, CSSPixelPoint scree
auto client_offset = compute_mouse_event_client_offset(position);
auto page_offset = compute_mouse_event_page_offset(client_offset);
if (node->dispatch_event(UIEvents::WheelEvent::create_from_platform_event(node->realm(), UIEvents::EventNames::wheel, screen_position, page_offset, client_offset, offset, wheel_delta_x, wheel_delta_y, button, buttons, modifiers).release_value_but_fixme_should_propagate_errors())) {
m_browsing_context->active_window()->scroll_by(wheel_delta_x, wheel_delta_y);
m_navigable->active_window()->scroll_by(wheel_delta_x, wheel_delta_y);
}
handled_event = true;
@ -216,12 +216,12 @@ bool EventHandler::handle_mousewheel(CSSPixelPoint position, CSSPixelPoint scree
bool EventHandler::handle_mouseup(CSSPixelPoint position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return false;
if (!m_browsing_context->active_document()->is_fully_active())
if (!m_navigable->active_document()->is_fully_active())
return false;
m_browsing_context->active_document()->update_layout();
m_navigable->active_document()->update_layout();
if (!paint_root())
return false;
@ -248,8 +248,8 @@ bool EventHandler::handle_mouseup(CSSPixelPoint position, CSSPixelPoint screen_p
if (node) {
if (is<HTML::HTMLIFrameElement>(*node)) {
if (auto* nested_browsing_context = static_cast<HTML::HTMLIFrameElement&>(*node).nested_browsing_context())
return nested_browsing_context->event_handler().handle_mouseup(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), screen_position, button, buttons, modifiers);
if (auto content_navigable = static_cast<HTML::HTMLIFrameElement&>(*node).content_navigable())
return content_navigable->event_handler().handle_mouseup(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), screen_position, button, buttons, modifiers);
return false;
}
@ -292,23 +292,23 @@ bool EventHandler::handle_mouseup(CSSPixelPoint position, CSSPixelPoint screen_p
//
// https://html.spec.whatwg.org/multipage/document-sequences.html#the-rules-for-choosing-a-navigable
auto top_level_position = m_browsing_context->active_document()->navigable()->to_top_level_position(position);
auto top_level_position = m_navigable->active_document()->navigable()->to_top_level_position(position);
if (JS::GCPtr<HTML::HTMLAnchorElement const> link = node->enclosing_link_element()) {
JS::NonnullGCPtr<DOM::Document> document = *m_browsing_context->active_document();
JS::NonnullGCPtr<DOM::Document> document = *m_navigable->active_document();
auto href = link->href();
auto url = document->parse_url(href);
dbgln("Web::EventHandler: Clicking on a link to {}", url);
if (button == GUI::MouseButton::Middle) {
m_browsing_context->page().client().page_did_middle_click_link(url, link->target().to_byte_string(), modifiers);
m_navigable->page().client().page_did_middle_click_link(url, link->target().to_byte_string(), modifiers);
} else if (button == GUI::MouseButton::Secondary) {
m_browsing_context->page().client().page_did_request_link_context_menu(top_level_position, url, link->target().to_byte_string(), modifiers);
m_navigable->page().client().page_did_request_link_context_menu(top_level_position, url, link->target().to_byte_string(), modifiers);
}
} else if (button == GUI::MouseButton::Secondary) {
if (is<HTML::HTMLImageElement>(*node)) {
auto& image_element = verify_cast<HTML::HTMLImageElement>(*node);
auto image_url = image_element.document().parse_url(image_element.src());
m_browsing_context->page().client().page_did_request_image_context_menu(top_level_position, image_url, "", modifiers, image_element.bitmap());
m_navigable->page().client().page_did_request_image_context_menu(top_level_position, image_url, "", modifiers, image_element.bitmap());
} else if (is<HTML::HTMLMediaElement>(*node)) {
auto& media_element = verify_cast<HTML::HTMLMediaElement>(*node);
@ -321,9 +321,9 @@ bool EventHandler::handle_mouseup(CSSPixelPoint position, CSSPixelPoint screen_p
.is_looping = media_element.has_attribute(HTML::AttributeNames::loop),
};
m_browsing_context->page().did_request_media_context_menu(media_element.unique_id(), top_level_position, "", modifiers, move(menu));
m_navigable->page().did_request_media_context_menu(media_element.unique_id(), top_level_position, "", modifiers, move(menu));
} else {
m_browsing_context->page().client().page_did_request_context_menu(top_level_position);
m_navigable->page().client().page_did_request_context_menu(top_level_position);
}
}
}
@ -338,17 +338,17 @@ after_node_use:
bool EventHandler::handle_mousedown(CSSPixelPoint position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return false;
if (!m_browsing_context->active_document()->is_fully_active())
if (!m_navigable->active_document()->is_fully_active())
return false;
m_browsing_context->active_document()->update_layout();
m_navigable->active_document()->update_layout();
if (!paint_root())
return false;
JS::NonnullGCPtr<DOM::Document> document = *m_browsing_context->active_document();
JS::NonnullGCPtr<DOM::Document> document = *m_navigable->active_document();
JS::GCPtr<DOM::Node> node;
{
@ -374,12 +374,12 @@ bool EventHandler::handle_mousedown(CSSPixelPoint position, CSSPixelPoint screen
return false;
if (is<HTML::HTMLIFrameElement>(*node)) {
if (auto* nested_browsing_context = static_cast<HTML::HTMLIFrameElement&>(*node).nested_browsing_context())
return nested_browsing_context->event_handler().handle_mousedown(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), screen_position, button, buttons, modifiers);
if (auto content_navigable = static_cast<HTML::HTMLIFrameElement&>(*node).content_navigable())
return content_navigable->event_handler().handle_mousedown(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), screen_position, button, buttons, modifiers);
return false;
}
m_browsing_context->page().set_focused_browsing_context({}, m_browsing_context);
m_navigable->page().set_focused_navigable({}, m_navigable);
// Search for the first parent of the hit target that's an element.
// "The click event type MUST be dispatched on the topmost event target indicated by the pointer." (https://www.w3.org/TR/uievents/#event-type-click)
@ -424,7 +424,7 @@ bool EventHandler::handle_mousedown(CSSPixelPoint position, CSSPixelPoint screen
// FIXME: This is all rather strange. Find a better solution.
if (!did_focus_something || paintable->dom_node()->is_editable()) {
auto& realm = document->realm();
m_browsing_context->set_cursor_position(DOM::Position::create(realm, *paintable->dom_node(), result->index_in_node));
m_navigable->set_cursor_position(DOM::Position::create(realm, *paintable->dom_node(), result->index_in_node));
if (auto selection = document->get_selection()) {
(void)selection->set_base_and_extent(*paintable->dom_node(), result->index_in_node, *paintable->dom_node(), result->index_in_node);
}
@ -438,17 +438,17 @@ bool EventHandler::handle_mousedown(CSSPixelPoint position, CSSPixelPoint screen
bool EventHandler::handle_mousemove(CSSPixelPoint position, CSSPixelPoint screen_position, u32 buttons, u32 modifiers)
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return false;
if (!m_browsing_context->active_document()->is_fully_active())
if (!m_navigable->active_document()->is_fully_active())
return false;
m_browsing_context->active_document()->update_layout();
m_navigable->active_document()->update_layout();
if (!paint_root())
return false;
auto& document = *m_browsing_context->active_document();
auto& document = *m_navigable->active_document();
auto& realm = document.realm();
bool hovered_node_changed = false;
@ -471,14 +471,14 @@ bool EventHandler::handle_mousemove(CSSPixelPoint position, CSSPixelPoint screen
return false;
// FIXME: It feels a bit aggressive to always update the cursor like this.
m_browsing_context->page().client().page_did_request_cursor_change(Gfx::StandardCursor::None);
m_navigable->page().client().page_did_request_cursor_change(Gfx::StandardCursor::None);
}
auto node = dom_node_for_event_dispatch(*paintable);
if (node && is<HTML::HTMLIFrameElement>(*node)) {
if (auto* nested_browsing_context = static_cast<HTML::HTMLIFrameElement&>(*node).nested_browsing_context())
return nested_browsing_context->event_handler().handle_mousemove(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), screen_position, buttons, modifiers);
if (auto content_navigable = static_cast<HTML::HTMLIFrameElement&>(*node).content_navigable())
return content_navigable->event_handler().handle_mousemove(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), screen_position, buttons, modifiers);
return false;
}
@ -529,7 +529,7 @@ bool EventHandler::handle_mousemove(CSSPixelPoint position, CSSPixelPoint screen
if (m_in_mouse_selection) {
auto hit = paint_root()->hit_test(position, Painting::HitTestType::TextCursor);
if (start_index.has_value() && hit.has_value() && hit->dom_node()) {
m_browsing_context->set_cursor_position(DOM::Position::create(realm, *hit->dom_node(), *start_index));
m_navigable->set_cursor_position(DOM::Position::create(realm, *hit->dom_node(), *start_index));
if (auto selection = document.get_selection()) {
auto anchor_node = selection->anchor_node();
if (anchor_node)
@ -542,14 +542,14 @@ bool EventHandler::handle_mousemove(CSSPixelPoint position, CSSPixelPoint screen
}
}
auto& page = m_browsing_context->page();
auto& page = m_navigable->page();
page.client().page_did_request_cursor_change(hovered_node_cursor);
if (hovered_node_changed) {
JS::GCPtr<HTML::HTMLElement const> hovered_html_element = document.hovered_node() ? document.hovered_node()->enclosing_html_element_with_attribute(HTML::AttributeNames::title) : nullptr;
if (hovered_html_element && hovered_html_element->title().has_value()) {
page.client().page_did_enter_tooltip_area(m_browsing_context->active_document()->navigable()->to_top_level_position(position), hovered_html_element->title()->to_byte_string());
page.client().page_did_enter_tooltip_area(m_navigable->active_document()->navigable()->to_top_level_position(position), hovered_html_element->title()->to_byte_string());
} else {
page.client().page_did_leave_tooltip_area();
}
@ -564,12 +564,12 @@ bool EventHandler::handle_mousemove(CSSPixelPoint position, CSSPixelPoint screen
bool EventHandler::handle_doubleclick(CSSPixelPoint position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return false;
if (!m_browsing_context->active_document()->is_fully_active())
if (!m_navigable->active_document()->is_fully_active())
return false;
m_browsing_context->active_document()->update_layout();
m_navigable->active_document()->update_layout();
if (!paint_root())
return false;
@ -595,8 +595,8 @@ bool EventHandler::handle_doubleclick(CSSPixelPoint position, CSSPixelPoint scre
return false;
if (is<HTML::HTMLIFrameElement>(*node)) {
if (auto* nested_browsing_context = static_cast<HTML::HTMLIFrameElement&>(*node).nested_browsing_context())
return nested_browsing_context->event_handler().handle_doubleclick(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), screen_position, button, buttons, modifiers);
if (auto content_navigable = static_cast<HTML::HTMLIFrameElement&>(*node).content_navigable())
return content_navigable->event_handler().handle_doubleclick(position.translated(compute_mouse_event_offset({}, paintable->layout_node())), screen_position, button, buttons, modifiers);
return false;
}
@ -648,7 +648,7 @@ bool EventHandler::handle_doubleclick(CSSPixelPoint position, CSSPixelPoint scre
}();
auto& realm = node->document().realm();
m_browsing_context->set_cursor_position(DOM::Position::create(realm, hit_dom_node, first_word_break_after));
m_navigable->set_cursor_position(DOM::Position::create(realm, hit_dom_node, first_word_break_after));
if (auto selection = node->document().get_selection()) {
(void)selection->set_base_and_extent(hit_dom_node, first_word_break_before, hit_dom_node, first_word_break_after);
}
@ -660,16 +660,16 @@ bool EventHandler::handle_doubleclick(CSSPixelPoint position, CSSPixelPoint scre
bool EventHandler::focus_next_element()
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return false;
if (!m_browsing_context->active_document()->is_fully_active())
if (!m_navigable->active_document()->is_fully_active())
return false;
auto* element = m_browsing_context->active_document()->focused_element();
auto* element = m_navigable->active_document()->focused_element();
if (!element) {
element = m_browsing_context->active_document()->first_child_of_type<DOM::Element>();
element = m_navigable->active_document()->first_child_of_type<DOM::Element>();
if (element && element->is_focusable()) {
m_browsing_context->active_document()->set_focused_element(element);
m_navigable->active_document()->set_focused_element(element);
return true;
}
}
@ -677,22 +677,22 @@ bool EventHandler::focus_next_element()
for (element = element->next_element_in_pre_order(); element && !element->is_focusable(); element = element->next_element_in_pre_order())
;
m_browsing_context->active_document()->set_focused_element(element);
m_navigable->active_document()->set_focused_element(element);
return element;
}
bool EventHandler::focus_previous_element()
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return false;
if (!m_browsing_context->active_document()->is_fully_active())
if (!m_navigable->active_document()->is_fully_active())
return false;
auto* element = m_browsing_context->active_document()->focused_element();
auto* element = m_navigable->active_document()->focused_element();
if (!element) {
element = m_browsing_context->active_document()->last_child_of_type<DOM::Element>();
element = m_navigable->active_document()->last_child_of_type<DOM::Element>();
if (element && element->is_focusable()) {
m_browsing_context->active_document()->set_focused_element(element);
m_navigable->active_document()->set_focused_element(element);
return true;
}
}
@ -700,7 +700,7 @@ bool EventHandler::focus_previous_element()
for (element = element->previous_element_in_pre_order(); element && !element->is_focusable(); element = element->previous_element_in_pre_order())
;
m_browsing_context->active_document()->set_focused_element(element);
m_navigable->active_document()->set_focused_element(element);
return element;
}
@ -713,9 +713,9 @@ constexpr bool should_ignore_keydown_event(u32 code_point, u32 modifiers)
return code_point == 0 || code_point == 27;
}
bool EventHandler::fire_keyboard_event(FlyString const& event_name, HTML::BrowsingContext& browsing_context, KeyCode key, u32 modifiers, u32 code_point)
bool EventHandler::fire_keyboard_event(FlyString const& event_name, HTML::Navigable& navigable, KeyCode key, u32 modifiers, u32 code_point)
{
JS::GCPtr<DOM::Document> document = browsing_context.active_document();
JS::GCPtr<DOM::Document> document = navigable.active_document();
if (!document)
return false;
if (!document->is_fully_active())
@ -724,8 +724,8 @@ bool EventHandler::fire_keyboard_event(FlyString const& event_name, HTML::Browsi
if (JS::GCPtr<DOM::Element> focused_element = document->focused_element()) {
if (is<HTML::NavigableContainer>(*focused_element)) {
auto& navigable_container = verify_cast<HTML::NavigableContainer>(*focused_element);
if (navigable_container.nested_browsing_context())
return fire_keyboard_event(event_name, *navigable_container.nested_browsing_context(), key, modifiers, code_point);
if (navigable_container.content_navigable())
return fire_keyboard_event(event_name, *navigable_container.content_navigable(), key, modifiers, code_point);
}
auto event = UIEvents::KeyboardEvent::create_from_platform_event(document->realm(), event_name, key, modifiers, code_point);
@ -743,12 +743,12 @@ bool EventHandler::fire_keyboard_event(FlyString const& event_name, HTML::Browsi
bool EventHandler::handle_keydown(KeyCode key, u32 modifiers, u32 code_point)
{
if (!m_browsing_context->active_document())
if (!m_navigable->active_document())
return false;
if (!m_browsing_context->active_document()->is_fully_active())
if (!m_navigable->active_document()->is_fully_active())
return false;
JS::NonnullGCPtr<DOM::Document> document = *m_browsing_context->active_document();
JS::NonnullGCPtr<DOM::Document> document = *m_navigable->active_document();
if (!document->layout_node())
return false;
@ -766,7 +766,7 @@ bool EventHandler::handle_keydown(KeyCode key, u32 modifiers, u32 code_point)
selection->remove_all_ranges();
// FIXME: This doesn't work for some reason?
m_browsing_context->set_cursor_position(DOM::Position::create(realm, *range->start_container(), range->start_offset()));
m_navigable->set_cursor_position(DOM::Position::create(realm, *range->start_container(), range->start_offset()));
if (key == KeyCode::Key_Backspace || key == KeyCode::Key_Delete) {
m_edit_event_handler->handle_delete(*range);
@ -775,69 +775,69 @@ bool EventHandler::handle_keydown(KeyCode key, u32 modifiers, u32 code_point)
// FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here.
if (!should_ignore_keydown_event(code_point, modifiers)) {
m_edit_event_handler->handle_delete(*range);
m_edit_event_handler->handle_insert(JS::NonnullGCPtr { *m_browsing_context->cursor_position() }, code_point);
m_browsing_context->increment_cursor_position_offset();
m_edit_event_handler->handle_insert(JS::NonnullGCPtr { *m_navigable->cursor_position() }, code_point);
m_navigable->increment_cursor_position_offset();
return true;
}
}
}
if (auto* element = m_browsing_context->active_document()->focused_element(); is<HTML::HTMLMediaElement>(element)) {
if (auto* element = m_navigable->active_document()->focused_element(); is<HTML::HTMLMediaElement>(element)) {
auto& media_element = static_cast<HTML::HTMLMediaElement&>(*element);
media_element.handle_keydown({}, key).release_value_but_fixme_should_propagate_errors();
}
bool continue_ = fire_keyboard_event(UIEvents::EventNames::keydown, m_browsing_context, key, modifiers, code_point);
bool continue_ = fire_keyboard_event(UIEvents::EventNames::keydown, m_navigable, key, modifiers, code_point);
if (!continue_)
return false;
if (m_browsing_context->cursor_position() && m_browsing_context->cursor_position()->node()->is_editable()) {
if (m_navigable->cursor_position() && m_navigable->cursor_position()->node()->is_editable()) {
if (key == KeyCode::Key_Backspace) {
if (!m_browsing_context->decrement_cursor_position_offset()) {
if (!m_navigable->decrement_cursor_position_offset()) {
// FIXME: Move to the previous node and delete the last character there.
return true;
}
m_edit_event_handler->handle_delete_character_after(*m_browsing_context->cursor_position());
m_edit_event_handler->handle_delete_character_after(*m_navigable->cursor_position());
return true;
}
if (key == KeyCode::Key_Delete) {
if (m_browsing_context->cursor_position()->offset_is_at_end_of_node()) {
if (m_navigable->cursor_position()->offset_is_at_end_of_node()) {
// FIXME: Move to the next node and delete the first character there.
return true;
}
m_edit_event_handler->handle_delete_character_after(*m_browsing_context->cursor_position());
m_edit_event_handler->handle_delete_character_after(*m_navigable->cursor_position());
return true;
}
if (key == KeyCode::Key_Right) {
if (!m_browsing_context->increment_cursor_position_offset()) {
if (!m_navigable->increment_cursor_position_offset()) {
// FIXME: Move to the next node.
}
return true;
}
if (key == KeyCode::Key_Left) {
if (!m_browsing_context->decrement_cursor_position_offset()) {
if (!m_navigable->decrement_cursor_position_offset()) {
// FIXME: Move to the previous node.
}
return true;
}
if (key == KeyCode::Key_Home) {
auto& cursor_position_node = *m_browsing_context->cursor_position()->node();
auto& cursor_position_node = *m_navigable->cursor_position()->node();
if (cursor_position_node.is_text())
m_browsing_context->set_cursor_position(DOM::Position::create(realm, cursor_position_node, 0));
m_navigable->set_cursor_position(DOM::Position::create(realm, cursor_position_node, 0));
return true;
}
if (key == KeyCode::Key_End) {
auto& cursor_position_node = *m_browsing_context->cursor_position()->node();
auto& cursor_position_node = *m_navigable->cursor_position()->node();
if (cursor_position_node.is_text()) {
auto& text_node = static_cast<DOM::Text&>(cursor_position_node);
m_browsing_context->set_cursor_position(DOM::Position::create(realm, text_node, (unsigned)text_node.data().bytes().size()));
m_navigable->set_cursor_position(DOM::Position::create(realm, text_node, (unsigned)text_node.data().bytes().size()));
}
return true;
}
if (key == KeyCode::Key_Return) {
HTML::HTMLInputElement* input_element = nullptr;
if (auto node = m_browsing_context->cursor_position()->node()) {
if (auto node = m_navigable->cursor_position()->node()) {
if (node->is_text()) {
auto& text_node = static_cast<DOM::Text&>(*node);
if (is<HTML::HTMLInputElement>(text_node.editable_text_node_owner()))
@ -858,30 +858,30 @@ bool EventHandler::handle_keydown(KeyCode key, u32 modifiers, u32 code_point)
}
// FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here.
if (!should_ignore_keydown_event(code_point, modifiers)) {
m_edit_event_handler->handle_insert(JS::NonnullGCPtr { *m_browsing_context->cursor_position() }, code_point);
m_browsing_context->increment_cursor_position_offset();
m_edit_event_handler->handle_insert(JS::NonnullGCPtr { *m_navigable->cursor_position() }, code_point);
m_navigable->increment_cursor_position_offset();
return true;
}
}
// FIXME: Work out and implement the difference between this and keydown.
return !fire_keyboard_event(UIEvents::EventNames::keypress, m_browsing_context, key, modifiers, code_point);
return !fire_keyboard_event(UIEvents::EventNames::keypress, m_navigable, key, modifiers, code_point);
}
bool EventHandler::handle_keyup(KeyCode key, u32 modifiers, u32 code_point)
{
return !fire_keyboard_event(UIEvents::EventNames::keyup, m_browsing_context, key, modifiers, code_point);
return !fire_keyboard_event(UIEvents::EventNames::keyup, m_navigable, key, modifiers, code_point);
}
void EventHandler::handle_paste(String const& text)
{
auto* active_document = m_browsing_context->active_document();
auto active_document = m_navigable->active_document();
if (!active_document)
return;
if (!active_document->is_fully_active())
return;
if (auto cursor_position = m_browsing_context->cursor_position()) {
if (auto cursor_position = m_navigable->cursor_position()) {
active_document->update_layout();
m_edit_event_handler->handle_insert(*cursor_position, text);
cursor_position->set_offset(cursor_position->offset() + text.code_points().length());
@ -898,7 +898,7 @@ CSSPixelPoint EventHandler::compute_mouse_event_client_offset(CSSPixelPoint even
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-mouseevent-clientx
// The clientX attribute must return the x-coordinate of the position where the event occurred relative to the origin of the viewport.
auto scroll_offset = m_browsing_context->active_document()->navigable()->viewport_scroll_offset();
auto scroll_offset = m_navigable->active_document()->navigable()->viewport_scroll_offset();
return event_page_position.translated(-scroll_offset);
}
@ -908,7 +908,7 @@ CSSPixelPoint EventHandler::compute_mouse_event_page_offset(CSSPixelPoint event_
// FIXME: 1. If the events dispatch flag is set, return the horizontal coordinate of the position where the event occurred relative to the origin of the initial containing block and terminate these steps.
// 2. Let offset be the value of the scrollX attribute of the events associated Window object, if there is one, or zero otherwise.
auto scroll_offset = m_browsing_context->active_document()->navigable()->viewport_scroll_offset();
auto scroll_offset = m_navigable->active_document()->navigable()->viewport_scroll_offset();
// 3. Return the sum of offset and the value of the events clientX attribute.
return event_client_offset.translated(scroll_offset);

View file

@ -22,7 +22,7 @@ namespace Web {
class EventHandler {
public:
explicit EventHandler(Badge<HTML::BrowsingContext>, HTML::BrowsingContext&);
explicit EventHandler(Badge<HTML::Navigable>, HTML::Navigable&);
~EventHandler();
bool handle_mouseup(CSSPixelPoint, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers);
@ -46,7 +46,7 @@ private:
bool focus_next_element();
bool focus_previous_element();
bool fire_keyboard_event(FlyString const& event_name, HTML::BrowsingContext& browsing_context, KeyCode key, unsigned modifiers, u32 code_point);
bool fire_keyboard_event(FlyString const& event_name, HTML::Navigable&, KeyCode, unsigned modifiers, u32 code_point);
CSSPixelPoint compute_mouse_event_client_offset(CSSPixelPoint event_page_position) const;
CSSPixelPoint compute_mouse_event_page_offset(CSSPixelPoint event_client_offset) const;
CSSPixelPoint compute_mouse_event_movement(CSSPixelPoint event_client_offset) const;
@ -60,7 +60,7 @@ private:
Painting::PaintableBox* paint_root();
Painting::PaintableBox const* paint_root() const;
JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;
JS::NonnullGCPtr<HTML::Navigable> m_navigable;
bool m_in_mouse_selection { false };

View file

@ -46,16 +46,16 @@ void Page::visit_edges(JS::Cell::Visitor& visitor)
visitor.visit(m_client);
}
HTML::BrowsingContext& Page::focused_context()
HTML::Navigable& Page::focused_navigable()
{
if (m_focused_context)
return *m_focused_context;
return top_level_browsing_context();
if (m_focused_navigable)
return *m_focused_navigable;
return top_level_traversable();
}
void Page::set_focused_browsing_context(Badge<EventHandler>, HTML::BrowsingContext& browsing_context)
void Page::set_focused_navigable(Badge<EventHandler>, HTML::Navigable& navigable)
{
m_focused_context = browsing_context.make_weak_ptr();
m_focused_navigable = navigable.make_weak_ptr();
}
void Page::load(URL::URL const& url)
@ -165,37 +165,37 @@ DevicePixelRect Page::rounded_device_rect(CSSPixelRect rect) const
bool Page::handle_mouseup(DevicePixelPoint position, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers)
{
return top_level_browsing_context().event_handler().handle_mouseup(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers);
return top_level_traversable()->event_handler().handle_mouseup(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers);
}
bool Page::handle_mousedown(DevicePixelPoint position, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers)
{
return top_level_browsing_context().event_handler().handle_mousedown(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers);
return top_level_traversable()->event_handler().handle_mousedown(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers);
}
bool Page::handle_mousemove(DevicePixelPoint position, DevicePixelPoint screen_position, unsigned buttons, unsigned modifiers)
{
return top_level_browsing_context().event_handler().handle_mousemove(device_to_css_point(position), device_to_css_point(screen_position), buttons, modifiers);
return top_level_traversable()->event_handler().handle_mousemove(device_to_css_point(position), device_to_css_point(screen_position), buttons, modifiers);
}
bool Page::handle_mousewheel(DevicePixelPoint position, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, DevicePixels wheel_delta_x, DevicePixels wheel_delta_y)
{
return top_level_browsing_context().event_handler().handle_mousewheel(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers, wheel_delta_x.value(), wheel_delta_y.value());
return top_level_traversable()->event_handler().handle_mousewheel(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers, wheel_delta_x.value(), wheel_delta_y.value());
}
bool Page::handle_doubleclick(DevicePixelPoint position, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers)
{
return top_level_browsing_context().event_handler().handle_doubleclick(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers);
return top_level_traversable()->event_handler().handle_doubleclick(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers);
}
bool Page::handle_keydown(KeyCode key, unsigned modifiers, u32 code_point)
{
return focused_context().event_handler().handle_keydown(key, modifiers, code_point);
return focused_navigable().event_handler().handle_keydown(key, modifiers, code_point);
}
bool Page::handle_keyup(KeyCode key, unsigned modifiers, u32 code_point)
{
return focused_context().event_handler().handle_keyup(key, modifiers, code_point);
return focused_navigable().event_handler().handle_keyup(key, modifiers, code_point);
}
void Page::set_top_level_traversable(JS::NonnullGCPtr<HTML::TraversableNavigable> navigable)

View file

@ -65,10 +65,10 @@ public:
JS::NonnullGCPtr<HTML::TraversableNavigable> top_level_traversable() const;
HTML::BrowsingContext& focused_context();
HTML::BrowsingContext const& focused_context() const { return const_cast<Page*>(this)->focused_context(); }
HTML::Navigable& focused_navigable();
HTML::Navigable const& focused_navigable() const { return const_cast<Page*>(this)->focused_navigable(); }
void set_focused_browsing_context(Badge<EventHandler>, HTML::BrowsingContext&);
void set_focused_navigable(Badge<EventHandler>, HTML::Navigable&);
void load(URL::URL const&);
@ -186,7 +186,7 @@ private:
JS::NonnullGCPtr<PageClient> m_client;
WeakPtr<HTML::BrowsingContext> m_focused_context;
WeakPtr<HTML::Navigable> m_focused_navigable;
JS::GCPtr<HTML::TraversableNavigable> m_top_level_traversable;

View file

@ -42,7 +42,7 @@ LabelablePaintable::DispatchEventOfSameName LabelablePaintable::handle_mousedown
set_being_pressed(true);
m_tracking_mouse = true;
browsing_context().event_handler().set_mouse_event_tracking_paintable(this);
navigable()->event_handler().set_mouse_event_tracking_paintable(this);
return DispatchEventOfSameName::Yes;
}
@ -57,7 +57,7 @@ LabelablePaintable::DispatchEventOfSameName LabelablePaintable::handle_mouseup(B
set_being_pressed(false);
m_tracking_mouse = false;
browsing_context().event_handler().set_mouse_event_tracking_paintable(nullptr);
navigable()->event_handler().set_mouse_event_tracking_paintable(nullptr);
return DispatchEventOfSameName::Yes;
}

View file

@ -284,7 +284,7 @@ MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mousedown(Badge<E
}
if (media_element.layout_mouse_tracking_component({}).has_value())
const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_paintable(this);
const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(this);
return DispatchEventOfSameName::Yes;
}
@ -306,7 +306,7 @@ MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mouseup(Badge<Eve
break;
}
const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_paintable(nullptr);
const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(nullptr);
media_element.set_layout_mouse_tracking_component({}, {});
return DispatchEventOfSameName::Yes;

View file

@ -65,7 +65,7 @@ void NestedBrowsingContextPaintable::paint(PaintContext& context, PaintPhase pha
context.recording_painter().restore();
if constexpr (HIGHLIGHT_FOCUSED_FRAME_DEBUG) {
if (layout_box().dom_node().nested_browsing_context()->is_focused_context()) {
if (layout_box().dom_node().content_navigable()->is_focused()) {
context.recording_painter().draw_rect(clip_rect.to_type<int>(), Color::Cyan);
}
}

View file

@ -559,19 +559,19 @@ void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase ph
void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment)
{
auto const& browsing_context = paintable.browsing_context();
auto const& navigable = *paintable.navigable();
if (!browsing_context.is_focused_context())
if (!navigable.is_focused())
return;
if (!browsing_context.cursor_blink_state())
if (!navigable.cursor_blink_state())
return;
if (browsing_context.cursor_position()->node() != paintable.dom_node())
if (navigable.cursor_position()->node() != paintable.dom_node())
return;
// NOTE: This checks if the cursor is before the start or after the end of the fragment. If it is at the end, after all text, it should still be painted.
if (browsing_context.cursor_position()->offset() < (unsigned)fragment.start() || browsing_context.cursor_position()->offset() > (unsigned)(fragment.start() + fragment.length()))
if (navigable.cursor_position()->offset() < (unsigned)fragment.start() || navigable.cursor_position()->offset() > (unsigned)(fragment.start() + fragment.length()))
return;
if (!fragment.layout_node().dom_node() || !fragment.layout_node().dom_node()->is_editable())
@ -581,7 +581,7 @@ void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintabl
auto text = fragment.string_view();
CSSPixelRect cursor_rect {
fragment_rect.x() + CSSPixels::nearest_value_for(paintable.layout_node().first_available_font().width(text.substring_view(0, paintable.browsing_context().cursor_position()->offset() - fragment.start()))),
fragment_rect.x() + CSSPixels::nearest_value_for(paintable.layout_node().first_available_font().width(text.substring_view(0, navigable.cursor_position()->offset() - fragment.start()))),
fragment_rect.top(),
1,
fragment_rect.height()

View file

@ -43,7 +43,7 @@ TextPaintable::DispatchEventOfSameName TextPaintable::handle_mousedown(Badge<Eve
if (!label)
return DispatchEventOfSameName::No;
const_cast<Layout::Label*>(label)->handle_mousedown_on_label({}, position, button);
const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_paintable(this);
const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(this);
return DispatchEventOfSameName::Yes;
}
@ -54,7 +54,7 @@ TextPaintable::DispatchEventOfSameName TextPaintable::handle_mouseup(Badge<Event
return DispatchEventOfSameName::No;
const_cast<Layout::Label*>(label)->handle_mouseup_on_label({}, position, button);
const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_paintable(nullptr);
const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(nullptr);
return DispatchEventOfSameName::Yes;
}

View file

@ -552,7 +552,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, i32 node_id, Optional<W
// FIXME: Pseudo-elements only exist as Layout::Nodes, which don't have style information
// in a format we can use. So, we run the StyleComputer again to get the specified
// values, and have to ignore the computed values and custom properties.
auto pseudo_element_style = page->page().focused_context().active_document()->style_computer().compute_style(element, pseudo_element);
auto pseudo_element_style = page->page().focused_navigable().active_document()->style_computer().compute_style(element, pseudo_element);
ByteString computed_values = serialize_json(pseudo_element_style);
ByteString resolved_values = "{}";
ByteString custom_properties_json = serialize_custom_properties_json(element, pseudo_element);
@ -817,20 +817,20 @@ Messages::WebContentServer::DumpGcGraphResponse ConnectionFromClient::dump_gc_gr
Messages::WebContentServer::GetSelectedTextResponse ConnectionFromClient::get_selected_text(u64 page_id)
{
if (auto page = this->page(page_id); page.has_value())
return page->page().focused_context().selected_text().to_byte_string();
return page->page().focused_navigable().selected_text().to_byte_string();
return ByteString {};
}
void ConnectionFromClient::select_all(u64 page_id)
{
if (auto page = this->page(page_id); page.has_value())
page->page().focused_context().select_all();
page->page().focused_navigable().select_all();
}
void ConnectionFromClient::paste(u64 page_id, String const& text)
{
if (auto page = this->page(page_id); page.has_value())
page->page().focused_context().paste(text);
page->page().focused_navigable().paste(text);
}
Messages::WebContentServer::DumpLayoutTreeResponse ConnectionFromClient::dump_layout_tree(u64 page_id)