servo/components/script/dom/attr.rs
Andrei Volykhin 6468734aea
svg: Add mock SVGImageElement interface (#36975)
Add mock SVGImageElement interface to fix TIMEOUT WPT tests
which are related to ImageBitmap (html/canvas/*).
https://svgwg.org/svg2-draft/embedded.html#InterfaceSVGImageElement

Rationality of this change to fire event "error" on any attempt to fetch
image resource on href attribute change to not block WPT tests
execution.

Some WPT tests use the legacy namespace attribute "xlink:href", so
support for it was added to source code.
https://svgwg.org/svg2-draft/linking.html#XLinkHrefAttribute
 - setAttributeNS("http://www.w3.org/1999/xlink", 'xlink:href', src);

Testing: Covered by existed WPT tests
 - fetch/metadata/generated/svg-image*
 - html/canvas/element/manual/*
 - html/dom/idlharness.https.html
 - html/semantics/embedded-content/the-canvas-element/*
 - html/webappapis/scripting/events/event-handler-all-global-events.html
 - mozilla/interfaces.https.html

Fixes: https://github.com/servo/servo/issues/35881

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
2025-05-13 10:43:10 +00:00

291 lines
9 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 std::borrow::ToOwned;
use std::cell::LazyCell;
use std::mem;
use devtools_traits::AttrInfo;
use dom_struct::dom_struct;
use html5ever::{LocalName, Namespace, Prefix, local_name, ns};
use style::attr::{AttrIdentifier, AttrValue};
use style::values::GenericAtomIdent;
use stylo_atoms::Atom;
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::customelementregistry::CallbackReaction;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::mutationobserver::{Mutation, MutationObserver};
use crate::dom::node::Node;
use crate::dom::virtualmethods::vtable_for;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
// https://dom.spec.whatwg.org/#interface-attr
#[dom_struct]
pub(crate) struct Attr {
node_: Node,
#[no_trace]
identifier: AttrIdentifier,
#[no_trace]
value: DomRefCell<AttrValue>,
/// the element that owns this attribute.
owner: MutNullableDom<Element>,
}
impl Attr {
fn new_inherited(
document: &Document,
local_name: LocalName,
value: AttrValue,
name: LocalName,
namespace: Namespace,
prefix: Option<Prefix>,
owner: Option<&Element>,
) -> Attr {
Attr {
node_: Node::new_inherited(document),
identifier: AttrIdentifier {
local_name: GenericAtomIdent(local_name),
name: GenericAtomIdent(name),
namespace: GenericAtomIdent(namespace),
prefix: prefix.map(GenericAtomIdent),
},
value: DomRefCell::new(value),
owner: MutNullableDom::new(owner),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
document: &Document,
local_name: LocalName,
value: AttrValue,
name: LocalName,
namespace: Namespace,
prefix: Option<Prefix>,
owner: Option<&Element>,
can_gc: CanGc,
) -> DomRoot<Attr> {
Node::reflect_node(
Box::new(Attr::new_inherited(
document, local_name, value, name, namespace, prefix, owner,
)),
document,
can_gc,
)
}
#[inline]
pub(crate) fn name(&self) -> &LocalName {
&self.identifier.name.0
}
#[inline]
pub(crate) fn namespace(&self) -> &Namespace {
&self.identifier.namespace.0
}
#[inline]
pub(crate) fn prefix(&self) -> Option<&Prefix> {
Some(&self.identifier.prefix.as_ref()?.0)
}
}
impl AttrMethods<crate::DomTypeHolder> for Attr {
// https://dom.spec.whatwg.org/#dom-attr-localname
fn LocalName(&self) -> DOMString {
// FIXME(ajeffrey): convert directly from LocalName to DOMString
DOMString::from(&**self.local_name())
}
// https://dom.spec.whatwg.org/#dom-attr-value
fn Value(&self) -> DOMString {
// FIXME(ajeffrey): convert directly from AttrValue to DOMString
DOMString::from(&**self.value())
}
// https://dom.spec.whatwg.org/#dom-attr-value
fn SetValue(&self, value: DOMString, can_gc: CanGc) {
if let Some(owner) = self.owner() {
let value = owner.parse_attribute(self.namespace(), self.local_name(), value);
self.set_value(value, &owner, can_gc);
} else {
*self.value.borrow_mut() = AttrValue::String(value.into());
}
}
// https://dom.spec.whatwg.org/#dom-attr-name
fn Name(&self) -> DOMString {
// FIXME(ajeffrey): convert directly from LocalName to DOMString
DOMString::from(&**self.name())
}
// https://dom.spec.whatwg.org/#dom-attr-namespaceuri
fn GetNamespaceURI(&self) -> Option<DOMString> {
match *self.namespace() {
ns!() => None,
ref url => Some(DOMString::from(&**url)),
}
}
// https://dom.spec.whatwg.org/#dom-attr-prefix
fn GetPrefix(&self) -> Option<DOMString> {
// FIXME(ajeffrey): convert directly from LocalName to DOMString
self.prefix().map(|p| DOMString::from(&**p))
}
// https://dom.spec.whatwg.org/#dom-attr-ownerelement
fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
self.owner()
}
// https://dom.spec.whatwg.org/#dom-attr-specified
fn Specified(&self) -> bool {
true // Always returns true
}
}
impl Attr {
pub(crate) fn set_value(&self, mut value: AttrValue, owner: &Element, can_gc: CanGc) {
let name = self.local_name().clone();
let namespace = self.namespace().clone();
let old_value = DOMString::from(&**self.value());
let new_value = DOMString::from(&*value);
let mutation = LazyCell::new(|| Mutation::Attribute {
name: name.clone(),
namespace: namespace.clone(),
old_value: Some(old_value.clone()),
});
MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation);
if owner.is_custom() {
let reaction = CallbackReaction::AttributeChanged(
name,
Some(old_value),
Some(new_value),
namespace,
);
ScriptThread::enqueue_callback_reaction(owner, reaction, None);
}
assert_eq!(Some(owner), self.owner().as_deref());
owner.will_mutate_attr(self);
self.swap_value(&mut value);
if is_relevant_attribute(self.namespace(), self.local_name()) {
vtable_for(owner.upcast()).attribute_mutated(
self,
AttributeMutation::Set(Some(&value)),
can_gc,
);
}
}
/// Used to swap the attribute's value without triggering mutation events
pub(crate) fn swap_value(&self, value: &mut AttrValue) {
mem::swap(&mut *self.value.borrow_mut(), value);
}
pub(crate) fn identifier(&self) -> &AttrIdentifier {
&self.identifier
}
pub(crate) fn value(&self) -> Ref<AttrValue> {
self.value.borrow()
}
pub(crate) fn local_name(&self) -> &LocalName {
&self.identifier.local_name
}
/// Sets the owner element. Should be called after the attribute is added
/// or removed from its older parent.
pub(crate) fn set_owner(&self, owner: Option<&Element>) {
let ns = self.namespace();
match (self.owner(), owner) {
(Some(old), None) => {
// Already gone from the list of attributes of old owner.
assert!(
old.get_attribute(ns, &self.identifier.local_name)
.as_deref() !=
Some(self)
)
},
(Some(old), Some(new)) => assert_eq!(&*old, new),
_ => {},
}
self.owner.set(owner);
}
pub(crate) fn owner(&self) -> Option<DomRoot<Element>> {
self.owner.get()
}
pub(crate) fn summarize(&self) -> AttrInfo {
AttrInfo {
namespace: (**self.namespace()).to_owned(),
name: String::from(self.Name()),
value: String::from(self.Value()),
}
}
pub(crate) fn qualified_name(&self) -> DOMString {
match self.prefix() {
Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())),
None => DOMString::from(&**self.local_name()),
}
}
}
#[allow(unsafe_code)]
pub(crate) trait AttrHelpersForLayout<'dom> {
fn value(self) -> &'dom AttrValue;
fn as_str(&self) -> &'dom str;
fn to_tokens(self) -> Option<&'dom [Atom]>;
fn local_name(self) -> &'dom LocalName;
fn namespace(self) -> &'dom Namespace;
}
#[allow(unsafe_code)]
impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> {
#[inline]
fn value(self) -> &'dom AttrValue {
unsafe { self.unsafe_get().value.borrow_for_layout() }
}
#[inline]
fn as_str(&self) -> &'dom str {
self.value()
}
#[inline]
fn to_tokens(self) -> Option<&'dom [Atom]> {
match *self.value() {
AttrValue::TokenList(_, ref tokens) => Some(tokens),
_ => None,
}
}
#[inline]
fn local_name(self) -> &'dom LocalName {
&self.unsafe_get().identifier.local_name.0
}
#[inline]
fn namespace(self) -> &'dom Namespace {
&self.unsafe_get().identifier.namespace.0
}
}
/// A helper function to check if attribute is relevant.
pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool {
// <https://svgwg.org/svg2-draft/linking.html#XLinkHrefAttribute>
namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href"))
}