From c41bae3d54a0e88af559751b4ec8ec5e77b00069 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 20 Oct 2019 09:14:12 +0200 Subject: [PATCH] LibHTML+Browser: Support scrolling to anchor with This patch implements basic support for fragment links. To figure out where we actually want to scroll to, we have to do something different based on the layout node's box type. So if it's a regular LayoutBox we can just use the LayoutBox::position(). However, if it's an inline layout node, we use the position of the first line box fragment in the containing block contributed by this layout node or one of its descendants. --- Applications/Browser/main.cpp | 6 ++++- Base/home/anon/www/afrag.html | 23 ++++++++++++++++++ Base/home/anon/www/welcome.html | 1 + Libraries/LibHTML/HtmlView.cpp | 24 +++++++++++++++++++ Libraries/LibHTML/HtmlView.h | 1 + Libraries/LibHTML/Layout/LayoutBlock.h | 21 +++++++++++++--- Libraries/LibHTML/Layout/LayoutNode.cpp | 32 ++++++++++++++++++++----- Libraries/LibHTML/Layout/LayoutNode.h | 5 ++-- 8 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 Base/home/anon/www/afrag.html diff --git a/Applications/Browser/main.cpp b/Applications/Browser/main.cpp index 95af1baeca..69e86b16a9 100644 --- a/Applications/Browser/main.cpp +++ b/Applications/Browser/main.cpp @@ -91,7 +91,11 @@ int main(int argc, char** argv) }; html_widget->on_link_click = [&](auto& url) { - html_widget->load(html_widget->document()->complete_url(url)); + if (url.starts_with("#")) { + html_widget->scroll_to_anchor(url.substring_view(1, url.length() - 1)); + } else { + html_widget->load(html_widget->document()->complete_url(url)); + } }; html_widget->on_title_change = [&](auto& title) { diff --git a/Base/home/anon/www/afrag.html b/Base/home/anon/www/afrag.html new file mode 100644 index 0000000000..cf60bc5b0e --- /dev/null +++ b/Base/home/anon/www/afrag.html @@ -0,0 +1,23 @@ + + a#hash test + + +

Section 1

+













+













+

Section 2

+













+













+

Section 3

+













+













+

Section 4

+













+













+ + diff --git a/Base/home/anon/www/welcome.html b/Base/home/anon/www/welcome.html index 7b5ddf0239..2e78818e68 100644 --- a/Base/home/anon/www/welcome.html +++ b/Base/home/anon/www/welcome.html @@ -33,6 +33,7 @@ h1 {
  • blink element
  • br element
  • hover element
  • +
  • links with fragments
  • www.serenityos.org
  • diff --git a/Libraries/LibHTML/HtmlView.cpp b/Libraries/LibHTML/HtmlView.cpp index a3b05db973..c454284267 100644 --- a/Libraries/LibHTML/HtmlView.cpp +++ b/Libraries/LibHTML/HtmlView.cpp @@ -312,3 +312,27 @@ LayoutDocument* HtmlView::layout_root() return nullptr; return const_cast(document()->layout_node()); } + +void HtmlView::scroll_to_anchor(const StringView& name) +{ + HTMLAnchorElement* element = nullptr; + m_document->for_each_in_subtree([&](auto& node) { + if (!is(node)) + return; + auto& anchor_element = to(node); + if (anchor_element.name() == name) + element = &anchor_element; + }); + + if (!element) { + dbg() << "HtmlView::scroll_to_anchor(): Anchor not found: '" << name << "'"; + return; + } + if (!element->layout_node()) { + dbg() << "HtmlView::scroll_to_anchor(): Anchor found but without layout node: '" << name << "'"; + return; + } + auto& layout_node = *element->layout_node(); + scroll_into_view({ layout_node.box_type_agnostic_position(), visible_content_rect().size() }, true, true); + window()->set_override_cursor(GStandardCursor::None); +} diff --git a/Libraries/LibHTML/HtmlView.h b/Libraries/LibHTML/HtmlView.h index b0d3d05d34..784950a692 100644 --- a/Libraries/LibHTML/HtmlView.h +++ b/Libraries/LibHTML/HtmlView.h @@ -23,6 +23,7 @@ public: void reload(); void load(const URL&); + void scroll_to_anchor(const StringView&); URL url() const; diff --git a/Libraries/LibHTML/Layout/LayoutBlock.h b/Libraries/LibHTML/Layout/LayoutBlock.h index 84e3ef7ef7..cd8dab8ccb 100644 --- a/Libraries/LibHTML/Layout/LayoutBlock.h +++ b/Libraries/LibHTML/Layout/LayoutBlock.h @@ -30,6 +30,11 @@ public: LayoutBlock* next_sibling() { return to(LayoutNode::next_sibling()); } const LayoutBlock* next_sibling() const { return to(LayoutNode::next_sibling()); } + template + void for_each_fragment(Callback); + template + void for_each_fragment(Callback) const; + private: virtual bool is_block() const override { return true; } @@ -46,10 +51,20 @@ private: }; template -void LayoutNode::for_each_fragment_of_this(Callback callback) +void LayoutBlock::for_each_fragment(Callback callback) { - auto& block = *containing_block(); - for (auto& line_box : block.line_boxes()) { + for (auto& line_box : line_boxes()) { + for (auto& fragment : line_box.fragments()) { + if (callback(fragment) == IterationDecision::Break) + return; + } + } +} + +template +void LayoutBlock::for_each_fragment(Callback callback) const +{ + for (auto& line_box : line_boxes()) { for (auto& fragment : line_box.fragments()) { if (callback(fragment) == IterationDecision::Break) return; diff --git a/Libraries/LibHTML/Layout/LayoutNode.cpp b/Libraries/LibHTML/Layout/LayoutNode.cpp index 95670127ed..12bff2146c 100644 --- a/Libraries/LibHTML/Layout/LayoutNode.cpp +++ b/Libraries/LibHTML/Layout/LayoutNode.cpp @@ -79,10 +79,30 @@ void LayoutNode::set_needs_display() auto* frame = document().frame(); ASSERT(frame); - for_each_fragment_of_this([&](auto& fragment) { - if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) { - const_cast(frame)->set_needs_display(fragment.rect()); - } - return IterationDecision::Continue; - }); + if (auto* block = containing_block()) { + block->for_each_fragment([&](auto& fragment) { + if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) { + const_cast(frame)->set_needs_display(fragment.rect()); + } + return IterationDecision::Continue; + }); + } +} + +Point LayoutNode::box_type_agnostic_position() const +{ + if (is_box()) + return to(*this).position(); + ASSERT(is_inline()); + Point position; + if (auto* block = containing_block()) { + block->for_each_fragment([&](auto& fragment) { + if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) { + position = fragment.rect().location(); + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + } + return position; } diff --git a/Libraries/LibHTML/Layout/LayoutNode.h b/Libraries/LibHTML/Layout/LayoutNode.h index 0fc8f38ca7..a3dc1e2050 100644 --- a/Libraries/LibHTML/Layout/LayoutNode.h +++ b/Libraries/LibHTML/Layout/LayoutNode.h @@ -80,9 +80,6 @@ public: virtual void set_needs_display(); - template - void for_each_fragment_of_this(Callback); - bool children_are_inline() const { return m_children_are_inline; } void set_children_are_inline(bool value) { m_children_are_inline = value; } @@ -104,6 +101,8 @@ public: template T* first_ancestor_of_type(); + Point box_type_agnostic_position() const; + protected: explicit LayoutNode(const Node*);