Files
servo/components/script/dom/svg/svgsvgelement.rs
elomscansio aa7eca43b7 script: propagate VirtualMethods::unbind_from_tree with &mut JSContext (#44422)
Propagate `&mut JSContext` in `VirtualMethods::unbind_from_tree`

Testing: Successful build is enough
Fixes: #42837

---------

Signed-off-by: Emmanuel Paul Elom <elomemmanuel007@gmail.com>
2026-04-23 14:09:11 +00:00

280 lines
9.7 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use base64::Engine as _;
use cssparser::{Parser, ParserInput};
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::context::JSContext;
use js::rust::HandleObject;
use layout_api::SVGElementData;
use servo_url::ServoUrl;
use style::attr::AttrValue;
use style::parser::ParserContext;
use style::stylesheets::Origin;
use style::values::specified::LengthPercentage;
use style_traits::ParsingMode;
use uuid::Uuid;
use xml5ever::serialize::TraversalScope;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::node::{
ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding,
UnbindContext,
};
use crate::dom::svg::svggraphicselement::SVGGraphicsElement;
use crate::dom::virtualmethods::VirtualMethods;
#[dom_struct]
pub(crate) struct SVGSVGElement {
svggraphicselement: SVGGraphicsElement,
uuid: String,
// The XML source of subtree rooted at this SVG element, serialized into
// a base64 encoded `data:` url. This is cached to avoid recomputation
// on each layout and must be invalidated when the subtree changes.
#[no_trace]
cached_serialized_data_url: DomRefCell<Option<Result<ServoUrl, ()>>>,
}
impl SVGSVGElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> SVGSVGElement {
SVGSVGElement {
svggraphicselement: SVGGraphicsElement::new_inherited(local_name, prefix, document),
uuid: Uuid::new_v4().to_string(),
cached_serialized_data_url: Default::default(),
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new(
cx: &mut js::context::JSContext,
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
) -> DomRoot<SVGSVGElement> {
Node::reflect_node_with_proto(
cx,
Box::new(SVGSVGElement::new_inherited(local_name, prefix, document)),
document,
proto,
)
}
#[expect(unsafe_code)]
pub(crate) fn serialize_and_cache_subtree(&self) {
// TODO: https://github.com/servo/servo/issues/43142
let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
let cx = &mut cx;
let cloned_nodes = self.process_use_elements(cx);
let serialize_result = self
.upcast::<Node>()
.xml_serialize(TraversalScope::IncludeNode);
self.cleanup_cloned_nodes(cx, &cloned_nodes);
let Ok(xml_source) = serialize_result else {
*self.cached_serialized_data_url.borrow_mut() = Some(Err(()));
return;
};
let xml_source: String = xml_source.into();
let base64_encoded_source = base64::engine::general_purpose::STANDARD.encode(xml_source);
let data_url = format!("data:image/svg+xml;base64,{}", base64_encoded_source);
match ServoUrl::parse(&data_url) {
Ok(url) => *self.cached_serialized_data_url.borrow_mut() = Some(Ok(url)),
Err(error) => error!("Unable to parse serialized SVG data url: {error}"),
};
}
fn process_use_elements(&self, cx: &mut JSContext) -> Vec<DomRoot<Node>> {
let mut cloned_nodes = Vec::new();
let root_node = self.upcast::<Node>();
for node in root_node.traverse_preorder(ShadowIncluding::No) {
if let Some(element) = node.downcast::<Element>() {
if element.local_name() == &local_name!("use") {
if let Some(cloned) = self.process_single_use_element(cx, element) {
cloned_nodes.push(cloned);
}
}
}
}
cloned_nodes
}
fn process_single_use_element(
&self,
cx: &mut JSContext,
use_element: &Element,
) -> Option<DomRoot<Node>> {
let href = use_element.get_string_attribute(&local_name!("href"));
let href_view = href.str();
let id_str = href_view.strip_prefix("#")?;
let id = DOMString::from(id_str);
let document = self.upcast::<Node>().owner_doc();
let referenced_element = document.GetElementById(id)?;
let referenced_node = referenced_element.upcast::<Node>();
let has_svg_ancestor = referenced_node
.inclusive_ancestors(ShadowIncluding::No)
.any(|ancestor| ancestor.is::<SVGSVGElement>());
if !has_svg_ancestor {
return None;
}
let cloned_node = Node::clone(
cx,
referenced_node,
None,
CloneChildrenFlag::CloneChildren,
None,
);
let root_node = self.upcast::<Node>();
let _ = root_node.AppendChild(cx, &cloned_node);
Some(cloned_node)
}
fn cleanup_cloned_nodes(&self, cx: &mut JSContext, cloned_nodes: &[DomRoot<Node>]) {
if cloned_nodes.is_empty() {
return;
}
let root_node = self.upcast::<Node>();
for cloned_node in cloned_nodes {
let _ = root_node.RemoveChild(cx, cloned_node);
}
}
fn invalidate_cached_serialized_subtree(&self) {
*self.cached_serialized_data_url.borrow_mut() = None;
self.upcast::<Node>().dirty(NodeDamage::Other);
}
}
impl<'dom> LayoutDom<'dom, SVGSVGElement> {
#[expect(unsafe_code)]
pub(crate) fn data(self) -> SVGElementData<'dom> {
let svg_id = self.unsafe_get().uuid.clone();
let element = self.upcast::<Element>();
let width = element.get_attr_for_layout(&ns!(), &local_name!("width"));
let height = element.get_attr_for_layout(&ns!(), &local_name!("height"));
let view_box = element.get_attr_for_layout(&ns!(), &local_name!("viewBox"));
SVGElementData {
source: unsafe {
self.unsafe_get()
.cached_serialized_data_url
.borrow_for_layout()
.clone()
},
width,
height,
view_box,
svg_id,
}
}
}
impl VirtualMethods for SVGSVGElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<SVGGraphicsElement>() as &dyn VirtualMethods)
}
fn attribute_mutated(
&self,
cx: &mut js::context::JSContext,
attr: &Attr,
mutation: AttributeMutation,
) {
self.super_type()
.unwrap()
.attribute_mutated(cx, attr, mutation);
self.invalidate_cached_serialized_subtree();
}
fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
match attr.local_name() {
&local_name!("width") | &local_name!("height") => true,
_ => self
.super_type()
.unwrap()
.attribute_affects_presentational_hints(attr),
}
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match *name {
local_name!("width") | local_name!("height") => {
let value = &value.str();
let parser_input = &mut ParserInput::new(value);
let parser = &mut Parser::new(parser_input);
let doc = self.owner_document();
let url = doc.url().into_url().into();
let context = ParserContext::new(
Origin::Author,
&url,
None,
ParsingMode::ALLOW_UNITLESS_LENGTH,
doc.quirks_mode(),
/* namespaces = */ Default::default(),
None,
None,
);
let val = LengthPercentage::parse_quirky(
&context,
parser,
style::values::specified::AllowQuirks::Always,
);
AttrValue::LengthPercentage(value.to_string(), val.ok())
},
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
if let Some(super_type) = self.super_type() {
super_type.children_changed(cx, mutation);
}
self.invalidate_cached_serialized_subtree();
}
fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext<'_>) {
if let Some(s) = self.super_type() {
s.unbind_from_tree(cx, context);
}
let owner_window = self.owner_window();
self.owner_window()
.image_cache()
.evict_rasterized_image(&self.uuid);
let data_url = self.cached_serialized_data_url.borrow().clone();
if let Some(Ok(url)) = data_url {
owner_window.layout_mut().remove_cached_image(&url);
owner_window.image_cache().evict_completed_image(
&url,
owner_window.origin().immutable(),
&None,
);
}
self.invalidate_cached_serialized_subtree();
}
}