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
commit 0ebfc0a4c4
Notes: sideshowbarker 2024-07-17 01:46:43 +09:00
26 changed files with 364 additions and 360 deletions

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>);