tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }