summaryrefslogtreecommitdiffstats
path: root/servo/components/style/gecko/snapshot_helpers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/gecko/snapshot_helpers.rs')
-rw-r--r--servo/components/style/gecko/snapshot_helpers.rs316
1 files changed, 316 insertions, 0 deletions
diff --git a/servo/components/style/gecko/snapshot_helpers.rs b/servo/components/style/gecko/snapshot_helpers.rs
new file mode 100644
index 0000000000..ab2d08eaf8
--- /dev/null
+++ b/servo/components/style/gecko/snapshot_helpers.rs
@@ -0,0 +1,316 @@
+/* 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)
+ },
+ }
+ }
+}