Coverage Report

Created: 2025-08-28 06:26

/src/serenity/Userland/Libraries/LibWeb/SVG/SVGElement.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
3
 * Copyright (c) 2023, Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibWeb/Bindings/ExceptionOrUtils.h>
9
#include <LibWeb/Bindings/Intrinsics.h>
10
#include <LibWeb/Bindings/SVGElementPrototype.h>
11
#include <LibWeb/CSS/StyleProperties.h>
12
#include <LibWeb/DOM/Document.h>
13
#include <LibWeb/DOM/ShadowRoot.h>
14
#include <LibWeb/HTML/DOMStringMap.h>
15
#include <LibWeb/SVG/SVGElement.h>
16
#include <LibWeb/SVG/SVGSVGElement.h>
17
#include <LibWeb/SVG/SVGUseElement.h>
18
19
namespace Web::SVG {
20
21
SVGElement::SVGElement(DOM::Document& document, DOM::QualifiedName qualified_name)
22
0
    : Element(document, move(qualified_name))
23
0
{
24
0
}
25
26
void SVGElement::initialize(JS::Realm& realm)
27
0
{
28
0
    Base::initialize(realm);
29
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGElement);
30
0
}
31
32
void SVGElement::visit_edges(Cell::Visitor& visitor)
33
0
{
34
0
    Base::visit_edges(visitor);
35
0
    visitor.visit(m_dataset);
36
0
    visitor.visit(m_class_name_animated_string);
37
0
}
38
39
JS::NonnullGCPtr<HTML::DOMStringMap> SVGElement::dataset()
40
0
{
41
0
    if (!m_dataset)
42
0
        m_dataset = HTML::DOMStringMap::create(*this);
43
0
    return *m_dataset;
44
0
}
45
46
void SVGElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value)
47
0
{
48
0
    Base::attribute_changed(name, old_value, value);
49
50
0
    update_use_elements_that_reference_this();
51
0
}
52
53
void SVGElement::inserted()
54
0
{
55
0
    Base::inserted();
56
57
0
    update_use_elements_that_reference_this();
58
0
}
59
60
void SVGElement::children_changed()
61
0
{
62
0
    Base::children_changed();
63
64
0
    update_use_elements_that_reference_this();
65
0
}
66
67
void SVGElement::update_use_elements_that_reference_this()
68
0
{
69
0
    if (is<SVGUseElement>(this)
70
        // If this element is in a shadow root, it already represents a clone and is not itself referenced.
71
0
        || is<DOM::ShadowRoot>(this->root())
72
        // If this does not have an id it cannot be referenced, no point in searching the entire DOM tree.
73
0
        || !id().has_value()
74
        // An unconnected node cannot have valid references.
75
        // This also prevents searches for elements that are in the process of being constructed - as clones.
76
0
        || !this->is_connected()
77
        // Each use element already listens for the completely_loaded event and then clones its referece,
78
        // we do not have to also clone it in the process of initial DOM building.
79
0
        || !document().is_completely_loaded()) {
80
81
0
        return;
82
0
    }
83
84
0
    document().for_each_in_subtree_of_type<SVGUseElement>([this](SVGUseElement& use_element) {
85
0
        use_element.svg_element_changed(*this);
86
0
        return TraversalDecision::Continue;
87
0
    });
88
0
}
89
90
void SVGElement::removed_from(Node* parent)
91
0
{
92
0
    Base::removed_from(parent);
93
94
0
    remove_from_use_element_that_reference_this();
95
0
}
96
97
void SVGElement::remove_from_use_element_that_reference_this()
98
0
{
99
0
    if (is<SVGUseElement>(this) || !id().has_value()) {
100
0
        return;
101
0
    }
102
103
0
    document().for_each_in_subtree_of_type<SVGUseElement>([this](SVGUseElement& use_element) {
104
0
        use_element.svg_element_removed(*this);
105
0
        return TraversalDecision::Continue;
106
0
    });
107
0
}
108
109
void SVGElement::focus()
110
0
{
111
0
    dbgln("(STUBBED) SVGElement::focus()");
112
0
}
113
114
void SVGElement::blur()
115
0
{
116
0
    dbgln("(STUBBED) SVGElement::blur()");
117
0
}
118
119
// https://svgwg.org/svg2-draft/types.html#__svg__SVGElement__classNames
120
JS::NonnullGCPtr<SVGAnimatedString> SVGElement::class_name()
121
0
{
122
    // The className IDL attribute reflects the ‘class’ attribute.
123
0
    if (!m_class_name_animated_string)
124
0
        m_class_name_animated_string = SVGAnimatedString::create(realm(), *this, AttributeNames::class_);
125
126
0
    return *m_class_name_animated_string;
127
0
}
128
129
// https://svgwg.org/svg2-draft/types.html#__svg__SVGElement__ownerSVGElement
130
JS::GCPtr<SVGSVGElement> SVGElement::owner_svg_element()
131
0
{
132
    // The ownerSVGElement IDL attribute represents the nearest ancestor ‘svg’ element.
133
    // On getting ownerSVGElement, the nearest ancestor ‘svg’ element is returned;
134
    // if the current element is the outermost svg element, then null is returned.
135
0
    return shadow_including_first_ancestor_of_type<SVGSVGElement>();
136
0
}
137
138
JS::NonnullGCPtr<SVGAnimatedLength> SVGElement::svg_animated_length_for_property(CSS::PropertyID property) const
139
0
{
140
    // FIXME: Create a proper animated value when animations are supported.
141
0
    auto make_length = [&] {
142
0
        if (auto const* style = computed_css_values(); style) {
143
0
            if (auto length = style->length_percentage(property); length.has_value())
144
0
                return SVGLength::from_length_percentage(realm(), *length);
145
0
        }
146
0
        return SVGLength::create(realm(), 0, 0.0f);
147
0
    };
148
0
    return SVGAnimatedLength::create(realm(), make_length(), make_length());
149
0
}
150
151
}