mirror of
https://github.com/servo/servo.git
synced 2025-09-27 15:20:09 +01:00
Additionally, several methods were updated with spec comments. That's because the "adopt the document from the element document" step was missing. By adding these spec comments, I also restructured some code to avoid duplication of mutation records and custom element reaction queueing. Node.textContent doesn't propagate the error yet, as that method has a lot of separate callers of elements that wouldn't fail. I will refactor those in a follow-up PR to keep things manageable. This implements part of the DOM integration from https://github.com/whatwg/dom/pull/1268 Part of #36258 --------- Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com> Signed-off-by: Tim van der Lippe <TimvdLippe@users.noreply.github.com>
333 lines
11 KiB
Rust
333 lines
11 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::mem;
|
||
use std::sync::LazyLock;
|
||
|
||
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::codegen::UnionTypes::TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString;
|
||
use crate::dom::bindings::error::Fallible;
|
||
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
|
||
use crate::dom::bindings::str::DOMString;
|
||
use crate::dom::document::Document;
|
||
use crate::dom::element::Element;
|
||
use crate::dom::node::{Node, NodeTraits};
|
||
use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
|
||
use crate::script_runtime::CanGc;
|
||
|
||
// 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/#set-an-existing-attribute-value>
|
||
fn SetValue(&self, value: DOMString, can_gc: CanGc) -> Fallible<()> {
|
||
// Step 2. Otherwise:
|
||
if let Some(owner) = self.owner() {
|
||
// Step 2.1. Let originalElement be attribute’s element.
|
||
let original_element = owner.clone();
|
||
// Step 2.2. Let verifiedValue be the result of calling
|
||
// get Trusted Types-compliant attribute value with attribute’s local name,
|
||
// attribute’s namespace, this, and value. [TRUSTED-TYPES]
|
||
let value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
|
||
owner.namespace(),
|
||
owner.local_name(),
|
||
self.local_name(),
|
||
Some(self.namespace()),
|
||
TrustedTypeOrString::String(value),
|
||
&owner.owner_global(),
|
||
can_gc,
|
||
)?;
|
||
if let Some(owner) = self.owner() {
|
||
// Step 2.4. If attribute’s element is not originalElement, then return.
|
||
if owner != original_element {
|
||
return Ok(());
|
||
}
|
||
// Step 2.5. Change attribute to verifiedValue.
|
||
let value = owner.parse_attribute(self.namespace(), self.local_name(), value);
|
||
owner.change_attribute(self, value, can_gc);
|
||
} else {
|
||
// Step 2.3. If attribute’s element is null, then set attribute’s value to verifiedValue, and return.
|
||
self.set_value(value);
|
||
}
|
||
} else {
|
||
// Step 1. If attribute’s element is null, then set attribute’s value to value.
|
||
self.set_value(value);
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
// 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 {
|
||
/// 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()
|
||
}
|
||
|
||
fn set_value(&self, value: DOMString) {
|
||
*self.value.borrow_mut() = AttrValue::String(value.into());
|
||
}
|
||
|
||
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"))
|
||
}
|
||
|
||
/// A help function to check if an attribute is a boolean attribute.
|
||
pub(crate) fn is_boolean_attribute(name: &str) -> bool {
|
||
// The full list of attributes can be found in [1]. All attributes marked as "Boolean
|
||
// attribute" in the "Value" column are boolean attributes. Note that "hidden" is effectively
|
||
// treated as a boolean attribute, according to WPT test "test_global_boolean_attributes" in
|
||
// webdriver/tests/classic/get_element_attribute/get.py
|
||
//
|
||
// [1] <https://html.spec.whatwg.org/multipage/#attributes-3>
|
||
static BOOLEAN_ATTRIBUTES: LazyLock<[&str; 30]> = LazyLock::new(|| {
|
||
[
|
||
"allowfullscreen",
|
||
"alpha",
|
||
"async",
|
||
"autofocus",
|
||
"autoplay",
|
||
"checked",
|
||
"controls",
|
||
"default",
|
||
"defer",
|
||
"disabled",
|
||
"formnovalidate",
|
||
"hidden",
|
||
"inert",
|
||
"ismap",
|
||
"itemscope",
|
||
"loop",
|
||
"multiple",
|
||
"muted",
|
||
"nomodule",
|
||
"novalidate",
|
||
"open",
|
||
"playsinline",
|
||
"readonly",
|
||
"required",
|
||
"reversed",
|
||
"selected",
|
||
"shadowrootclonable",
|
||
"shadowrootcustomelementregistry",
|
||
"shadowrootdelegatesfocus",
|
||
"shadowrootserializable",
|
||
]
|
||
});
|
||
|
||
BOOLEAN_ATTRIBUTES
|
||
.iter()
|
||
.any(|&boolean_attr| boolean_attr.eq_ignore_ascii_case(name))
|
||
}
|