style/
dom_apis.rs

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//! Generic implementations of some DOM APIs so they can be shared between Servo
6//! and Gecko.
7
8use crate::context::QuirksMode;
9use crate::dom::{TDocument, TElement, TNode, TShadowRoot};
10use crate::invalidation::element::invalidation_map::Dependency;
11use crate::invalidation::element::invalidator::{
12    DescendantInvalidationLists, Invalidation, SiblingTraversalMap,
13};
14use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
15use crate::selector_parser::SelectorImpl;
16use crate::values::AtomIdent;
17use selectors::attr::CaseSensitivity;
18use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
19use selectors::matching::{
20    self, MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags,
21    SelectorCaches,
22};
23use selectors::parser::{Combinator, Component, LocalName};
24use selectors::{Element, OpaqueElement, SelectorList};
25use smallvec::SmallVec;
26
27/// <https://dom.spec.whatwg.org/#dom-element-matches>
28pub fn element_matches<E>(
29    element: &E,
30    selector_list: &SelectorList<E::Impl>,
31    quirks_mode: QuirksMode,
32) -> bool
33where
34    E: Element,
35{
36    let mut selector_caches = SelectorCaches::default();
37
38    let mut context = MatchingContext::new(
39        MatchingMode::Normal,
40        None,
41        &mut selector_caches,
42        quirks_mode,
43        NeedsSelectorFlags::No,
44        MatchingForInvalidation::No,
45    );
46    context.scope_element = Some(element.opaque());
47    context.current_host = element.containing_shadow_host().map(|e| e.opaque());
48    matching::matches_selector_list(selector_list, element, &mut context)
49}
50
51/// <https://dom.spec.whatwg.org/#dom-element-closest>
52pub fn element_closest<E>(
53    element: E,
54    selector_list: &SelectorList<E::Impl>,
55    quirks_mode: QuirksMode,
56) -> Option<E>
57where
58    E: Element,
59{
60    let mut selector_caches = SelectorCaches::default();
61
62    let mut context = MatchingContext::new(
63        MatchingMode::Normal,
64        None,
65        &mut selector_caches,
66        quirks_mode,
67        NeedsSelectorFlags::No,
68        MatchingForInvalidation::No,
69    );
70    context.scope_element = Some(element.opaque());
71    context.current_host = element.containing_shadow_host().map(|e| e.opaque());
72
73    let mut current = Some(element);
74    while let Some(element) = current.take() {
75        if matching::matches_selector_list(selector_list, &element, &mut context) {
76            return Some(element);
77        }
78        current = element.parent_element();
79    }
80
81    return None;
82}
83
84/// A selector query abstraction, in order to be generic over QuerySelector and
85/// QuerySelectorAll.
86pub trait SelectorQuery<E: TElement> {
87    /// The output of the query.
88    type Output;
89
90    /// Whether the query should stop after the first element has been matched.
91    fn should_stop_after_first_match() -> bool;
92
93    /// Append an element matching after the first query.
94    fn append_element(output: &mut Self::Output, element: E);
95
96    /// Returns true if the output is empty.
97    fn is_empty(output: &Self::Output) -> bool;
98}
99
100/// The result of a querySelectorAll call.
101pub type QuerySelectorAllResult<E> = SmallVec<[E; 128]>;
102
103/// A query for all the elements in a subtree.
104pub struct QueryAll;
105
106impl<E: TElement> SelectorQuery<E> for QueryAll {
107    type Output = QuerySelectorAllResult<E>;
108
109    fn should_stop_after_first_match() -> bool {
110        false
111    }
112
113    fn append_element(output: &mut Self::Output, element: E) {
114        output.push(element);
115    }
116
117    fn is_empty(output: &Self::Output) -> bool {
118        output.is_empty()
119    }
120}
121
122/// A query for the first in-tree match of all the elements in a subtree.
123pub struct QueryFirst;
124
125impl<E: TElement> SelectorQuery<E> for QueryFirst {
126    type Output = Option<E>;
127
128    fn should_stop_after_first_match() -> bool {
129        true
130    }
131
132    fn append_element(output: &mut Self::Output, element: E) {
133        if output.is_none() {
134            *output = Some(element)
135        }
136    }
137
138    fn is_empty(output: &Self::Output) -> bool {
139        output.is_none()
140    }
141}
142
143struct QuerySelectorProcessor<'a, 'b, E, Q>
144where
145    E: TElement + 'a,
146    Q: SelectorQuery<E>,
147    Q::Output: 'a,
148{
149    results: &'a mut Q::Output,
150    matching_context: MatchingContext<'b, E::Impl>,
151    traversal_map: SiblingTraversalMap<E>,
152    dependencies: &'a [Dependency],
153}
154
155impl<'a, 'b, E, Q> InvalidationProcessor<'a, 'b, E> for QuerySelectorProcessor<'a, 'b, E, Q>
156where
157    E: TElement + 'a,
158    Q: SelectorQuery<E>,
159    Q::Output: 'a,
160{
161    fn light_tree_only(&self) -> bool {
162        true
163    }
164
165    fn check_outer_dependency(&mut self, _: &Dependency, _: E, _:Option<OpaqueElement>) -> bool {
166        debug_assert!(
167            false,
168            "How? We should only have parent-less dependencies here!"
169        );
170        true
171    }
172
173    fn collect_invalidations(
174        &mut self,
175        element: E,
176        self_invalidations: &mut InvalidationVector<'a>,
177        descendant_invalidations: &mut DescendantInvalidationLists<'a>,
178        _sibling_invalidations: &mut InvalidationVector<'a>,
179    ) -> bool {
180        // TODO(emilio): If the element is not a root element, and
181        // selector_list has any descendant combinator, we need to do extra work
182        // in order to handle properly things like:
183        //
184        //   <div id="a">
185        //     <div id="b">
186        //       <div id="c"></div>
187        //     </div>
188        //   </div>
189        //
190        // b.querySelector('#a div'); // Should return "c".
191        //
192        // For now, assert it's a root element.
193        debug_assert!(element.parent_element().is_none());
194
195        let target_vector = if self.matching_context.scope_element.is_some() {
196            &mut descendant_invalidations.dom_descendants
197        } else {
198            self_invalidations
199        };
200
201        for dependency in self.dependencies.iter() {
202            target_vector.push(Invalidation::new(
203                dependency,
204                self.matching_context.current_host.clone(),
205                self.matching_context.scope_element.clone(),
206            ))
207        }
208
209        false
210    }
211
212    fn matching_context(&mut self) -> &mut MatchingContext<'b, E::Impl> {
213        &mut self.matching_context
214    }
215
216    fn sibling_traversal_map(&self) -> &SiblingTraversalMap<E> {
217        &self.traversal_map
218    }
219
220    fn should_process_descendants(&mut self, _: E) -> bool {
221        if Q::should_stop_after_first_match() {
222            return Q::is_empty(&self.results);
223        }
224
225        true
226    }
227
228    fn invalidated_self(&mut self, e: E) {
229        Q::append_element(self.results, e);
230    }
231
232    fn invalidated_sibling(&mut self, e: E, _of: E) {
233        Q::append_element(self.results, e);
234    }
235
236    fn recursion_limit_exceeded(&mut self, _e: E) {}
237    fn invalidated_descendants(&mut self, _e: E, _child: E) {}
238}
239
240fn collect_all_elements<E, Q, F>(root: E::ConcreteNode, results: &mut Q::Output, mut filter: F)
241where
242    E: TElement,
243    Q: SelectorQuery<E>,
244    F: FnMut(E) -> bool,
245{
246    for node in root.dom_descendants() {
247        let element = match node.as_element() {
248            Some(e) => e,
249            None => continue,
250        };
251
252        if !filter(element) {
253            continue;
254        }
255
256        Q::append_element(results, element);
257        if Q::should_stop_after_first_match() {
258            return;
259        }
260    }
261}
262
263/// Returns whether a given element connected to `root` is descendant of `root`.
264///
265/// NOTE(emilio): if root == element, this returns false.
266fn connected_element_is_descendant_of<E>(element: E, root: E::ConcreteNode) -> bool
267where
268    E: TElement,
269{
270    // Optimize for when the root is a document or a shadow root and the element
271    // is connected to that root.
272    if root.as_document().is_some() {
273        debug_assert!(element.as_node().is_in_document(), "Not connected?");
274        debug_assert_eq!(
275            root,
276            root.owner_doc().as_node(),
277            "Where did this element come from?",
278        );
279        return true;
280    }
281
282    if root.as_shadow_root().is_some() {
283        debug_assert_eq!(
284            element.containing_shadow().unwrap().as_node(),
285            root,
286            "Not connected?"
287        );
288        return true;
289    }
290
291    let mut current = element.as_node().parent_node();
292    while let Some(n) = current.take() {
293        if n == root {
294            return true;
295        }
296
297        current = n.parent_node();
298    }
299    false
300}
301
302/// Fast path for iterating over every element with a given id in the document
303/// or shadow root that `root` is connected to.
304fn fast_connected_elements_with_id<'a, N>(
305    root: N,
306    id: &AtomIdent,
307    case_sensitivity: CaseSensitivity,
308) -> Result<&'a [N::ConcreteElement], ()>
309where
310    N: TNode + 'a,
311{
312    if case_sensitivity != CaseSensitivity::CaseSensitive {
313        return Err(());
314    }
315
316    if root.is_in_document() {
317        return root.owner_doc().elements_with_id(id);
318    }
319
320    if let Some(shadow) = root.as_shadow_root() {
321        return shadow.elements_with_id(id);
322    }
323
324    if let Some(shadow) = root.as_element().and_then(|e| e.containing_shadow()) {
325        return shadow.elements_with_id(id);
326    }
327
328    Err(())
329}
330
331/// Collects elements with a given id under `root`, that pass `filter`.
332fn collect_elements_with_id<E, Q, F>(
333    root: E::ConcreteNode,
334    id: &AtomIdent,
335    results: &mut Q::Output,
336    class_and_id_case_sensitivity: CaseSensitivity,
337    mut filter: F,
338) where
339    E: TElement,
340    Q: SelectorQuery<E>,
341    F: FnMut(E) -> bool,
342{
343    let elements = match fast_connected_elements_with_id(root, id, class_and_id_case_sensitivity) {
344        Ok(elements) => elements,
345        Err(()) => {
346            collect_all_elements::<E, Q, _>(root, results, |e| {
347                e.has_id(id, class_and_id_case_sensitivity) && filter(e)
348            });
349
350            return;
351        },
352    };
353
354    for element in elements {
355        // If the element is not an actual descendant of the root, even though
356        // it's connected, we don't really care about it.
357        if !connected_element_is_descendant_of(*element, root) {
358            continue;
359        }
360
361        if !filter(*element) {
362            continue;
363        }
364
365        Q::append_element(results, *element);
366        if Q::should_stop_after_first_match() {
367            break;
368        }
369    }
370}
371
372fn has_attr<E>(element: E, local_name: &crate::LocalName) -> bool
373where
374    E: TElement,
375{
376    let mut found = false;
377    element.each_attr_name(|name| found |= name == local_name);
378    found
379}
380
381#[inline(always)]
382fn local_name_matches<E>(element: E, local_name: &LocalName<E::Impl>) -> bool
383where
384    E: TElement,
385{
386    let LocalName {
387        ref name,
388        ref lower_name,
389    } = *local_name;
390
391    let chosen_name = if name == lower_name || element.is_html_element_in_html_document() {
392        lower_name
393    } else {
394        name
395    };
396
397    element.local_name() == &**chosen_name
398}
399
400fn get_attr_name(component: &Component<SelectorImpl>) -> Option<&crate::LocalName> {
401    let (name, name_lower) = match component {
402        Component::AttributeInNoNamespace { ref local_name, .. } => return Some(local_name),
403        Component::AttributeInNoNamespaceExists {
404            ref local_name,
405            ref local_name_lower,
406            ..
407        } => (local_name, local_name_lower),
408        Component::AttributeOther(ref attr) => (&attr.local_name, &attr.local_name_lower),
409        _ => return None,
410    };
411    if name != name_lower {
412        return None; // TODO: Maybe optimize this?
413    }
414    Some(name)
415}
416
417fn get_id(component: &Component<SelectorImpl>) -> Option<&AtomIdent> {
418    use selectors::attr::AttrSelectorOperator;
419    Some(match component {
420        Component::ID(ref id) => id,
421        Component::AttributeInNoNamespace {
422            ref operator,
423            ref local_name,
424            ref value,
425            ..
426        } => {
427            if *local_name != local_name!("id") {
428                return None;
429            }
430            if *operator != AttrSelectorOperator::Equal {
431                return None;
432            }
433            AtomIdent::cast(&value.0)
434        },
435        _ => return None,
436    })
437}
438
439/// Fast paths for querySelector with a single simple selector.
440fn query_selector_single_query<E, Q>(
441    root: E::ConcreteNode,
442    component: &Component<E::Impl>,
443    results: &mut Q::Output,
444    class_and_id_case_sensitivity: CaseSensitivity,
445) -> Result<(), ()>
446where
447    E: TElement,
448    Q: SelectorQuery<E>,
449{
450    match *component {
451        Component::ExplicitUniversalType => {
452            collect_all_elements::<E, Q, _>(root, results, |_| true)
453        },
454        Component::Class(ref class) => collect_all_elements::<E, Q, _>(root, results, |element| {
455            element.has_class(class, class_and_id_case_sensitivity)
456        }),
457        Component::LocalName(ref local_name) => {
458            collect_all_elements::<E, Q, _>(root, results, |element| {
459                local_name_matches(element, local_name)
460            })
461        },
462        Component::AttributeInNoNamespaceExists {
463            ref local_name,
464            ref local_name_lower,
465        } => collect_all_elements::<E, Q, _>(root, results, |element| {
466            element.has_attr_in_no_namespace(matching::select_name(
467                &element,
468                local_name,
469                local_name_lower,
470            ))
471        }),
472        Component::AttributeInNoNamespace {
473            ref local_name,
474            ref value,
475            operator,
476            case_sensitivity,
477        } => {
478            let empty_namespace = selectors::parser::namespace_empty_string::<E::Impl>();
479            let namespace_constraint = NamespaceConstraint::Specific(&empty_namespace);
480            collect_all_elements::<E, Q, _>(root, results, |element| {
481                element.attr_matches(
482                    &namespace_constraint,
483                    local_name,
484                    &AttrSelectorOperation::WithValue {
485                        operator,
486                        case_sensitivity: matching::to_unconditional_case_sensitivity(
487                            case_sensitivity,
488                            &element,
489                        ),
490                        value,
491                    },
492                )
493            })
494        },
495        ref other => {
496            let id = match get_id(other) {
497                Some(id) => id,
498                // TODO(emilio): More fast paths?
499                None => return Err(()),
500            };
501            collect_elements_with_id::<E, Q, _>(
502                root,
503                id,
504                results,
505                class_and_id_case_sensitivity,
506                |_| true,
507            );
508        },
509    }
510
511    Ok(())
512}
513
514enum SimpleFilter<'a> {
515    Class(&'a AtomIdent),
516    Attr(&'a crate::LocalName),
517    LocalName(&'a LocalName<SelectorImpl>),
518}
519
520/// Fast paths for a given selector query.
521///
522/// When there's only one component, we go directly to
523/// `query_selector_single_query`, otherwise, we try to optimize by looking just
524/// at the subtrees rooted at ids in the selector, and otherwise we try to look
525/// up by class name or local name in the rightmost compound.
526///
527/// FIXME(emilio, nbp): This may very well be a good candidate for code to be
528/// replaced by HolyJit :)
529fn query_selector_fast<E, Q>(
530    root: E::ConcreteNode,
531    selector_list: &SelectorList<E::Impl>,
532    results: &mut Q::Output,
533    matching_context: &mut MatchingContext<E::Impl>,
534) -> Result<(), ()>
535where
536    E: TElement,
537    Q: SelectorQuery<E>,
538{
539    // We need to return elements in document order, and reordering them
540    // afterwards is kinda silly.
541    if selector_list.len() > 1 {
542        return Err(());
543    }
544
545    let selector = &selector_list.slice()[0];
546    let class_and_id_case_sensitivity = matching_context.classes_and_ids_case_sensitivity();
547    // Let's just care about the easy cases for now.
548    if selector.len() == 1 {
549        if query_selector_single_query::<E, Q>(
550            root,
551            selector.iter().next().unwrap(),
552            results,
553            class_and_id_case_sensitivity,
554        )
555        .is_ok()
556        {
557            return Ok(());
558        }
559    }
560
561    let mut iter = selector.iter();
562    let mut combinator: Option<Combinator> = None;
563
564    // We want to optimize some cases where there's no id involved whatsoever,
565    // like `.foo .bar`, but we don't want to make `#foo .bar` slower because of
566    // that.
567    let mut simple_filter = None;
568
569    'selector_loop: loop {
570        debug_assert!(combinator.map_or(true, |c| !c.is_sibling()));
571
572        'component_loop: for component in &mut iter {
573            match *component {
574                Component::Class(ref class) => {
575                    if combinator.is_none() {
576                        simple_filter = Some(SimpleFilter::Class(class));
577                    }
578                },
579                Component::LocalName(ref local_name) => {
580                    if combinator.is_none() {
581                        // Prefer to look at class rather than local-name if
582                        // both are present.
583                        if let Some(SimpleFilter::Class(..)) = simple_filter {
584                            continue;
585                        }
586                        simple_filter = Some(SimpleFilter::LocalName(local_name));
587                    }
588                },
589                ref other => {
590                    if let Some(id) = get_id(other) {
591                        if combinator.is_none() {
592                            // In the rightmost compound, just find descendants of root that match
593                            // the selector list with that id.
594                            collect_elements_with_id::<E, Q, _>(
595                                root,
596                                id,
597                                results,
598                                class_and_id_case_sensitivity,
599                                |e| {
600                                    matching::matches_selector_list(
601                                        selector_list,
602                                        &e,
603                                        matching_context,
604                                    )
605                                },
606                            );
607                            return Ok(());
608                        }
609
610                        let elements = fast_connected_elements_with_id(
611                            root,
612                            id,
613                            class_and_id_case_sensitivity,
614                        )?;
615                        if elements.is_empty() {
616                            return Ok(());
617                        }
618
619                        // Results need to be in document order. Let's not bother
620                        // reordering or deduplicating nodes, which we would need to
621                        // do if one element with the given id were a descendant of
622                        // another element with that given id.
623                        if !Q::should_stop_after_first_match() && elements.len() > 1 {
624                            continue;
625                        }
626
627                        for element in elements {
628                            // If the element is not a descendant of the root, then
629                            // it may have descendants that match our selector that
630                            // _are_ descendants of the root, and other descendants
631                            // that match our selector that are _not_.
632                            //
633                            // So we can't just walk over the element's descendants
634                            // and match the selector against all of them, nor can
635                            // we skip looking at this element's descendants.
636                            //
637                            // Give up on trying to optimize based on this id and
638                            // keep walking our selector.
639                            if !connected_element_is_descendant_of(*element, root) {
640                                continue 'component_loop;
641                            }
642
643                            query_selector_slow::<E, Q>(
644                                element.as_node(),
645                                selector_list,
646                                results,
647                                matching_context,
648                            );
649
650                            if Q::should_stop_after_first_match() && !Q::is_empty(&results) {
651                                break;
652                            }
653                        }
654
655                        return Ok(());
656                    }
657                    if combinator.is_none() && simple_filter.is_none() {
658                        if let Some(attr_name) = get_attr_name(other) {
659                            simple_filter = Some(SimpleFilter::Attr(attr_name));
660                        }
661                    }
662                },
663            }
664        }
665
666        loop {
667            let next_combinator = match iter.next_sequence() {
668                None => break 'selector_loop,
669                Some(c) => c,
670            };
671
672            // We don't want to scan stuff affected by sibling combinators,
673            // given we scan the subtree of elements with a given id (and we
674            // don't want to care about scanning the siblings' subtrees).
675            if next_combinator.is_sibling() {
676                // Advance to the next combinator.
677                for _ in &mut iter {}
678                continue;
679            }
680
681            combinator = Some(next_combinator);
682            break;
683        }
684    }
685
686    // We got here without finding any ID or such that we could handle. Try to
687    // use one of the simple filters.
688    let simple_filter = match simple_filter {
689        Some(f) => f,
690        None => return Err(()),
691    };
692
693    match simple_filter {
694        SimpleFilter::Class(ref class) => {
695            collect_all_elements::<E, Q, _>(root, results, |element| {
696                element.has_class(class, class_and_id_case_sensitivity)
697                    && matching::matches_selector_list(selector_list, &element, matching_context)
698            });
699        },
700        SimpleFilter::LocalName(ref local_name) => {
701            collect_all_elements::<E, Q, _>(root, results, |element| {
702                local_name_matches(element, local_name)
703                    && matching::matches_selector_list(selector_list, &element, matching_context)
704            });
705        },
706        SimpleFilter::Attr(ref local_name) => {
707            collect_all_elements::<E, Q, _>(root, results, |element| {
708                has_attr(element, local_name)
709                    && matching::matches_selector_list(selector_list, &element, matching_context)
710            });
711        },
712    }
713
714    Ok(())
715}
716
717// Slow path for a given selector query.
718fn query_selector_slow<E, Q>(
719    root: E::ConcreteNode,
720    selector_list: &SelectorList<E::Impl>,
721    results: &mut Q::Output,
722    matching_context: &mut MatchingContext<E::Impl>,
723) where
724    E: TElement,
725    Q: SelectorQuery<E>,
726{
727    collect_all_elements::<E, Q, _>(root, results, |element| {
728        matching::matches_selector_list(selector_list, &element, matching_context)
729    });
730}
731
732/// Whether the invalidation machinery should be used for this query.
733#[derive(PartialEq)]
734pub enum MayUseInvalidation {
735    /// We may use it if we deem it useful.
736    Yes,
737    /// Don't use it.
738    No,
739}
740
741/// <https://dom.spec.whatwg.org/#dom-parentnode-queryselector>
742pub fn query_selector<E, Q>(
743    root: E::ConcreteNode,
744    selector_list: &SelectorList<E::Impl>,
745    results: &mut Q::Output,
746    may_use_invalidation: MayUseInvalidation,
747) where
748    E: TElement,
749    Q: SelectorQuery<E>,
750{
751    use crate::invalidation::element::invalidator::TreeStyleInvalidator;
752
753    let mut selector_caches = SelectorCaches::default();
754    let quirks_mode = root.owner_doc().quirks_mode();
755
756    let mut matching_context = MatchingContext::new(
757        MatchingMode::Normal,
758        None,
759        &mut selector_caches,
760        quirks_mode,
761        NeedsSelectorFlags::No,
762        MatchingForInvalidation::No,
763    );
764    let root_element = root.as_element();
765    matching_context.scope_element = root_element.map(|e| e.opaque());
766    matching_context.current_host = match root_element {
767        Some(root) => root.containing_shadow_host().map(|host| host.opaque()),
768        None => root.as_shadow_root().map(|root| root.host().opaque()),
769    };
770
771    let fast_result =
772        query_selector_fast::<E, Q>(root, selector_list, results, &mut matching_context);
773
774    if fast_result.is_ok() {
775        return;
776    }
777
778    // Slow path: Use the invalidation machinery if we're a root, and tree
779    // traversal otherwise.
780    //
781    // See the comment in collect_invalidations to see why only if we're a root.
782    //
783    // The invalidation mechanism is only useful in presence of combinators.
784    //
785    // We could do that check properly here, though checking the length of the
786    // selectors is a good heuristic.
787    //
788    // A selector with a combinator needs to have a length of at least 3: A
789    // simple selector, a combinator, and another simple selector.
790    let invalidation_may_be_useful = may_use_invalidation == MayUseInvalidation::Yes
791        && selector_list.slice().iter().any(|s| s.len() > 2);
792
793    if root_element.is_some() || !invalidation_may_be_useful {
794        query_selector_slow::<E, Q>(root, selector_list, results, &mut matching_context);
795    } else {
796        let dependencies = selector_list
797            .slice()
798            .iter()
799            .map(|selector| Dependency::for_full_selector_invalidation(selector.clone()))
800            .collect::<SmallVec<[_; 5]>>();
801        let mut processor = QuerySelectorProcessor::<E, Q> {
802            results,
803            matching_context,
804            traversal_map: SiblingTraversalMap::default(),
805            dependencies: &dependencies,
806        };
807
808        for node in root.dom_children() {
809            if let Some(e) = node.as_element() {
810                TreeStyleInvalidator::new(e, /* stack_limit_checker = */ None, &mut processor)
811                    .invalidate();
812            }
813        }
814    }
815}