snapshot_helpers.rs (10386B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Element an snapshot common logic. 6 7 use crate::dom::TElement; 8 use crate::gecko::wrapper::namespace_id_to_atom; 9 use crate::gecko_bindings::bindings; 10 use crate::gecko_bindings::structs::{self, nsAtom}; 11 use crate::invalidation::element::element_wrapper::ElementSnapshot; 12 use crate::selector_parser::{AttrValue, SnapshotMap}; 13 use crate::string_cache::WeakAtom; 14 use crate::values::AtomIdent; 15 use crate::{Atom, CaseSensitivityExt, LocalName, Namespace}; 16 use selectors::attr::{ 17 AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint, 18 }; 19 use smallvec::SmallVec; 20 21 /// A function that, given an element of type `T`, allows you to get a single 22 /// class or a class list. 23 enum Class<'a> { 24 None, 25 One(*const nsAtom), 26 More(&'a [structs::RefPtr<nsAtom>]), 27 } 28 29 #[inline(always)] 30 fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType { 31 (attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType 32 } 33 34 #[inline(always)] 35 unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T { 36 (attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T 37 } 38 39 #[inline(always)] 40 unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class<'_> { 41 debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr)); 42 let base_type = base_type(attr); 43 if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase { 44 return Class::One(ptr::<nsAtom>(attr)); 45 } 46 if base_type == structs::nsAttrValue_ValueBaseType_eOtherBase { 47 let container = ptr::<structs::MiscContainer>(attr); 48 debug_assert_eq!( 49 (*container).mType, 50 structs::nsAttrValue_ValueType_eAtomArray 51 ); 52 // NOTE: Bindgen doesn't deal with AutoTArray, so cast it below. 53 let attr_array: *const _ = *(*container) 54 .__bindgen_anon_1 55 .mValue 56 .as_ref() 57 .__bindgen_anon_1 58 .mAtomArray 59 .as_ref(); 60 let array = 61 (*attr_array).mArray.0.as_ptr() as *const structs::nsTArray<structs::RefPtr<nsAtom>>; 62 return Class::More(&**array); 63 } 64 debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase); 65 Class::None 66 } 67 68 #[inline(always)] 69 unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom { 70 debug_assert_eq!( 71 base_type(attr), 72 structs::nsAttrValue_ValueBaseType_eAtomBase 73 ); 74 WeakAtom::new(ptr::<nsAtom>(attr)) 75 } 76 77 impl structs::nsAttrName { 78 #[inline] 79 fn is_nodeinfo(&self) -> bool { 80 self.mBits & 1 != 0 81 } 82 83 #[inline] 84 unsafe fn as_nodeinfo(&self) -> &structs::NodeInfo { 85 debug_assert!(self.is_nodeinfo()); 86 &*((self.mBits & !1) as *const structs::NodeInfo) 87 } 88 89 #[inline] 90 fn namespace_id(&self) -> i32 { 91 if !self.is_nodeinfo() { 92 return structs::kNameSpaceID_None; 93 } 94 unsafe { self.as_nodeinfo() }.mInner.mNamespaceID 95 } 96 97 /// Returns the attribute name as an atom pointer. 98 #[inline] 99 pub fn name(&self) -> *const nsAtom { 100 if self.is_nodeinfo() { 101 unsafe { self.as_nodeinfo() }.mInner.mName 102 } else { 103 self.mBits as *const nsAtom 104 } 105 } 106 } 107 108 /// Find an attribute value with a given name and no namespace. 109 #[inline(always)] 110 pub fn find_attr<'a>( 111 attrs: &'a [structs::AttrArray_InternalAttr], 112 name: &Atom, 113 ) -> Option<&'a structs::nsAttrValue> { 114 attrs 115 .iter() 116 .find(|attr| attr.mName.mBits == name.as_ptr() as usize) 117 .map(|attr| &attr.mValue) 118 } 119 120 /// Finds the id attribute from a list of attributes. 121 #[inline(always)] 122 pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> { 123 Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) }) 124 } 125 126 #[inline(always)] 127 pub(super) fn each_exported_part( 128 attrs: &[structs::AttrArray_InternalAttr], 129 name: &AtomIdent, 130 mut callback: impl FnMut(&AtomIdent), 131 ) { 132 let attr = match find_attr(attrs, &atom!("exportparts")) { 133 Some(attr) => attr, 134 None => return, 135 }; 136 let mut length = 0; 137 let atoms = unsafe { bindings::Gecko_Element_ExportedParts(attr, name.as_ptr(), &mut length) }; 138 if atoms.is_null() { 139 return; 140 } 141 142 unsafe { 143 for atom in std::slice::from_raw_parts(atoms, length) { 144 AtomIdent::with(*atom, &mut callback) 145 } 146 } 147 } 148 149 #[inline(always)] 150 pub(super) fn imported_part( 151 attrs: &[structs::AttrArray_InternalAttr], 152 name: &AtomIdent, 153 ) -> Option<AtomIdent> { 154 let attr = find_attr(attrs, &atom!("exportparts"))?; 155 let atom = unsafe { bindings::Gecko_Element_ImportedPart(attr, name.as_ptr()) }; 156 if atom.is_null() { 157 return None; 158 } 159 Some(AtomIdent(unsafe { Atom::from_raw(atom) })) 160 } 161 162 /// Given a class or part name, a case sensitivity, and an array of attributes, 163 /// returns whether the attribute has that name. 164 #[inline(always)] 165 pub fn has_class_or_part( 166 name: &AtomIdent, 167 case_sensitivity: CaseSensitivity, 168 attr: &structs::nsAttrValue, 169 ) -> bool { 170 match unsafe { get_class_or_part_from_attr(attr) } { 171 Class::None => false, 172 Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) }, 173 Class::More(atoms) => match case_sensitivity { 174 CaseSensitivity::CaseSensitive => { 175 let name_ptr = name.as_ptr(); 176 atoms.iter().any(|atom| atom.mRawPtr == name_ptr) 177 }, 178 CaseSensitivity::AsciiCaseInsensitive => unsafe { 179 atoms 180 .iter() 181 .any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name)) 182 }, 183 }, 184 } 185 } 186 187 /// Given an item, a callback, and a getter, execute `callback` for each class 188 /// or part name this `item` has. 189 #[inline(always)] 190 pub fn each_class_or_part<F>(attr: &structs::nsAttrValue, mut callback: F) 191 where 192 F: FnMut(&AtomIdent), 193 { 194 unsafe { 195 match get_class_or_part_from_attr(attr) { 196 Class::None => {}, 197 Class::One(atom) => AtomIdent::with(atom, callback), 198 Class::More(atoms) => { 199 for atom in atoms { 200 AtomIdent::with(atom.mRawPtr, &mut callback) 201 } 202 }, 203 } 204 } 205 } 206 207 /// Returns a list of classes that were either added to or removed from the 208 /// element since the snapshot. 209 pub fn classes_changed<E: TElement>(element: &E, snapshots: &SnapshotMap) -> SmallVec<[Atom; 8]> { 210 debug_assert!(element.has_snapshot(), "Why bothering?"); 211 let snapshot = snapshots.get(element).expect("has_snapshot lied"); 212 if !snapshot.class_changed() { 213 return SmallVec::new(); 214 } 215 216 let mut classes_changed = SmallVec::<[Atom; 8]>::new(); 217 snapshot.each_class(|c| { 218 if !element.has_class(c, CaseSensitivity::CaseSensitive) { 219 classes_changed.push(c.0.clone()); 220 } 221 }); 222 element.each_class(|c| { 223 if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) { 224 classes_changed.push(c.0.clone()); 225 } 226 }); 227 228 classes_changed 229 } 230 231 /// Returns whether a given attribute selector matches given the internal attrs. 232 #[inline(always)] 233 pub(crate) fn attr_matches( 234 attrs: &[structs::AttrArray_InternalAttr], 235 ns: &NamespaceConstraint<&Namespace>, 236 local_name: &LocalName, 237 operation: &AttrSelectorOperation<&AttrValue>, 238 ) -> bool { 239 let name_ptr = local_name.as_ptr(); 240 for attr in attrs { 241 if attr.mName.name() != name_ptr { 242 continue; 243 } 244 245 if attr_matches_checked_name(attr, ns, operation) { 246 return true; 247 } 248 249 // The name matched but the value or namespace didn't. The only reason to check the other 250 // attributes now would be to find one with the same name but a different namespace. 251 if *ns != NamespaceConstraint::Any { 252 // We don't want to look for other namespaces, so we're done. 253 return false; 254 } 255 } 256 false 257 } 258 259 /// Returns whether a given attribute selector matches given a single attribute, 260 /// for the case where the caller has already found an attribute with the right name. 261 fn attr_matches_checked_name( 262 attr: &structs::AttrArray_InternalAttr, 263 ns: &NamespaceConstraint<&Namespace>, 264 operation: &AttrSelectorOperation<&AttrValue>, 265 ) -> bool { 266 let ns_matches = match *ns { 267 NamespaceConstraint::Any => true, 268 NamespaceConstraint::Specific(ns) => { 269 if *ns == ns!() { 270 !attr.mName.is_nodeinfo() 271 } else { 272 ns.as_ptr() == unsafe { namespace_id_to_atom(attr.mName.namespace_id()) } 273 } 274 }, 275 }; 276 277 if !ns_matches { 278 return false; 279 } 280 281 let (operator, case_sensitivity, value) = match *operation { 282 AttrSelectorOperation::Exists => return true, 283 AttrSelectorOperation::WithValue { 284 operator, 285 case_sensitivity, 286 value, 287 } => (operator, case_sensitivity, value), 288 }; 289 let ignore_case = match case_sensitivity { 290 CaseSensitivity::CaseSensitive => false, 291 CaseSensitivity::AsciiCaseInsensitive => true, 292 }; 293 let value = value.as_ptr(); 294 unsafe { 295 match operator { 296 AttrSelectorOperator::Equal => { 297 bindings::Gecko_AttrEquals(&attr.mValue, value, ignore_case) 298 }, 299 AttrSelectorOperator::Includes => { 300 bindings::Gecko_AttrIncludes(&attr.mValue, value, ignore_case) 301 }, 302 AttrSelectorOperator::DashMatch => { 303 bindings::Gecko_AttrDashEquals(&attr.mValue, value, ignore_case) 304 }, 305 AttrSelectorOperator::Prefix => { 306 bindings::Gecko_AttrHasPrefix(&attr.mValue, value, ignore_case) 307 }, 308 AttrSelectorOperator::Suffix => { 309 bindings::Gecko_AttrHasSuffix(&attr.mValue, value, ignore_case) 310 }, 311 AttrSelectorOperator::Substring => { 312 bindings::Gecko_AttrHasSubstring(&attr.mValue, value, ignore_case) 313 }, 314 } 315 } 316 }