
With this change, we now have ~1200 CellAllocators across both LibJS and LibWeb in a normal WebContent instance. This gives us a minimum heap size of 4.7 MiB in the scenario where we only have one cell allocated per type. Of course, in practice there will be many more of each type, so the effective overhead is quite a bit smaller than that in practice. I left a few types unconverted to this mechanism because I got tired of doing this. :^)
189 lines
6.6 KiB
C++
189 lines
6.6 KiB
C++
/*
|
||
* Copyright (c) 2023, Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <LibWeb/Bindings/Intrinsics.h>
|
||
#include <LibWeb/DOM/Document.h>
|
||
#include <LibWeb/DOM/ElementFactory.h>
|
||
#include <LibWeb/DOM/Event.h>
|
||
#include <LibWeb/DOM/ShadowRoot.h>
|
||
#include <LibWeb/Layout/Box.h>
|
||
#include <LibWeb/Namespace.h>
|
||
#include <LibWeb/SVG/AttributeNames.h>
|
||
#include <LibWeb/SVG/SVGSVGElement.h>
|
||
#include <LibWeb/SVG/SVGUseElement.h>
|
||
|
||
namespace Web::SVG {
|
||
|
||
JS_DEFINE_ALLOCATOR(SVGUseElement);
|
||
|
||
SVGUseElement::SVGUseElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
||
: SVGGraphicsElement(document, qualified_name)
|
||
{
|
||
}
|
||
|
||
void SVGUseElement::initialize(JS::Realm& realm)
|
||
{
|
||
Base::initialize(realm);
|
||
set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGUseElementPrototype>(realm, "SVGUseElement"));
|
||
|
||
// The shadow tree is open (inspectable by script), but read-only.
|
||
auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm, document(), *this, Bindings::ShadowRootMode::Open);
|
||
|
||
// The user agent must create a use-element shadow tree whose host is the ‘use’ element itself
|
||
set_shadow_root(shadow_root);
|
||
|
||
m_document_observer = realm.heap().allocate<DOM::DocumentObserver>(realm, realm, document());
|
||
m_document_observer->set_document_completely_loaded([this]() {
|
||
clone_element_tree_as_our_shadow_tree(referenced_element());
|
||
});
|
||
}
|
||
|
||
void SVGUseElement::visit_edges(Cell::Visitor& visitor)
|
||
{
|
||
Base::visit_edges(visitor);
|
||
visitor.visit(m_document_observer);
|
||
}
|
||
|
||
void SVGUseElement::attribute_changed(FlyString const& name, Optional<String> const& value)
|
||
{
|
||
Base::attribute_changed(name, value);
|
||
|
||
// https://svgwg.org/svg2-draft/struct.html#UseLayout
|
||
if (name == SVG::AttributeNames::x) {
|
||
m_x = AttributeParser::parse_coordinate(value.value_or(String {}));
|
||
} else if (name == SVG::AttributeNames::y) {
|
||
m_y = AttributeParser::parse_coordinate(value.value_or(String {}));
|
||
} else if (name == SVG::AttributeNames::href) {
|
||
// FIXME: Support the xlink:href attribute as a fallback
|
||
m_referenced_id = parse_id_from_href(value.value_or(String {}).to_deprecated_string());
|
||
|
||
clone_element_tree_as_our_shadow_tree(referenced_element());
|
||
}
|
||
}
|
||
|
||
Optional<FlyString> SVGUseElement::parse_id_from_href(DeprecatedString const& href)
|
||
{
|
||
auto id_seperator = href.find('#');
|
||
if (!id_seperator.has_value()) {
|
||
return {};
|
||
}
|
||
|
||
auto id = href.substring_view(id_seperator.value() + 1);
|
||
return MUST(FlyString::from_utf8(id));
|
||
}
|
||
|
||
Gfx::AffineTransform SVGUseElement::element_transform() const
|
||
{
|
||
// The x and y properties define an additional transformation (translate(x,y), where x and y represent the computed value of the corresponding property)
|
||
// to be applied to the ‘use’ element, after any transformations specified with other properties
|
||
return Base::element_transform().translate(m_x.value_or(0), m_y.value_or(0));
|
||
}
|
||
|
||
void SVGUseElement::inserted()
|
||
{
|
||
Base::inserted();
|
||
}
|
||
|
||
void SVGUseElement::svg_element_changed(SVGElement& svg_element)
|
||
{
|
||
auto to_clone = referenced_element();
|
||
if (!to_clone) {
|
||
return;
|
||
}
|
||
|
||
// NOTE: We need to check the ancestor because attribute_changed of a child doesn't call children_changed on the parent(s)
|
||
if (to_clone == &svg_element || to_clone->is_ancestor_of(svg_element)) {
|
||
clone_element_tree_as_our_shadow_tree(to_clone);
|
||
}
|
||
}
|
||
|
||
void SVGUseElement::svg_element_removed(SVGElement& svg_element)
|
||
{
|
||
if (!m_referenced_id.has_value()) {
|
||
return;
|
||
}
|
||
|
||
if (svg_element.deprecated_attribute("id"sv).matches(m_referenced_id.value())) {
|
||
shadow_root()->remove_all_children();
|
||
}
|
||
}
|
||
|
||
JS::GCPtr<DOM::Element> SVGUseElement::referenced_element()
|
||
{
|
||
if (!m_referenced_id.has_value()) {
|
||
return nullptr;
|
||
}
|
||
|
||
// FIXME: Support loading of external svg documents
|
||
return document().get_element_by_id(m_referenced_id.value());
|
||
}
|
||
|
||
// https://svgwg.org/svg2-draft/struct.html#UseShadowTree
|
||
void SVGUseElement::clone_element_tree_as_our_shadow_tree(Element* to_clone) const
|
||
{
|
||
shadow_root()->remove_all_children();
|
||
|
||
if (to_clone && is_valid_reference_element(to_clone)) {
|
||
// The ‘use’ element references another element, a copy of which is rendered in place of the ‘use’ in the document.
|
||
auto cloned_reference_node = to_clone->clone_node(nullptr, true);
|
||
shadow_root()->append_child(cloned_reference_node).release_value_but_fixme_should_propagate_errors();
|
||
}
|
||
}
|
||
|
||
bool SVGUseElement::is_valid_reference_element(Element* reference_element) const
|
||
{
|
||
// If the referenced element that results from resolving the URL is not an SVG element, then the reference is invalid and the ‘use’ element is in error.
|
||
// If the referenced element is a (shadow-including) ancestor of the ‘use’ element, then this is an invalid circular reference and the ‘use’ element is in error.
|
||
return reference_element->is_svg_element() && !reference_element->is_ancestor_of(*this);
|
||
}
|
||
|
||
// https://www.w3.org/TR/SVG11/shapes.html#RectElementXAttribute
|
||
JS::NonnullGCPtr<SVGAnimatedLength> SVGUseElement::x() const
|
||
{
|
||
// FIXME: Populate the unit type when it is parsed (0 here is "unknown").
|
||
// FIXME: Create a proper animated value when animations are supported.
|
||
auto base_length = SVGLength::create(realm(), 0, m_x.value_or(0));
|
||
auto anim_length = SVGLength::create(realm(), 0, m_x.value_or(0));
|
||
return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length));
|
||
}
|
||
|
||
// https://www.w3.org/TR/SVG11/shapes.html#RectElementYAttribute
|
||
JS::NonnullGCPtr<SVGAnimatedLength> SVGUseElement::y() const
|
||
{
|
||
// FIXME: Populate the unit type when it is parsed (0 here is "unknown").
|
||
// FIXME: Create a proper animated value when animations are supported.
|
||
auto base_length = SVGLength::create(realm(), 0, m_y.value_or(0));
|
||
auto anim_length = SVGLength::create(realm(), 0, m_y.value_or(0));
|
||
return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length));
|
||
}
|
||
|
||
JS::NonnullGCPtr<SVGAnimatedLength> SVGUseElement::width() const
|
||
{
|
||
TODO();
|
||
}
|
||
|
||
JS::NonnullGCPtr<SVGAnimatedLength> SVGUseElement::height() const
|
||
{
|
||
TODO();
|
||
}
|
||
|
||
// https://svgwg.org/svg2-draft/struct.html#TermInstanceRoot
|
||
JS::GCPtr<SVGElement> SVGUseElement::instance_root() const
|
||
{
|
||
return shadow_root()->first_child_of_type<SVGElement>();
|
||
}
|
||
|
||
JS::GCPtr<SVGElement> SVGUseElement::animated_instance_root() const
|
||
{
|
||
return instance_root();
|
||
}
|
||
|
||
JS::GCPtr<Layout::Node> SVGUseElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
|
||
{
|
||
return heap().allocate_without_realm<Layout::Box>(document(), this, move(style));
|
||
}
|
||
|
||
}
|