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