mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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>
291 lines
9 KiB
Rust
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"))
|
|
}
|