316 lines
10 KiB
Rust
316 lines
10 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/. */
|
|
|
|
//! Element an snapshot common logic.
|
|
|
|
use crate::dom::TElement;
|
|
use crate::gecko::wrapper::namespace_id_to_atom;
|
|
use crate::gecko_bindings::bindings;
|
|
use crate::gecko_bindings::structs::{self, nsAtom};
|
|
use crate::invalidation::element::element_wrapper::ElementSnapshot;
|
|
use crate::selector_parser::{AttrValue, SnapshotMap};
|
|
use crate::string_cache::WeakAtom;
|
|
use crate::values::AtomIdent;
|
|
use crate::{Atom, CaseSensitivityExt, LocalName, Namespace};
|
|
use selectors::attr::{
|
|
AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint,
|
|
};
|
|
use smallvec::SmallVec;
|
|
|
|
/// A function that, given an element of type `T`, allows you to get a single
|
|
/// class or a class list.
|
|
enum Class<'a> {
|
|
None,
|
|
One(*const nsAtom),
|
|
More(&'a [structs::RefPtr<nsAtom>]),
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType {
|
|
(attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType
|
|
}
|
|
|
|
#[inline(always)]
|
|
unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T {
|
|
(attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T
|
|
}
|
|
|
|
#[inline(always)]
|
|
unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
|
|
debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr));
|
|
let base_type = base_type(attr);
|
|
if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase {
|
|
return Class::One(ptr::<nsAtom>(attr));
|
|
}
|
|
if base_type == structs::nsAttrValue_ValueBaseType_eOtherBase {
|
|
let container = ptr::<structs::MiscContainer>(attr);
|
|
debug_assert_eq!(
|
|
(*container).mType,
|
|
structs::nsAttrValue_ValueType_eAtomArray
|
|
);
|
|
// NOTE: Bindgen doesn't deal with AutoTArray, so cast it below.
|
|
let attr_array: *const _ = *(*container)
|
|
.__bindgen_anon_1
|
|
.mValue
|
|
.as_ref()
|
|
.__bindgen_anon_1
|
|
.mAtomArray
|
|
.as_ref();
|
|
let array =
|
|
(*attr_array).mArray.as_ptr() as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
|
|
return Class::More(&**array);
|
|
}
|
|
debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);
|
|
Class::None
|
|
}
|
|
|
|
#[inline(always)]
|
|
unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom {
|
|
debug_assert_eq!(
|
|
base_type(attr),
|
|
structs::nsAttrValue_ValueBaseType_eAtomBase
|
|
);
|
|
WeakAtom::new(ptr::<nsAtom>(attr))
|
|
}
|
|
|
|
impl structs::nsAttrName {
|
|
#[inline]
|
|
fn is_nodeinfo(&self) -> bool {
|
|
self.mBits & 1 != 0
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn as_nodeinfo(&self) -> &structs::NodeInfo {
|
|
debug_assert!(self.is_nodeinfo());
|
|
&*((self.mBits & !1) as *const structs::NodeInfo)
|
|
}
|
|
|
|
#[inline]
|
|
fn namespace_id(&self) -> i32 {
|
|
if !self.is_nodeinfo() {
|
|
return structs::kNameSpaceID_None;
|
|
}
|
|
unsafe { self.as_nodeinfo() }.mInner.mNamespaceID
|
|
}
|
|
|
|
/// Returns the attribute name as an atom pointer.
|
|
#[inline]
|
|
pub fn name(&self) -> *const nsAtom {
|
|
if self.is_nodeinfo() {
|
|
unsafe { self.as_nodeinfo() }.mInner.mName
|
|
} else {
|
|
self.mBits as *const nsAtom
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Find an attribute value with a given name and no namespace.
|
|
#[inline(always)]
|
|
pub fn find_attr<'a>(
|
|
attrs: &'a [structs::AttrArray_InternalAttr],
|
|
name: &Atom,
|
|
) -> Option<&'a structs::nsAttrValue> {
|
|
attrs
|
|
.iter()
|
|
.find(|attr| attr.mName.mBits == name.as_ptr() as usize)
|
|
.map(|attr| &attr.mValue)
|
|
}
|
|
|
|
/// Finds the id attribute from a list of attributes.
|
|
#[inline(always)]
|
|
pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> {
|
|
Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) })
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(super) fn each_exported_part(
|
|
attrs: &[structs::AttrArray_InternalAttr],
|
|
name: &AtomIdent,
|
|
mut callback: impl FnMut(&AtomIdent),
|
|
) {
|
|
let attr = match find_attr(attrs, &atom!("exportparts")) {
|
|
Some(attr) => attr,
|
|
None => return,
|
|
};
|
|
let mut length = 0;
|
|
let atoms = unsafe { bindings::Gecko_Element_ExportedParts(attr, name.as_ptr(), &mut length) };
|
|
if atoms.is_null() {
|
|
return;
|
|
}
|
|
|
|
unsafe {
|
|
for atom in std::slice::from_raw_parts(atoms, length) {
|
|
AtomIdent::with(*atom, &mut callback)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(super) fn imported_part(
|
|
attrs: &[structs::AttrArray_InternalAttr],
|
|
name: &AtomIdent,
|
|
) -> Option<AtomIdent> {
|
|
let attr = find_attr(attrs, &atom!("exportparts"))?;
|
|
let atom = unsafe { bindings::Gecko_Element_ImportedPart(attr, name.as_ptr()) };
|
|
if atom.is_null() {
|
|
return None;
|
|
}
|
|
Some(AtomIdent(unsafe { Atom::from_raw(atom) }))
|
|
}
|
|
|
|
/// Given a class or part name, a case sensitivity, and an array of attributes,
|
|
/// returns whether the attribute has that name.
|
|
#[inline(always)]
|
|
pub fn has_class_or_part(
|
|
name: &AtomIdent,
|
|
case_sensitivity: CaseSensitivity,
|
|
attr: &structs::nsAttrValue,
|
|
) -> bool {
|
|
match unsafe { get_class_or_part_from_attr(attr) } {
|
|
Class::None => false,
|
|
Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) },
|
|
Class::More(atoms) => match case_sensitivity {
|
|
CaseSensitivity::CaseSensitive => {
|
|
let name_ptr = name.as_ptr();
|
|
atoms.iter().any(|atom| atom.mRawPtr == name_ptr)
|
|
},
|
|
CaseSensitivity::AsciiCaseInsensitive => unsafe {
|
|
atoms
|
|
.iter()
|
|
.any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name))
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Given an item, a callback, and a getter, execute `callback` for each class
|
|
/// or part name this `item` has.
|
|
#[inline(always)]
|
|
pub fn each_class_or_part<F>(attr: &structs::nsAttrValue, mut callback: F)
|
|
where
|
|
F: FnMut(&AtomIdent),
|
|
{
|
|
unsafe {
|
|
match get_class_or_part_from_attr(attr) {
|
|
Class::None => {},
|
|
Class::One(atom) => AtomIdent::with(atom, callback),
|
|
Class::More(atoms) => {
|
|
for atom in atoms {
|
|
AtomIdent::with(atom.mRawPtr, &mut callback)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a list of classes that were either added to or removed from the
|
|
/// element since the snapshot.
|
|
pub fn classes_changed<E: TElement>(element: &E, snapshots: &SnapshotMap) -> SmallVec<[Atom; 8]> {
|
|
debug_assert!(element.has_snapshot(), "Why bothering?");
|
|
let snapshot = snapshots.get(element).expect("has_snapshot lied");
|
|
if !snapshot.class_changed() {
|
|
return SmallVec::new();
|
|
}
|
|
|
|
let mut classes_changed = SmallVec::<[Atom; 8]>::new();
|
|
snapshot.each_class(|c| {
|
|
if !element.has_class(c, CaseSensitivity::CaseSensitive) {
|
|
classes_changed.push(c.0.clone());
|
|
}
|
|
});
|
|
element.each_class(|c| {
|
|
if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) {
|
|
classes_changed.push(c.0.clone());
|
|
}
|
|
});
|
|
|
|
classes_changed
|
|
}
|
|
|
|
/// Returns whether a given attribute selector matches given the internal attrs.
|
|
#[inline(always)]
|
|
pub(crate) fn attr_matches(
|
|
attrs: &[structs::AttrArray_InternalAttr],
|
|
ns: &NamespaceConstraint<&Namespace>,
|
|
local_name: &LocalName,
|
|
operation: &AttrSelectorOperation<&AttrValue>,
|
|
) -> bool {
|
|
let name_ptr = local_name.as_ptr();
|
|
for attr in attrs {
|
|
if attr.mName.name() != name_ptr {
|
|
continue;
|
|
}
|
|
|
|
if attr_matches_checked_name(attr, ns, operation) {
|
|
return true;
|
|
}
|
|
|
|
// The name matched but the value or namespace didn't. The only reason to check the other
|
|
// attributes now would be to find one with the same name but a different namespace.
|
|
if *ns != NamespaceConstraint::Any {
|
|
// We don't want to look for other namespaces, so we're done.
|
|
return false;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Returns whether a given attribute selector matches given a single attribute,
|
|
/// for the case where the caller has already found an attribute with the right name.
|
|
fn attr_matches_checked_name(
|
|
attr: &structs::AttrArray_InternalAttr,
|
|
ns: &NamespaceConstraint<&Namespace>,
|
|
operation: &AttrSelectorOperation<&AttrValue>,
|
|
) -> bool {
|
|
let ns_matches = match *ns {
|
|
NamespaceConstraint::Any => true,
|
|
NamespaceConstraint::Specific(ns) => {
|
|
if *ns == ns!() {
|
|
!attr.mName.is_nodeinfo()
|
|
} else {
|
|
ns.as_ptr() == unsafe { namespace_id_to_atom(attr.mName.namespace_id()) }
|
|
}
|
|
},
|
|
};
|
|
|
|
if !ns_matches {
|
|
return false;
|
|
}
|
|
|
|
let (operator, case_sensitivity, value) = match *operation {
|
|
AttrSelectorOperation::Exists => return true,
|
|
AttrSelectorOperation::WithValue {
|
|
operator,
|
|
case_sensitivity,
|
|
value,
|
|
} => (operator, case_sensitivity, value),
|
|
};
|
|
let ignore_case = match case_sensitivity {
|
|
CaseSensitivity::CaseSensitive => false,
|
|
CaseSensitivity::AsciiCaseInsensitive => true,
|
|
};
|
|
let value = value.as_ptr();
|
|
unsafe {
|
|
match operator {
|
|
AttrSelectorOperator::Equal => {
|
|
bindings::Gecko_AttrEquals(&attr.mValue, value, ignore_case)
|
|
},
|
|
AttrSelectorOperator::Includes => {
|
|
bindings::Gecko_AttrIncludes(&attr.mValue, value, ignore_case)
|
|
},
|
|
AttrSelectorOperator::DashMatch => {
|
|
bindings::Gecko_AttrDashEquals(&attr.mValue, value, ignore_case)
|
|
},
|
|
AttrSelectorOperator::Prefix => {
|
|
bindings::Gecko_AttrHasPrefix(&attr.mValue, value, ignore_case)
|
|
},
|
|
AttrSelectorOperator::Suffix => {
|
|
bindings::Gecko_AttrHasSuffix(&attr.mValue, value, ignore_case)
|
|
},
|
|
AttrSelectorOperator::Substring => {
|
|
bindings::Gecko_AttrHasSubstring(&attr.mValue, value, ignore_case)
|
|
},
|
|
}
|
|
}
|
|
}
|