mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-20 09:49:15 +03:00
LibHTML+Browser: Support scrolling to anchor with <a href="#foo">
This patch implements basic support for <a href="#foo"> 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.
This commit is contained in:
parent
202dfbd6cd
commit
c41bae3d54
Notes:
sideshowbarker
2024-07-19 11:37:23 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/c41bae3d54a
@ -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) {
|
||||
|
23
Base/home/anon/www/afrag.html
Normal file
23
Base/home/anon/www/afrag.html
Normal file
@ -0,0 +1,23 @@
|
||||
<html>
|
||||
<head><title>a#hash test</title></head>
|
||||
<body>
|
||||
<ul>
|
||||
<li><a href="#section1">Section 1</a></li>
|
||||
<li><a href="#section2">Section 2</a></li>
|
||||
<li><a href="#section3">Section 3</a></li>
|
||||
<li><a href="#section4">Section 4</a></li>
|
||||
</ul>
|
||||
<h1><a name="section1">Section 1</a></h1>
|
||||
<br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br>
|
||||
<br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br>
|
||||
<h1><a name="section2">Section 2</a></h1>
|
||||
<br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br>
|
||||
<br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br>
|
||||
<h1><a name="section3">Section 3</a></h1>
|
||||
<br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br>
|
||||
<br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br>
|
||||
<h1><a name="section4">Section 4</a></h1>
|
||||
<br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br>
|
||||
<br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br>
|
||||
</body>
|
||||
</html>
|
@ -33,6 +33,7 @@ h1 {
|
||||
<li><a href="blink.html">blink element</a></li>
|
||||
<li><a href="br.html">br element</a></li>
|
||||
<li><a href="hover.html">hover element</a></li>
|
||||
<li><a href="afrag.html">links with fragments</a></li>
|
||||
<li><a href="http://www.serenityos.org/">www.serenityos.org</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
|
@ -312,3 +312,27 @@ LayoutDocument* HtmlView::layout_root()
|
||||
return nullptr;
|
||||
return const_cast<LayoutDocument*>(document()->layout_node());
|
||||
}
|
||||
|
||||
void HtmlView::scroll_to_anchor(const StringView& name)
|
||||
{
|
||||
HTMLAnchorElement* element = nullptr;
|
||||
m_document->for_each_in_subtree([&](auto& node) {
|
||||
if (!is<HTMLAnchorElement>(node))
|
||||
return;
|
||||
auto& anchor_element = to<HTMLAnchorElement>(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);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ public:
|
||||
|
||||
void reload();
|
||||
void load(const URL&);
|
||||
void scroll_to_anchor(const StringView&);
|
||||
|
||||
URL url() const;
|
||||
|
||||
|
@ -30,6 +30,11 @@ public:
|
||||
LayoutBlock* next_sibling() { return to<LayoutBlock>(LayoutNode::next_sibling()); }
|
||||
const LayoutBlock* next_sibling() const { return to<LayoutBlock>(LayoutNode::next_sibling()); }
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_fragment(Callback);
|
||||
template<typename Callback>
|
||||
void for_each_fragment(Callback) const;
|
||||
|
||||
private:
|
||||
virtual bool is_block() const override { return true; }
|
||||
|
||||
@ -46,10 +51,20 @@ private:
|
||||
};
|
||||
|
||||
template<typename Callback>
|
||||
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<typename Callback>
|
||||
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;
|
||||
|
@ -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*>(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*>(frame)->set_needs_display(fragment.rect());
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Point LayoutNode::box_type_agnostic_position() const
|
||||
{
|
||||
if (is_box())
|
||||
return to<LayoutBox>(*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;
|
||||
}
|
||||
|
@ -80,9 +80,6 @@ public:
|
||||
|
||||
virtual void set_needs_display();
|
||||
|
||||
template<typename Callback>
|
||||
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<typename T>
|
||||
T* first_ancestor_of_type();
|
||||
|
||||
Point box_type_agnostic_position() const;
|
||||
|
||||
protected:
|
||||
explicit LayoutNode(const Node*);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user