LibWeb/SVG: Implement activation behavior for SVGAElement

This makes navigation work when clicking on SVG `<a>` elements.
This commit is contained in:
Tim Ledbetter 2026-01-10 21:04:37 +00:00 committed by Jelle Raaijmakers
parent ba7b0c60f0
commit 94fa08dcfe
5 changed files with 65 additions and 2 deletions

View file

@ -267,7 +267,7 @@ bool Element::cannot_navigate() const
return true;
// - element is not an a element and is not connected.
return !is_html_anchor_element() && !is_connected();
return !(is_html_anchor_element() || is_svg_a_element()) && !is_connected();
}
// https://html.spec.whatwg.org/multipage/links.html#following-hyperlinks-2
@ -281,7 +281,7 @@ void Element::follow_the_hyperlink(Optional<String> hyperlink_suffix, HTML::User
String target_attribute_value;
// 3. If subject is an a or area element, then set targetAttributeValue to the result of getting an element's target given subject.
if (is_html_anchor_element() || is_html_area_element())
if (is_html_anchor_element() || is_html_area_element() || is_svg_a_element())
target_attribute_value = get_an_elements_target();
// 4. Let urlRecord be the result of encoding-parsing a URL given subject's href attribute value, relative to subject's node document.

View file

@ -7,8 +7,11 @@
#include <LibWeb/Bindings/SVGAElementPrototype.h>
#include <LibWeb/DOM/DOMTokenList.h>
#include <LibWeb/HTML/UserNavigationInvolvement.h>
#include <LibWeb/Layout/SVGGraphicsBox.h>
#include <LibWeb/SVG/AttributeNames.h>
#include <LibWeb/SVG/SVGAElement.h>
#include <LibWeb/UIEvents/MouseEvent.h>
namespace Web::SVG {
@ -84,4 +87,39 @@ GC::Ptr<Layout::Node> SVGAElement::create_layout_node(GC::Ref<CSS::ComputedPrope
return heap().allocate<Layout::SVGGraphicsBox>(document(), *this, move(style));
}
// https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements
void SVGAElement::activation_behavior(DOM::Event const& event)
{
// The activation behavior of an a or area element element given an event event is:
// 1. If element has no href attribute, then return.
if (href()->base_val().is_empty())
return;
// AD-HOC: Do not activate the element for clicks with the ctrl/cmd modifier present. This lets
// the browser process open the link in a new tab.
if (is<UIEvents::MouseEvent>(event)) {
auto const& mouse_event = static_cast<UIEvents::MouseEvent const&>(event);
if (mouse_event.platform_ctrl_key())
return;
}
// 2. Let hyperlinkSuffix be null.
Optional<String> hyperlink_suffix {};
// FIXME: 3. If element is an a element, and event's target is an img with an ismap attribute specified, then:
// 4. Let userInvolvement be event's user navigation involvement.
auto user_involvement = HTML::user_navigation_involvement(event);
// FIXME: 5. If the user has expressed a preference to download the hyperlink, then set userInvolvement to "browser UI".
// FIXME: 6. If element has a download attribute, or if the user has expressed a preference to download the
// hyperlink, then download the hyperlink created by element with hyperlinkSuffix set to hyperlinkSuffix and
// userInvolvement set to userInvolvement.
// 7. Otherwise, follow the hyperlink created by element with hyperlinkSuffix set to hyperlinkSuffix and userInvolvement set to userInvolvement.
follow_the_hyperlink(hyperlink_suffix, user_involvement);
}
}

View file

@ -39,6 +39,9 @@ private:
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
virtual i32 default_tab_index_value() const override;
virtual bool has_activation_behavior() const override { return true; }
virtual void activation_behavior(DOM::Event const&) override;
GC::Ptr<DOM::DOMTokenList> m_rel_list;
GC::Ptr<SVGAnimatedString> m_target;

View file

@ -0,0 +1,2 @@
basic link activated
target link activated in iframe

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<script src="include.js"></script>
<iframe id="target-frame" name="target-frame"></iframe>
<svg>
<a id="basic-link" href="javascript:println('basic link activated')">
<text y="20">Basic Link</text>
</a>
<a id="target-link" href="javascript:parent.println('target link activated in iframe')" target="target-frame">
<text y="40">Target Link</text>
</a>
</svg>
<script>
promiseTest(async () => {
document.getElementById("basic-link").dispatchEvent(new MouseEvent("click"));
await animationFrame();
document.getElementById("target-link").dispatchEvent(new MouseEvent("click"));
await animationFrame();
});
</script>