diff options
Diffstat (limited to 'Userland/Libraries/LibWeb/HTML')
7 files changed, 501 insertions, 5 deletions
diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp b/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp new file mode 100644 index 0000000000..fb15aa9335 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWeb/DOM/Document.h> +#include <LibWeb/DOM/HTMLCollection.h> +#include <LibWeb/DOM/Window.h> +#include <LibWeb/HTML/BrowsingContext.h> +#include <LibWeb/HTML/EventLoop/EventLoop.h> +#include <LibWeb/HTML/HTMLAnchorElement.h> +#include <LibWeb/Layout/BreakNode.h> +#include <LibWeb/Layout/InitialContainingBlock.h> +#include <LibWeb/Layout/TextNode.h> +#include <LibWeb/Page/Page.h> + +namespace Web::HTML { + +BrowsingContext::BrowsingContext(Page& page, HTML::BrowsingContextContainer* container) + : m_page(page) + , m_loader(*this) + , m_event_handler({}, *this) + , m_container(container) +{ + m_cursor_blink_timer = Core::Timer::construct(500, [this] { + if (!is_focused_context()) + return; + if (m_cursor_position.node() && m_cursor_position.node()->layout_node()) { + m_cursor_blink_state = !m_cursor_blink_state; + m_cursor_position.node()->layout_node()->set_needs_display(); + } + }); +} + +BrowsingContext::~BrowsingContext() +{ +} + +void BrowsingContext::did_edit(Badge<EditEventHandler>) +{ + reset_cursor_blink_cycle(); +} + +void BrowsingContext::reset_cursor_blink_cycle() +{ + m_cursor_blink_state = true; + m_cursor_blink_timer->restart(); + m_cursor_position.node()->layout_node()->set_needs_display(); +} + +bool BrowsingContext::is_focused_context() const +{ + return m_page && &m_page->focused_context() == this; +} + +void BrowsingContext::set_active_document(DOM::Document* document) +{ + if (m_active_document == document) + return; + + m_cursor_position = {}; + + if (m_active_document) + m_active_document->detach_from_browsing_context({}, *this); + + m_active_document = document; + + if (m_active_document) { + m_active_document->attach_to_browsing_context({}, *this); + if (m_page && is_top_level()) + m_page->client().page_did_change_title(m_active_document->title()); + } + + if (m_page) + m_page->client().page_did_set_document_in_top_level_browsing_context(m_active_document); +} + +void BrowsingContext::set_viewport_rect(Gfx::IntRect const& rect) +{ + bool did_change = false; + + if (m_size != rect.size()) { + m_size = rect.size(); + if (auto* document = active_document()) + document->set_needs_layout(); + did_change = true; + } + + if (m_viewport_scroll_offset != rect.location()) { + m_viewport_scroll_offset = rect.location(); + did_change = true; + } + + if (did_change) { + for (auto* client : m_viewport_clients) + client->browsing_context_did_set_viewport_rect(rect); + } + + // Schedule the HTML event loop to ensure that a `resize` event gets fired. + HTML::main_thread_event_loop().schedule(); +} + +void BrowsingContext::set_size(Gfx::IntSize const& size) +{ + if (m_size == size) + return; + m_size = size; + + if (auto* document = active_document()) + document->set_needs_layout(); + + for (auto* client : m_viewport_clients) + client->browsing_context_did_set_viewport_rect(viewport_rect()); + + // Schedule the HTML event loop to ensure that a `resize` event gets fired. + HTML::main_thread_event_loop().schedule(); +} + +void BrowsingContext::set_viewport_scroll_offset(Gfx::IntPoint const& offset) +{ + if (m_viewport_scroll_offset == offset) + return; + m_viewport_scroll_offset = offset; + + for (auto* client : m_viewport_clients) + client->browsing_context_did_set_viewport_rect(viewport_rect()); +} + +void BrowsingContext::set_needs_display(Gfx::IntRect const& rect) +{ + if (!viewport_rect().intersects(rect)) + return; + + if (is_top_level()) { + if (m_page) + m_page->client().page_did_invalidate(to_top_level_rect(rect)); + return; + } + + if (container() && container()->layout_node()) + container()->layout_node()->set_needs_display(); +} + +void BrowsingContext::scroll_to_anchor(String const& fragment) +{ + if (!active_document()) + return; + + auto element = active_document()->get_element_by_id(fragment); + if (!element) { + auto candidates = active_document()->get_elements_by_name(fragment); + for (auto& candidate : candidates->collect_matching_elements()) { + if (is<HTML::HTMLAnchorElement>(*candidate)) { + element = verify_cast<HTML::HTMLAnchorElement>(*candidate); + break; + } + } + } + + active_document()->update_layout(); + + if (!element || !element->layout_node()) + return; + + auto& layout_node = *element->layout_node(); + + Gfx::FloatRect float_rect { layout_node.box_type_agnostic_position(), { (float)viewport_rect().width(), (float)viewport_rect().height() } }; + if (is<Layout::Box>(layout_node)) { + auto& layout_box = verify_cast<Layout::Box>(layout_node); + auto padding_box = layout_box.box_model().padding_box(); + float_rect.translate_by(-padding_box.left, -padding_box.top); + } + + if (m_page) + m_page->client().page_did_request_scroll_into_view(enclosing_int_rect(float_rect)); +} + +Gfx::IntRect BrowsingContext::to_top_level_rect(Gfx::IntRect const& a_rect) +{ + auto rect = a_rect; + rect.set_location(to_top_level_position(a_rect.location())); + return rect; +} + +Gfx::IntPoint BrowsingContext::to_top_level_position(Gfx::IntPoint const& a_position) +{ + auto position = a_position; + for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) { + if (ancestor->is_top_level()) + break; + if (!ancestor->container()) + return {}; + if (!ancestor->container()->layout_node()) + return {}; + position.translate_by(ancestor->container()->layout_node()->box_type_agnostic_position().to_type<int>()); + } + return position; +} + +void BrowsingContext::set_cursor_position(DOM::Position position) +{ + if (m_cursor_position == position) + return; + + if (m_cursor_position.node() && m_cursor_position.node()->layout_node()) + m_cursor_position.node()->layout_node()->set_needs_display(); + + m_cursor_position = move(position); + + if (m_cursor_position.node() && m_cursor_position.node()->layout_node()) + m_cursor_position.node()->layout_node()->set_needs_display(); + + reset_cursor_blink_cycle(); +} + +String BrowsingContext::selected_text() const +{ + StringBuilder builder; + if (!active_document()) + return {}; + auto* layout_root = active_document()->layout_node(); + if (!layout_root) + return {}; + if (!layout_root->selection().is_valid()) + return {}; + + auto selection = layout_root->selection().normalized(); + + if (selection.start().layout_node == selection.end().layout_node) { + if (!is<Layout::TextNode>(*selection.start().layout_node)) + return ""; + return verify_cast<Layout::TextNode>(*selection.start().layout_node).text_for_rendering().substring(selection.start().index_in_node, selection.end().index_in_node - selection.start().index_in_node); + } + + // Start node + auto layout_node = selection.start().layout_node; + if (is<Layout::TextNode>(*layout_node)) { + auto& text = verify_cast<Layout::TextNode>(*layout_node).text_for_rendering(); + builder.append(text.substring(selection.start().index_in_node, text.length() - selection.start().index_in_node)); + } + + // Middle nodes + layout_node = layout_node->next_in_pre_order(); + while (layout_node && layout_node != selection.end().layout_node) { + if (is<Layout::TextNode>(*layout_node)) + builder.append(verify_cast<Layout::TextNode>(*layout_node).text_for_rendering()); + else if (is<Layout::BreakNode>(*layout_node) || is<Layout::BlockContainer>(*layout_node)) + builder.append('\n'); + + layout_node = layout_node->next_in_pre_order(); + } + + // End node + VERIFY(layout_node == selection.end().layout_node); + if (is<Layout::TextNode>(*layout_node)) { + auto& text = verify_cast<Layout::TextNode>(*layout_node).text_for_rendering(); + builder.append(text.substring(0, selection.end().index_in_node)); + } + + return builder.to_string(); +} + +void BrowsingContext::select_all() +{ + if (!active_document()) + return; + auto* layout_root = active_document()->layout_node(); + if (!layout_root) + return; + + Layout::Node const* first_layout_node = layout_root; + + for (;;) { + auto* next = first_layout_node->next_in_pre_order(); + if (!next) + break; + first_layout_node = next; + if (is<Layout::TextNode>(*first_layout_node)) + break; + } + + Layout::Node const* last_layout_node = first_layout_node; + + for (Layout::Node const* layout_node = first_layout_node; layout_node; layout_node = layout_node->next_in_pre_order()) { + if (is<Layout::TextNode>(*layout_node)) + last_layout_node = layout_node; + } + + VERIFY(first_layout_node); + VERIFY(last_layout_node); + + int last_layout_node_index_in_node = 0; + if (is<Layout::TextNode>(*last_layout_node)) { + auto const& text_for_rendering = verify_cast<Layout::TextNode>(*last_layout_node).text_for_rendering(); + if (!text_for_rendering.is_empty()) + last_layout_node_index_in_node = text_for_rendering.length() - 1; + } + + layout_root->set_selection({ { first_layout_node, 0 }, { last_layout_node, last_layout_node_index_in_node } }); +} + +void BrowsingContext::register_viewport_client(ViewportClient& client) +{ + auto result = m_viewport_clients.set(&client); + VERIFY(result == AK::HashSetResult::InsertedNewEntry); +} + +void BrowsingContext::unregister_viewport_client(ViewportClient& client) +{ + bool was_removed = m_viewport_clients.remove(&client); + VERIFY(was_removed); +} + +void BrowsingContext::register_frame_nesting(AK::URL const& url) +{ + m_frame_nesting_levels.ensure(url)++; +} + +bool BrowsingContext::is_frame_nesting_allowed(AK::URL const& url) const +{ + return m_frame_nesting_levels.get(url).value_or(0) < 3; +} + +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; +} + +DOM::Document* BrowsingContext::container_document() +{ + if (auto* container = this->container()) + return &container->document(); + return nullptr; +} + +DOM::Document const* BrowsingContext::container_document() const +{ + if (auto* container = this->container()) + return &container->document(); + return nullptr; +} + +// https://html.spec.whatwg.org/#rendering-opportunity +bool BrowsingContext::has_a_rendering_opportunity() const +{ + // A browsing context has a rendering opportunity if the user agent is currently able to present the contents of the browsing context to the user, + // accounting for hardware refresh rate constraints and user agent throttling for performance reasons, but considering content presentable even if it's outside the viewport. + + // FIXME: We should at the very least say `false` here if we're an inactive browser tab. + return true; +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContext.h b/Userland/Libraries/LibWeb/HTML/BrowsingContext.h new file mode 100644 index 0000000000..559a59ffae --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContext.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Function.h> +#include <AK/Noncopyable.h> +#include <AK/RefPtr.h> +#include <AK/WeakPtr.h> +#include <LibCore/Timer.h> +#include <LibGfx/Bitmap.h> +#include <LibGfx/Rect.h> +#include <LibGfx/Size.h> +#include <LibWeb/DOM/Position.h> +#include <LibWeb/HTML/BrowsingContextContainer.h> +#include <LibWeb/Loader/FrameLoader.h> +#include <LibWeb/Page/EventHandler.h> +#include <LibWeb/TreeNode.h> + +namespace Web::HTML { + +class BrowsingContext : public TreeNode<BrowsingContext> { +public: + static NonnullRefPtr<BrowsingContext> create_nested(Page& page, HTML::BrowsingContextContainer& container) { return adopt_ref(*new BrowsingContext(page, &container)); } + static NonnullRefPtr<BrowsingContext> create(Page& page) { return adopt_ref(*new BrowsingContext(page, nullptr)); } + ~BrowsingContext(); + + class ViewportClient { + public: + virtual ~ViewportClient() { } + virtual void browsing_context_did_set_viewport_rect(Gfx::IntRect const&) = 0; + }; + void register_viewport_client(ViewportClient&); + void unregister_viewport_client(ViewportClient&); + + bool is_top_level() const { return !container(); } + bool is_focused_context() const; + + DOM::Document const* active_document() const { return m_active_document; } + DOM::Document* active_document() { return m_active_document; } + + void set_active_document(DOM::Document*); + + Page* page() { return m_page; } + Page const* page() const { return m_page; } + + Gfx::IntSize const& size() const { return m_size; } + void set_size(Gfx::IntSize const&); + + void set_needs_display(Gfx::IntRect const&); + + Gfx::IntPoint const& viewport_scroll_offset() const { return m_viewport_scroll_offset; } + void set_viewport_scroll_offset(Gfx::IntPoint const&); + Gfx::IntRect viewport_rect() const { return { m_viewport_scroll_offset, m_size }; } + void set_viewport_rect(Gfx::IntRect const&); + + FrameLoader& loader() { return m_loader; } + FrameLoader const& loader() const { return m_loader; } + + Web::EventHandler& event_handler() { return m_event_handler; } + Web::EventHandler const& event_handler() const { return m_event_handler; } + + void scroll_to_anchor(String const&); + + BrowsingContext& top_level_browsing_context() + { + BrowsingContext* context = this; + while (context->parent()) + context = context->parent(); + return *context; + } + + BrowsingContext const& top_level_browsing_context() const { return const_cast<BrowsingContext*>(this)->top_level_browsing_context(); } + + HTML::BrowsingContextContainer* container() { return m_container; } + HTML::BrowsingContextContainer const* container() const { return m_container; } + + Gfx::IntPoint to_top_level_position(Gfx::IntPoint const&); + Gfx::IntRect to_top_level_rect(Gfx::IntRect const&); + + DOM::Position const& cursor_position() const { return m_cursor_position; } + void set_cursor_position(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 did_edit(Badge<EditEventHandler>); + + void register_frame_nesting(AK::URL const&); + bool is_frame_nesting_allowed(AK::URL const&) const; + + void set_frame_nesting_levels(HashMap<AK::URL, size_t> frame_nesting_levels) { m_frame_nesting_levels = move(frame_nesting_levels); }; + HashMap<AK::URL, size_t> const& frame_nesting_levels() const { return m_frame_nesting_levels; } + + DOM::Document* container_document(); + DOM::Document const* container_document() const; + + bool has_a_rendering_opportunity() const; + +private: + explicit BrowsingContext(Page&, HTML::BrowsingContextContainer*); + + void reset_cursor_blink_cycle(); + + WeakPtr<Page> m_page; + + FrameLoader m_loader; + Web::EventHandler m_event_handler; + + WeakPtr<HTML::BrowsingContextContainer> m_container; + RefPtr<DOM::Document> m_active_document; + Gfx::IntSize m_size; + Gfx::IntPoint m_viewport_scroll_offset; + + DOM::Position m_cursor_position; + RefPtr<Core::Timer> m_cursor_blink_timer; + bool m_cursor_blink_state { false }; + + HashTable<ViewportClient*> m_viewport_clients; + + HashMap<AK::URL, size_t> m_frame_nesting_levels; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp b/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp index 5d7b826f07..24e372943b 100644 --- a/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp +++ b/Userland/Libraries/LibWeb/HTML/BrowsingContextContainer.cpp @@ -6,9 +6,9 @@ #include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Event.h> +#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/BrowsingContextContainer.h> #include <LibWeb/Origin.h> -#include <LibWeb/Page/BrowsingContext.h> #include <LibWeb/Page/Page.h> namespace Web::HTML { diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index 1fd5acfb18..4bdd50c33b 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -10,9 +10,9 @@ #include <LibWeb/Bindings/MainThreadVM.h> #include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Window.h> +#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/EventLoop/EventLoop.h> #include <LibWeb/HighResolutionTime/Performance.h> -#include <LibWeb/Page/BrowsingContext.h> namespace Web::HTML { diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp index 3c8e3d59d4..3dac65925f 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp @@ -6,11 +6,11 @@ #include <AK/StringBuilder.h> #include <LibWeb/DOM/Document.h> +#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/EventNames.h> #include <LibWeb/HTML/HTMLFormElement.h> #include <LibWeb/HTML/HTMLInputElement.h> #include <LibWeb/HTML/SubmitEvent.h> -#include <LibWeb/Page/BrowsingContext.h> #include <LibWeb/Page/Page.h> #include <LibWeb/URL/URL.h> diff --git a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp index 7af50c7cf7..6783e5cce8 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLIFrameElement.cpp @@ -6,10 +6,10 @@ #include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Event.h> +#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/HTMLIFrameElement.h> #include <LibWeb/Layout/FrameBox.h> #include <LibWeb/Origin.h> -#include <LibWeb/Page/BrowsingContext.h> namespace Web::HTML { diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 9198e864e1..376fbf1199 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -8,6 +8,7 @@ #include <LibWeb/DOM/Event.h> #include <LibWeb/DOM/ShadowRoot.h> #include <LibWeb/DOM/Text.h> +#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/EventNames.h> #include <LibWeb/HTML/HTMLFormElement.h> #include <LibWeb/HTML/HTMLInputElement.h> @@ -15,7 +16,6 @@ #include <LibWeb/Layout/ButtonBox.h> #include <LibWeb/Layout/CheckBox.h> #include <LibWeb/Layout/RadioButton.h> -#include <LibWeb/Page/BrowsingContext.h> namespace Web::HTML { |