script/dom/
customelementregistry.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
5use std::cell::Cell;
6use std::collections::VecDeque;
7use std::ffi::CStr;
8use std::rc::Rc;
9use std::{mem, ptr};
10
11use dom_struct::dom_struct;
12use html5ever::{LocalName, Namespace, Prefix, ns};
13use js::glue::UnwrapObjectStatic;
14use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor, JSAutoRealm, JSObject};
15use js::jsval::{BooleanValue, JSVal, NullValue, ObjectValue, UndefinedValue};
16use js::rust::wrappers::{Construct1, JS_GetProperty, SameValue};
17use js::rust::{HandleObject, MutableHandleValue};
18use rustc_hash::FxBuildHasher;
19use script_bindings::conversions::{SafeFromJSValConvertible, SafeToJSValConvertible};
20
21use super::bindings::trace::HashMapTracedValues;
22use crate::dom::bindings::callback::{CallbackContainer, ExceptionHandling};
23use crate::dom::bindings::cell::DomRefCell;
24use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::{
25    CustomElementConstructor, CustomElementRegistryMethods, ElementDefinitionOptions,
26};
27use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
28use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
29use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
30use crate::dom::bindings::conversions::{ConversionResult, StringificationBehavior};
31use crate::dom::bindings::error::{
32    Error, ErrorResult, Fallible, report_pending_exception, throw_dom_exception,
33};
34use crate::dom::bindings::inheritance::Castable;
35use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector, reflect_dom_object};
36use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot};
37use crate::dom::bindings::settings_stack::is_execution_stack_empty;
38use crate::dom::bindings::str::DOMString;
39use crate::dom::document::Document;
40use crate::dom::domexception::{DOMErrorName, DOMException};
41use crate::dom::element::Element;
42use crate::dom::globalscope::GlobalScope;
43use crate::dom::html::htmlelement::HTMLElement;
44use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
45use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
46use crate::dom::promise::Promise;
47use crate::dom::window::Window;
48use crate::microtask::Microtask;
49use crate::realms::{InRealm, enter_realm};
50use crate::script_runtime::{CanGc, JSContext};
51use crate::script_thread::ScriptThread;
52
53/// <https://dom.spec.whatwg.org/#concept-element-custom-element-state>
54#[derive(Clone, Copy, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
55pub(crate) enum CustomElementState {
56    Undefined,
57    Failed,
58    #[default]
59    Uncustomized,
60    Precustomized,
61    Custom,
62}
63
64/// <https://html.spec.whatwg.org/multipage/#customelementregistry>
65#[dom_struct]
66pub(crate) struct CustomElementRegistry {
67    reflector_: Reflector,
68
69    window: Dom<Window>,
70
71    #[ignore_malloc_size_of = "Rc"]
72    /// It is safe to use FxBuildHasher here as `LocalName` is an `Atom` in the string_cache.
73    /// These get a u32 hashed instead of a string.
74    when_defined: DomRefCell<HashMapTracedValues<LocalName, Rc<Promise>, FxBuildHasher>>,
75
76    element_definition_is_running: Cell<bool>,
77
78    #[ignore_malloc_size_of = "Rc"]
79    definitions:
80        DomRefCell<HashMapTracedValues<LocalName, Rc<CustomElementDefinition>, FxBuildHasher>>,
81}
82
83impl CustomElementRegistry {
84    fn new_inherited(window: &Window) -> CustomElementRegistry {
85        CustomElementRegistry {
86            reflector_: Reflector::new(),
87            window: Dom::from_ref(window),
88            when_defined: DomRefCell::new(HashMapTracedValues::new_fx()),
89            element_definition_is_running: Cell::new(false),
90            definitions: DomRefCell::new(HashMapTracedValues::new_fx()),
91        }
92    }
93
94    pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<CustomElementRegistry> {
95        reflect_dom_object(
96            Box::new(CustomElementRegistry::new_inherited(window)),
97            window,
98            can_gc,
99        )
100    }
101
102    /// Cleans up any active promises
103    /// <https://github.com/servo/servo/issues/15318>
104    pub(crate) fn teardown(&self) {
105        self.when_defined.borrow_mut().0.clear()
106    }
107
108    /// <https://html.spec.whatwg.org/multipage/#look-up-a-custom-element-definition>
109    pub(crate) fn lookup_definition(
110        &self,
111        local_name: &LocalName,
112        is: Option<&LocalName>,
113    ) -> Option<Rc<CustomElementDefinition>> {
114        self.definitions
115            .borrow()
116            .0
117            .values()
118            .find(|definition| {
119                // Step 4-5
120                definition.local_name == *local_name &&
121                    (definition.name == *local_name || Some(&definition.name) == is)
122            })
123            .cloned()
124    }
125
126    pub(crate) fn lookup_definition_by_constructor(
127        &self,
128        constructor: HandleObject,
129    ) -> Option<Rc<CustomElementDefinition>> {
130        self.definitions
131            .borrow()
132            .0
133            .values()
134            .find(|definition| definition.constructor.callback() == constructor.get())
135            .cloned()
136    }
137
138    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
139    /// Steps 10.1, 10.2
140    #[allow(unsafe_code)]
141    fn check_prototype(
142        &self,
143        constructor: HandleObject,
144        mut prototype: MutableHandleValue,
145    ) -> ErrorResult {
146        unsafe {
147            // Step 10.1
148            if !JS_GetProperty(
149                *GlobalScope::get_cx(),
150                constructor,
151                c"prototype".as_ptr(),
152                prototype.reborrow(),
153            ) {
154                return Err(Error::JSFailed);
155            }
156
157            // Step 10.2
158            if !prototype.is_object() {
159                return Err(Error::Type(
160                    "constructor.prototype is not an object".to_owned(),
161                ));
162            }
163        }
164        Ok(())
165    }
166
167    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
168    /// This function includes both steps 14.3 and 14.4 which add the callbacks to a map and
169    /// process them.
170    #[allow(unsafe_code)]
171    unsafe fn get_callbacks(&self, prototype: HandleObject) -> Fallible<LifecycleCallbacks> {
172        let cx = GlobalScope::get_cx();
173
174        // Step 4
175        Ok(LifecycleCallbacks {
176            connected_callback: get_callback(cx, prototype, c"connectedCallback")?,
177            disconnected_callback: get_callback(cx, prototype, c"disconnectedCallback")?,
178            adopted_callback: get_callback(cx, prototype, c"adoptedCallback")?,
179            attribute_changed_callback: get_callback(cx, prototype, c"attributeChangedCallback")?,
180
181            form_associated_callback: None,
182            form_disabled_callback: None,
183            form_reset_callback: None,
184            form_state_restore_callback: None,
185        })
186    }
187
188    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
189    /// Step 14.13: Add form associated callbacks to LifecycleCallbacks
190    #[allow(unsafe_code)]
191    unsafe fn add_form_associated_callbacks(
192        &self,
193        prototype: HandleObject,
194        callbacks: &mut LifecycleCallbacks,
195    ) -> ErrorResult {
196        let cx = self.window.get_cx();
197
198        callbacks.form_associated_callback =
199            get_callback(cx, prototype, c"formAssociatedCallback")?;
200        callbacks.form_reset_callback = get_callback(cx, prototype, c"formResetCallback")?;
201        callbacks.form_disabled_callback = get_callback(cx, prototype, c"formDisabledCallback")?;
202        callbacks.form_state_restore_callback =
203            get_callback(cx, prototype, c"formStateRestoreCallback")?;
204
205        Ok(())
206    }
207
208    #[allow(unsafe_code)]
209    fn get_observed_attributes(&self, constructor: HandleObject) -> Fallible<Vec<DOMString>> {
210        let cx = GlobalScope::get_cx();
211        rooted!(in(*cx) let mut observed_attributes = UndefinedValue());
212        if unsafe {
213            !JS_GetProperty(
214                *cx,
215                constructor,
216                c"observedAttributes".as_ptr(),
217                observed_attributes.handle_mut(),
218            )
219        } {
220            return Err(Error::JSFailed);
221        }
222
223        if observed_attributes.is_undefined() {
224            return Ok(Vec::new());
225        }
226
227        let conversion = SafeFromJSValConvertible::safe_from_jsval(
228            cx,
229            observed_attributes.handle(),
230            StringificationBehavior::Default,
231        );
232        match conversion {
233            Ok(ConversionResult::Success(attributes)) => Ok(attributes),
234            Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())),
235            _ => Err(Error::JSFailed),
236        }
237    }
238
239    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
240    /// Step 14.11: Get the value of `formAssociated`.
241    #[allow(unsafe_code)]
242    fn get_form_associated_value(&self, constructor: HandleObject) -> Fallible<bool> {
243        let cx = self.window.get_cx();
244        rooted!(in(*cx) let mut form_associated_value = UndefinedValue());
245        if unsafe {
246            !JS_GetProperty(
247                *cx,
248                constructor,
249                c"formAssociated".as_ptr(),
250                form_associated_value.handle_mut(),
251            )
252        } {
253            return Err(Error::JSFailed);
254        }
255
256        if form_associated_value.is_undefined() {
257            return Ok(false);
258        }
259
260        let conversion =
261            SafeFromJSValConvertible::safe_from_jsval(cx, form_associated_value.handle(), ());
262        match conversion {
263            Ok(ConversionResult::Success(flag)) => Ok(flag),
264            Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())),
265            _ => Err(Error::JSFailed),
266        }
267    }
268
269    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
270    /// Step 14.7: Get `disabledFeatures` value
271    #[allow(unsafe_code)]
272    fn get_disabled_features(&self, constructor: HandleObject) -> Fallible<Vec<DOMString>> {
273        let cx = self.window.get_cx();
274        rooted!(in(*cx) let mut disabled_features = UndefinedValue());
275        if unsafe {
276            !JS_GetProperty(
277                *cx,
278                constructor,
279                c"disabledFeatures".as_ptr(),
280                disabled_features.handle_mut(),
281            )
282        } {
283            return Err(Error::JSFailed);
284        }
285
286        if disabled_features.is_undefined() {
287            return Ok(Vec::new());
288        }
289
290        let conversion = SafeFromJSValConvertible::safe_from_jsval(
291            cx,
292            disabled_features.handle(),
293            StringificationBehavior::Default,
294        );
295        match conversion {
296            Ok(ConversionResult::Success(attributes)) => Ok(attributes),
297            Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())),
298            _ => Err(Error::JSFailed),
299        }
300    }
301}
302
303/// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
304/// Step 14.4: Get `callbackValue` for all `callbackName` in `lifecycleCallbacks`.
305#[allow(unsafe_code)]
306fn get_callback(
307    cx: JSContext,
308    prototype: HandleObject,
309    name: &CStr,
310) -> Fallible<Option<Rc<Function>>> {
311    rooted!(in(*cx) let mut callback = UndefinedValue());
312    unsafe {
313        // Step 10.4.1
314        if !JS_GetProperty(
315            *cx,
316            prototype,
317            name.as_ptr() as *const _,
318            callback.handle_mut(),
319        ) {
320            return Err(Error::JSFailed);
321        }
322
323        // Step 10.4.2
324        if !callback.is_undefined() {
325            if !callback.is_object() || !IsCallable(callback.to_object()) {
326                return Err(Error::Type("Lifecycle callback is not callable".to_owned()));
327            }
328            Ok(Some(Function::new(cx, callback.to_object())))
329        } else {
330            Ok(None)
331        }
332    }
333}
334
335impl CustomElementRegistryMethods<crate::DomTypeHolder> for CustomElementRegistry {
336    #[allow(unsafe_code)]
337    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
338    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define>
339    fn Define(
340        &self,
341        name: DOMString,
342        constructor_: Rc<CustomElementConstructor>,
343        options: &ElementDefinitionOptions,
344        can_gc: CanGc,
345    ) -> ErrorResult {
346        let cx = GlobalScope::get_cx();
347        rooted!(in(*cx) let constructor = constructor_.callback());
348        let name = LocalName::from(&*name);
349
350        // Step 1. If IsConstructor(constructor) is false, then throw a TypeError.
351        // We must unwrap the constructor as all wrappers are constructable if they are callable.
352        rooted!(in(*cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) });
353
354        if unwrapped_constructor.is_null() {
355            // We do not have permission to access the unwrapped constructor.
356            return Err(Error::Security);
357        }
358
359        if unsafe { !IsConstructor(unwrapped_constructor.get()) } {
360            return Err(Error::Type(
361                "Second argument of CustomElementRegistry.define is not a constructor".to_owned(),
362            ));
363        }
364
365        // Step 2. If name is not a valid custom element name, then throw a "SyntaxError" DOMException.
366        if !is_valid_custom_element_name(&name) {
367            return Err(Error::Syntax(None));
368        }
369
370        // Step 3. If this's custom element definition set contains an item with name name,
371        // then throw a "NotSupportedError" DOMException.
372        if self.definitions.borrow().contains_key(&name) {
373            return Err(Error::NotSupported);
374        }
375
376        // Step 4. If this's custom element definition set contains an
377        // item with constructor constructor, then throw a "NotSupportedError" DOMException.
378        if self
379            .definitions
380            .borrow()
381            .iter()
382            .any(|(_, def)| def.constructor == constructor_)
383        {
384            return Err(Error::NotSupported);
385        }
386
387        // Step 6. Let extends be options["extends"] if it exists; otherwise null.
388        let extends = &options.extends;
389
390        // Steps 5, 7
391        let local_name = if let Some(ref extended_name) = *extends {
392            // TODO Step 7.1 If this's is scoped is true, then throw a "NotSupportedError" DOMException.
393
394            // Step 7.2 If extends is a valid custom element name, then throw a "NotSupportedError" DOMException.
395            if is_valid_custom_element_name(extended_name) {
396                return Err(Error::NotSupported);
397            }
398
399            // Step 7.3 If the element interface for extends and the HTML namespace is HTMLUnknownElement
400            // (e.g., if extends does not indicate an element definition in this specification)
401            // then throw a "NotSupportedError" DOMException.
402            if !is_extendable_element_interface(extended_name) {
403                return Err(Error::NotSupported);
404            }
405
406            // Step 7.4 Set localName to extends.
407            LocalName::from(&**extended_name)
408        } else {
409            // Step 5. Let localName be name.
410            name.clone()
411        };
412
413        // Step 8
414        if self.element_definition_is_running.get() {
415            return Err(Error::NotSupported);
416        }
417
418        // Step 9
419        self.element_definition_is_running.set(true);
420
421        // Steps 10-13: Initialize `formAssociated`, `disableInternals`, `disableShadow`, and
422        // `observedAttributes` with default values, but this is done later.
423
424        // Steps 14.1 - 14.2: Get the value of the prototype.
425        rooted!(in(*cx) let mut prototype = UndefinedValue());
426        {
427            let _ac = JSAutoRealm::new(*cx, constructor.get());
428            if let Err(error) = self.check_prototype(constructor.handle(), prototype.handle_mut()) {
429                self.element_definition_is_running.set(false);
430                return Err(error);
431            }
432        };
433
434        // Steps 10.3 - 10.4
435        // It would be easier to get all the callbacks in one pass after
436        // we know whether this definition is going to be form-associated,
437        // but the order of operations is specified and it's observable
438        // if one of the callback getters throws an exception.
439        rooted!(in(*cx) let proto_object = prototype.to_object());
440        let mut callbacks = {
441            let _ac = JSAutoRealm::new(*cx, proto_object.get());
442            let callbacks = unsafe { self.get_callbacks(proto_object.handle()) };
443            match callbacks {
444                Ok(callbacks) => callbacks,
445                Err(error) => {
446                    self.element_definition_is_running.set(false);
447                    return Err(error);
448                },
449            }
450        };
451
452        // Step 14.5: Handle the case where with `attributeChangedCallback` on `lifecycleCallbacks`
453        // is not null.
454        let observed_attributes = if callbacks.attribute_changed_callback.is_some() {
455            let _ac = JSAutoRealm::new(*cx, constructor.get());
456            match self.get_observed_attributes(constructor.handle()) {
457                Ok(attributes) => attributes,
458                Err(error) => {
459                    self.element_definition_is_running.set(false);
460                    return Err(error);
461                },
462            }
463        } else {
464            Vec::new()
465        };
466
467        // Steps 14.6 - 14.10: Handle `disabledFeatures`.
468        let (disable_internals, disable_shadow) = {
469            let _ac = JSAutoRealm::new(*cx, constructor.get());
470            match self.get_disabled_features(constructor.handle()) {
471                Ok(sequence) => (
472                    sequence.iter().any(|s| *s == "internals"),
473                    sequence.iter().any(|s| *s == "shadow"),
474                ),
475                Err(error) => {
476                    self.element_definition_is_running.set(false);
477                    return Err(error);
478                },
479            }
480        };
481
482        // Step 14.11 - 14.12: Handle `formAssociated`.
483        let form_associated = {
484            let _ac = JSAutoRealm::new(*cx, constructor.get());
485            match self.get_form_associated_value(constructor.handle()) {
486                Ok(flag) => flag,
487                Err(error) => {
488                    self.element_definition_is_running.set(false);
489                    return Err(error);
490                },
491            }
492        };
493
494        // Steps 14.13: Add the `formAssociated` callbacks.
495        if form_associated {
496            let _ac = JSAutoRealm::new(*cx, proto_object.get());
497            unsafe {
498                if let Err(error) =
499                    self.add_form_associated_callbacks(proto_object.handle(), &mut callbacks)
500                {
501                    self.element_definition_is_running.set(false);
502                    return Err(error);
503                }
504            }
505        }
506
507        self.element_definition_is_running.set(false);
508
509        // Step 15: Set up the new custom element definition.
510        let definition = Rc::new(CustomElementDefinition::new(
511            name.clone(),
512            local_name.clone(),
513            constructor_,
514            observed_attributes,
515            callbacks,
516            form_associated,
517            disable_internals,
518            disable_shadow,
519        ));
520
521        // Step 16: Add definition to this CustomElementRegistry.
522        self.definitions
523            .borrow_mut()
524            .insert(name.clone(), definition.clone());
525
526        // Step 17: Let document be this CustomElementRegistry's relevant global object's
527        // associated Document.
528        let document = self.window.Document();
529
530        // Steps 18-19: Enqueue custom elements upgrade reaction for upgrade candidates.
531        for candidate in document
532            .upcast::<Node>()
533            .traverse_preorder(ShadowIncluding::Yes)
534            .filter_map(DomRoot::downcast::<Element>)
535        {
536            let is = candidate.get_is();
537            if *candidate.local_name() == local_name &&
538                *candidate.namespace() == ns!(html) &&
539                (extends.is_none() || is.as_ref() == Some(&name))
540            {
541                ScriptThread::enqueue_upgrade_reaction(&candidate, definition.clone());
542            }
543        }
544
545        // Step 16, 16.3
546        let promise = self.when_defined.borrow_mut().remove(&name);
547        if let Some(promise) = promise {
548            rooted!(in(*cx) let mut constructor = UndefinedValue());
549            definition
550                .constructor
551                .safe_to_jsval(cx, constructor.handle_mut());
552            promise.resolve_native(&constructor.get(), can_gc);
553        }
554        Ok(())
555    }
556
557    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-get>
558    fn Get(&self, cx: JSContext, name: DOMString, mut retval: MutableHandleValue) {
559        match self.definitions.borrow().get(&LocalName::from(&*name)) {
560            Some(definition) => definition.constructor.safe_to_jsval(cx, retval),
561            None => retval.set(UndefinedValue()),
562        }
563    }
564
565    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-getname>
566    fn GetName(&self, constructor: Rc<CustomElementConstructor>) -> Option<DOMString> {
567        self.definitions
568            .borrow()
569            .0
570            .values()
571            .find(|definition| definition.constructor == constructor)
572            .map(|definition| DOMString::from(definition.name.to_string()))
573    }
574
575    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-whendefined>
576    fn WhenDefined(&self, name: DOMString, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
577        let name = LocalName::from(&*name);
578
579        // Step 1
580        if !is_valid_custom_element_name(&name) {
581            let promise = Promise::new_in_current_realm(comp, can_gc);
582            promise.reject_native(
583                &DOMException::new(
584                    self.window.as_global_scope(),
585                    DOMErrorName::SyntaxError,
586                    can_gc,
587                ),
588                can_gc,
589            );
590            return promise;
591        }
592
593        // Step 2
594        if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) {
595            let cx = GlobalScope::get_cx();
596            rooted!(in(*cx) let mut constructor = UndefinedValue());
597            definition
598                .constructor
599                .safe_to_jsval(cx, constructor.handle_mut());
600            let promise = Promise::new_in_current_realm(comp, can_gc);
601            promise.resolve_native(&constructor.get(), can_gc);
602            return promise;
603        }
604
605        // Steps 3, 4, 5, 6
606        let existing_promise = self.when_defined.borrow().get(&name).cloned();
607        existing_promise.unwrap_or_else(|| {
608            let promise = Promise::new_in_current_realm(comp, can_gc);
609            self.when_defined.borrow_mut().insert(name, promise.clone());
610            promise
611        })
612    }
613    /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-upgrade>
614    fn Upgrade(&self, node: &Node) {
615        // Spec says to make a list first and then iterate the list, but
616        // try-to-upgrade only queues upgrade reactions and doesn't itself
617        // modify the tree, so that's not an observable distinction.
618        node.traverse_preorder(ShadowIncluding::Yes).for_each(|n| {
619            if let Some(element) = n.downcast::<Element>() {
620                try_upgrade_element(element);
621            }
622        });
623    }
624}
625
626#[derive(Clone, JSTraceable, MallocSizeOf)]
627pub(crate) struct LifecycleCallbacks {
628    #[conditional_malloc_size_of]
629    connected_callback: Option<Rc<Function>>,
630
631    #[conditional_malloc_size_of]
632    disconnected_callback: Option<Rc<Function>>,
633
634    #[conditional_malloc_size_of]
635    adopted_callback: Option<Rc<Function>>,
636
637    #[conditional_malloc_size_of]
638    attribute_changed_callback: Option<Rc<Function>>,
639
640    #[conditional_malloc_size_of]
641    form_associated_callback: Option<Rc<Function>>,
642
643    #[conditional_malloc_size_of]
644    form_reset_callback: Option<Rc<Function>>,
645
646    #[conditional_malloc_size_of]
647    form_disabled_callback: Option<Rc<Function>>,
648
649    #[conditional_malloc_size_of]
650    form_state_restore_callback: Option<Rc<Function>>,
651}
652
653#[derive(Clone, JSTraceable, MallocSizeOf)]
654pub(crate) enum ConstructionStackEntry {
655    Element(DomRoot<Element>),
656    AlreadyConstructedMarker,
657}
658
659/// <https://html.spec.whatwg.org/multipage/#custom-element-definition>
660#[derive(Clone, JSTraceable, MallocSizeOf)]
661pub(crate) struct CustomElementDefinition {
662    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-name>
663    #[no_trace]
664    pub(crate) name: LocalName,
665
666    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-local-name>
667    #[no_trace]
668    pub(crate) local_name: LocalName,
669
670    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-constructor>
671    #[conditional_malloc_size_of]
672    pub(crate) constructor: Rc<CustomElementConstructor>,
673
674    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-observed-attributes>
675    pub(crate) observed_attributes: Vec<DOMString>,
676
677    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-lifecycle-callbacks>
678    pub(crate) callbacks: LifecycleCallbacks,
679
680    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-construction-stack>
681    pub(crate) construction_stack: DomRefCell<Vec<ConstructionStackEntry>>,
682
683    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-form-associated>
684    pub(crate) form_associated: bool,
685
686    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-disable-internals>
687    pub(crate) disable_internals: bool,
688
689    /// <https://html.spec.whatwg.org/multipage/#concept-custom-element-definition-disable-shadow>
690    pub(crate) disable_shadow: bool,
691}
692
693impl CustomElementDefinition {
694    #[allow(clippy::too_many_arguments)]
695    fn new(
696        name: LocalName,
697        local_name: LocalName,
698        constructor: Rc<CustomElementConstructor>,
699        observed_attributes: Vec<DOMString>,
700        callbacks: LifecycleCallbacks,
701        form_associated: bool,
702        disable_internals: bool,
703        disable_shadow: bool,
704    ) -> CustomElementDefinition {
705        CustomElementDefinition {
706            name,
707            local_name,
708            constructor,
709            observed_attributes,
710            callbacks,
711            construction_stack: Default::default(),
712            form_associated,
713            disable_internals,
714            disable_shadow,
715        }
716    }
717
718    /// <https://html.spec.whatwg.org/multipage/#autonomous-custom-element>
719    pub(crate) fn is_autonomous(&self) -> bool {
720        self.name == self.local_name
721    }
722
723    /// <https://dom.spec.whatwg.org/#concept-create-element> Step 4.1
724    #[allow(unsafe_code)]
725    pub(crate) fn create_element(
726        &self,
727        document: &Document,
728        prefix: Option<Prefix>,
729        can_gc: CanGc,
730    ) -> Fallible<DomRoot<Element>> {
731        let window = document.window();
732        let cx = GlobalScope::get_cx();
733        // Step 4.1.1. Let C be definition’s constructor.
734        rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback()));
735        rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>());
736        {
737            // Go into the constructor's realm
738            let _ac = JSAutoRealm::new(*cx, self.constructor.callback());
739            // Step 4.1.2. Set result to the result of constructing C, with no arguments.
740            let args = HandleValueArray::empty();
741            if unsafe { !Construct1(*cx, constructor.handle(), &args, element.handle_mut()) } {
742                return Err(Error::JSFailed);
743            }
744        }
745
746        // https://heycam.github.io/webidl/#construct-a-callback-function
747        // https://html.spec.whatwg.org/multipage/#clean-up-after-running-script
748        if is_execution_stack_empty() {
749            window
750                .as_global_scope()
751                .perform_a_microtask_checkpoint(can_gc);
752        }
753
754        rooted!(in(*cx) let element_val = ObjectValue(element.get()));
755        let element: DomRoot<Element> =
756            match SafeFromJSValConvertible::safe_from_jsval(cx, element_val.handle(), ()) {
757                Ok(ConversionResult::Success(element)) => element,
758                Ok(ConversionResult::Failure(..)) => {
759                    return Err(Error::Type(
760                        "Constructor did not return a DOM node".to_owned(),
761                    ));
762                },
763                _ => return Err(Error::JSFailed),
764            };
765
766        // Step 4.1.3. Assert: result’s custom element state and custom element definition are initialized.
767        // Step 4.1.4. Assert: result’s namespace is the HTML namespace.
768        // Note: IDL enforces that result is an HTMLElement object, which all use the HTML namespace.
769        // Note: the custom element definition is initialized by the caller if
770        // this method returns a success value.
771        assert!(element.is::<HTMLElement>());
772
773        // Step 4.1.5. If result’s attribute list is not empty, then throw a "NotSupportedError" DOMException.
774        // Step 4.1.6. If result has children, then throw a "NotSupportedError" DOMException.
775        // Step 4.1.7. If result’s parent is not null, then throw a "NotSupportedError" DOMException.
776        // Step 4.1.8. If result’s node document is not document, then throw a "NotSupportedError" DOMException.
777        // Step 4.1.9. If result’s local name is not equal to localName then throw a "NotSupportedError" DOMException.
778        if element.HasAttributes() ||
779            element.upcast::<Node>().children_count() > 0 ||
780            element.upcast::<Node>().has_parent() ||
781            &*element.upcast::<Node>().owner_doc() != document ||
782            *element.namespace() != ns!(html) ||
783            *element.local_name() != self.local_name
784        {
785            return Err(Error::NotSupported);
786        }
787
788        // Step 4.1.10. Set result’s namespace prefix to prefix.
789        element.set_prefix(prefix);
790
791        // Step 4.1.11. Set result’s is value to null.
792        // Element's `is` is None by default
793
794        Ok(element)
795    }
796}
797
798/// <https://html.spec.whatwg.org/multipage/#concept-upgrade-an-element>
799pub(crate) fn upgrade_element(
800    definition: Rc<CustomElementDefinition>,
801    element: &Element,
802    can_gc: CanGc,
803) {
804    // Step 1. If element's custom element state is not "undefined" or "uncustomized", then return.
805    let state = element.get_custom_element_state();
806    if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized {
807        return;
808    }
809
810    // Step 2. Set element's custom element definition to definition.
811    element.set_custom_element_definition(Rc::clone(&definition));
812
813    // Step 3. Set element's custom element state to "failed".
814    element.set_custom_element_state(CustomElementState::Failed);
815
816    // Step 4. For each attribute in element's attribute list, in order, enqueue a custom element callback reaction
817    // with element, callback name "attributeChangedCallback", and « attribute's local name, null, attribute's value,
818    // attribute's namespace ».
819    let custom_element_reaction_stack = ScriptThread::custom_element_reaction_stack();
820    for attr in element.attrs().iter() {
821        let local_name = attr.local_name().clone();
822        let value = DOMString::from(&**attr.value());
823        let namespace = attr.namespace().clone();
824        custom_element_reaction_stack.enqueue_callback_reaction(
825            element,
826            CallbackReaction::AttributeChanged(local_name, None, Some(value), namespace),
827            Some(definition.clone()),
828        );
829    }
830
831    // Step 5. If element is connected, then enqueue a custom element callback reaction with element,
832    // callback name "connectedCallback", and « ».
833    if element.is_connected() {
834        ScriptThread::enqueue_callback_reaction(
835            element,
836            CallbackReaction::Connected,
837            Some(definition.clone()),
838        );
839    }
840
841    // Step 6. Add element to the end of definition's construction stack.
842    definition
843        .construction_stack
844        .borrow_mut()
845        .push(ConstructionStackEntry::Element(DomRoot::from_ref(element)));
846
847    // Steps 7-8, successful case
848    let result = run_upgrade_constructor(&definition, element, can_gc);
849
850    // "regardless of whether the above steps threw an exception" step
851    definition.construction_stack.borrow_mut().pop();
852
853    // Step 8 exception handling
854    if let Err(error) = result {
855        // Step 8.exception.1
856        element.clear_custom_element_definition();
857
858        // Step 8.exception.2
859        element.clear_reaction_queue();
860
861        // Step 8.exception.3
862        let global = GlobalScope::current().expect("No current global");
863        let cx = GlobalScope::get_cx();
864        let ar = enter_realm(&*global);
865        throw_dom_exception(cx, &global, error, can_gc);
866        report_pending_exception(cx, true, InRealm::Entered(&ar), can_gc);
867
868        return;
869    }
870
871    // Step 9: handle with form-associated custom element
872    if let Some(html_element) = element.downcast::<HTMLElement>() {
873        if html_element.is_form_associated_custom_element() {
874            // We know this element is is form-associated, so we can use the implementation of
875            // `FormControl` for HTMLElement, which makes that assumption.
876            // Step 9.1: Reset the form owner of element
877            html_element.reset_form_owner(can_gc);
878            if let Some(form) = html_element.form_owner() {
879                // Even though the tree hasn't structurally mutated,
880                // HTMLCollections need to be invalidated.
881                form.upcast::<Node>().rev_version();
882                // The spec tells us specifically to enqueue a formAssociated reaction
883                // here, but it also says to do that for resetting form owner in general,
884                // and we don't need two reactions.
885            }
886
887            // Either enabled_state or disabled_state needs to be set,
888            // and the possibility of a disabled fieldset ancestor needs
889            // to be accounted for. (In the spec, being disabled is
890            // a fact that's true or false about a node at a given time,
891            // not a flag that belongs to the node and is updated,
892            // so it doesn't describe this check as an action.)
893            element.check_disabled_attribute();
894            element.check_ancestors_disabled_state_for_form_control();
895            element.update_read_write_state_from_readonly_attribute();
896
897            // Step 9.2: If element is disabled, then enqueue a custom element callback reaction
898            // with element.
899            if element.disabled_state() {
900                ScriptThread::enqueue_callback_reaction(
901                    element,
902                    CallbackReaction::FormDisabled(true),
903                    Some(definition.clone()),
904                )
905            }
906        }
907    }
908
909    // Step 10
910    element.set_custom_element_state(CustomElementState::Custom);
911}
912
913/// <https://html.spec.whatwg.org/multipage/#concept-upgrade-an-element>
914/// Steps 8.1-8.3
915#[allow(unsafe_code)]
916fn run_upgrade_constructor(
917    definition: &CustomElementDefinition,
918    element: &Element,
919    can_gc: CanGc,
920) -> ErrorResult {
921    let constructor = &definition.constructor;
922    let window = element.owner_window();
923    let cx = GlobalScope::get_cx();
924    rooted!(in(*cx) let constructor_val = ObjectValue(constructor.callback()));
925    rooted!(in(*cx) let mut element_val = UndefinedValue());
926    element.safe_to_jsval(cx, element_val.handle_mut());
927    rooted!(in(*cx) let mut construct_result = ptr::null_mut::<JSObject>());
928    {
929        // Step 8.1. If definition's disable shadow is true and element's shadow root is non-null,
930        // then throw a "NotSupportedError" DOMException.
931        if definition.disable_shadow && element.is_shadow_host() {
932            return Err(Error::NotSupported);
933        }
934
935        // Go into the constructor's realm
936        let _ac = JSAutoRealm::new(*cx, constructor.callback());
937        let args = HandleValueArray::empty();
938        // Step 8.2. Set element's custom element state to "precustomized".
939        element.set_custom_element_state(CustomElementState::Precustomized);
940
941        if unsafe {
942            !Construct1(
943                *cx,
944                constructor_val.handle(),
945                &args,
946                construct_result.handle_mut(),
947            )
948        } {
949            return Err(Error::JSFailed);
950        }
951
952        // https://heycam.github.io/webidl/#construct-a-callback-function
953        // https://html.spec.whatwg.org/multipage/#clean-up-after-running-script
954        if is_execution_stack_empty() {
955            window
956                .as_global_scope()
957                .perform_a_microtask_checkpoint(can_gc);
958        }
959
960        // Step 8.3. Let constructResult be the result of constructing C, with no arguments.
961        let mut same = false;
962        rooted!(in(*cx) let construct_result_val = ObjectValue(construct_result.get()));
963        // Step 8.4. If SameValue(constructResult, element) is false, then throw a TypeError.
964        if unsafe {
965            !SameValue(
966                *cx,
967                construct_result_val.handle(),
968                element_val.handle(),
969                &mut same,
970            )
971        } {
972            return Err(Error::JSFailed);
973        }
974        if !same {
975            return Err(Error::Type(
976                "Returned element is not SameValue as the upgraded element".to_string(),
977            ));
978        }
979    }
980    Ok(())
981}
982
983/// <https://html.spec.whatwg.org/multipage/#concept-try-upgrade>
984pub(crate) fn try_upgrade_element(element: &Element) {
985    // Step 1. Let definition be the result of looking up a custom element definition given element's node document,
986    // element's namespace, element's local name, and element's is value.
987    let document = element.owner_document();
988    let namespace = element.namespace();
989    let local_name = element.local_name();
990    let is = element.get_is();
991    if let Some(definition) =
992        document.lookup_custom_element_definition(namespace, local_name, is.as_ref())
993    {
994        // Step 2. If definition is not null, then enqueue a custom element upgrade reaction given
995        // element and definition.
996        ScriptThread::enqueue_upgrade_reaction(element, definition);
997    }
998}
999
1000#[derive(JSTraceable, MallocSizeOf)]
1001#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1002pub(crate) enum CustomElementReaction {
1003    Upgrade(#[conditional_malloc_size_of] Rc<CustomElementDefinition>),
1004    Callback(
1005        #[conditional_malloc_size_of] Rc<Function>,
1006        #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>,
1007    ),
1008}
1009
1010impl CustomElementReaction {
1011    /// <https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions>
1012    pub(crate) fn invoke(&self, element: &Element, can_gc: CanGc) {
1013        // Step 2.1
1014        match *self {
1015            CustomElementReaction::Upgrade(ref definition) => {
1016                upgrade_element(definition.clone(), element, can_gc)
1017            },
1018            CustomElementReaction::Callback(ref callback, ref arguments) => {
1019                // We're rooted, so it's safe to hand out a handle to objects in Heap
1020                let arguments = arguments.iter().map(|arg| arg.as_handle_value()).collect();
1021                rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
1022                let _ = callback.Call_(
1023                    element,
1024                    arguments,
1025                    value.handle_mut(),
1026                    ExceptionHandling::Report,
1027                    can_gc,
1028                );
1029            },
1030        }
1031    }
1032}
1033
1034pub(crate) enum CallbackReaction {
1035    Connected,
1036    Disconnected,
1037    Adopted(DomRoot<Document>, DomRoot<Document>),
1038    AttributeChanged(LocalName, Option<DOMString>, Option<DOMString>, Namespace),
1039    FormAssociated(Option<DomRoot<HTMLFormElement>>),
1040    FormDisabled(bool),
1041    FormReset,
1042}
1043
1044/// <https://html.spec.whatwg.org/multipage/#processing-the-backup-element-queue>
1045#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)]
1046enum BackupElementQueueFlag {
1047    Processing,
1048    NotProcessing,
1049}
1050
1051/// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack>
1052/// # Safety
1053/// This can be shared inside an Rc because one of those Rc copies lives
1054/// inside ScriptThread, so the GC can always reach this structure.
1055#[derive(JSTraceable, MallocSizeOf)]
1056#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1057#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
1058pub(crate) struct CustomElementReactionStack {
1059    stack: DomRefCell<Vec<ElementQueue>>,
1060    backup_queue: ElementQueue,
1061    processing_backup_element_queue: Cell<BackupElementQueueFlag>,
1062}
1063
1064impl CustomElementReactionStack {
1065    pub(crate) fn new() -> CustomElementReactionStack {
1066        CustomElementReactionStack {
1067            stack: DomRefCell::new(Vec::new()),
1068            backup_queue: ElementQueue::new(),
1069            processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing),
1070        }
1071    }
1072
1073    pub(crate) fn push_new_element_queue(&self) {
1074        self.stack.borrow_mut().push(ElementQueue::new());
1075    }
1076
1077    pub(crate) fn pop_current_element_queue(&self, can_gc: CanGc) {
1078        rooted_vec!(let mut stack);
1079        mem::swap(&mut *stack, &mut *self.stack.borrow_mut());
1080
1081        if let Some(current_queue) = stack.last() {
1082            current_queue.invoke_reactions(can_gc);
1083        }
1084        stack.pop();
1085
1086        mem::swap(&mut *self.stack.borrow_mut(), &mut *stack);
1087        self.stack.borrow_mut().append(&mut *stack);
1088    }
1089
1090    /// <https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue>
1091    /// Step 4
1092    pub(crate) fn invoke_backup_element_queue(&self, can_gc: CanGc) {
1093        // Step 4.1
1094        self.backup_queue.invoke_reactions(can_gc);
1095
1096        // Step 4.2
1097        self.processing_backup_element_queue
1098            .set(BackupElementQueueFlag::NotProcessing);
1099    }
1100
1101    /// <https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue>
1102    pub(crate) fn enqueue_element(&self, element: &Element) {
1103        if let Some(current_queue) = self.stack.borrow().last() {
1104            // Step 2
1105            current_queue.append_element(element);
1106        } else {
1107            // Step 1.1
1108            self.backup_queue.append_element(element);
1109
1110            // Step 1.2
1111            if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing {
1112                return;
1113            }
1114
1115            // Step 1.3
1116            self.processing_backup_element_queue
1117                .set(BackupElementQueueFlag::Processing);
1118
1119            // Step 4
1120            ScriptThread::enqueue_microtask(Microtask::CustomElementReaction);
1121        }
1122    }
1123
1124    /// <https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-callback-reaction>
1125    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
1126    pub(crate) fn enqueue_callback_reaction(
1127        &self,
1128        element: &Element,
1129        reaction: CallbackReaction,
1130        definition: Option<Rc<CustomElementDefinition>>,
1131    ) {
1132        // Step 1
1133        let definition = match definition.or_else(|| element.get_custom_element_definition()) {
1134            Some(definition) => definition,
1135            None => return,
1136        };
1137
1138        // Step 2
1139        let (callback, args) = match reaction {
1140            CallbackReaction::Connected => {
1141                (definition.callbacks.connected_callback.clone(), Vec::new())
1142            },
1143            CallbackReaction::Disconnected => (
1144                definition.callbacks.disconnected_callback.clone(),
1145                Vec::new(),
1146            ),
1147            CallbackReaction::Adopted(ref old_doc, ref new_doc) => {
1148                let args = vec![Heap::default(), Heap::default()];
1149                args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get()));
1150                args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get()));
1151                (definition.callbacks.adopted_callback.clone(), args)
1152            },
1153            CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => {
1154                // Step 4
1155                if !definition
1156                    .observed_attributes
1157                    .iter()
1158                    .any(|attr| *attr == *local_name)
1159                {
1160                    return;
1161                }
1162
1163                let cx = GlobalScope::get_cx();
1164                // We might be here during HTML parsing, rather than
1165                // during Javscript execution, and so we typically aren't
1166                // already in a realm here.
1167                let _ac = JSAutoRealm::new(*cx, element.global().reflector().get_jsobject().get());
1168
1169                let local_name = DOMString::from(&*local_name);
1170                rooted!(in(*cx) let mut name_value = UndefinedValue());
1171                local_name.safe_to_jsval(cx, name_value.handle_mut());
1172
1173                rooted!(in(*cx) let mut old_value = NullValue());
1174                if let Some(old_val) = old_val {
1175                    old_val.safe_to_jsval(cx, old_value.handle_mut());
1176                }
1177
1178                rooted!(in(*cx) let mut value = NullValue());
1179                if let Some(val) = val {
1180                    val.safe_to_jsval(cx, value.handle_mut());
1181                }
1182
1183                rooted!(in(*cx) let mut namespace_value = NullValue());
1184                if namespace != ns!() {
1185                    let namespace = DOMString::from(&*namespace);
1186                    namespace.safe_to_jsval(cx, namespace_value.handle_mut());
1187                }
1188
1189                let args = vec![
1190                    Heap::default(),
1191                    Heap::default(),
1192                    Heap::default(),
1193                    Heap::default(),
1194                ];
1195                args[0].set(name_value.get());
1196                args[1].set(old_value.get());
1197                args[2].set(value.get());
1198                args[3].set(namespace_value.get());
1199
1200                (
1201                    definition.callbacks.attribute_changed_callback.clone(),
1202                    args,
1203                )
1204            },
1205            CallbackReaction::FormAssociated(form) => {
1206                let args = vec![Heap::default()];
1207                if let Some(form) = form {
1208                    args[0].set(ObjectValue(form.reflector().get_jsobject().get()));
1209                } else {
1210                    args[0].set(NullValue());
1211                }
1212                (definition.callbacks.form_associated_callback.clone(), args)
1213            },
1214            CallbackReaction::FormDisabled(disabled) => {
1215                let cx = GlobalScope::get_cx();
1216                rooted!(in(*cx) let mut disabled_value = BooleanValue(disabled));
1217                let args = vec![Heap::default()];
1218                args[0].set(disabled_value.get());
1219                (definition.callbacks.form_disabled_callback.clone(), args)
1220            },
1221            CallbackReaction::FormReset => {
1222                (definition.callbacks.form_reset_callback.clone(), Vec::new())
1223            },
1224        };
1225
1226        // Step 3
1227        let callback = match callback {
1228            Some(callback) => callback,
1229            None => return,
1230        };
1231
1232        // Step 5
1233        element.push_callback_reaction(callback, args.into_boxed_slice());
1234
1235        // Step 6
1236        self.enqueue_element(element);
1237    }
1238
1239    /// <https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-upgrade-reaction>
1240    pub(crate) fn enqueue_upgrade_reaction(
1241        &self,
1242        element: &Element,
1243        definition: Rc<CustomElementDefinition>,
1244    ) {
1245        // Step 1. Add a new upgrade reaction to element's custom element reaction queue,
1246        // with custom element definition definition.
1247        element.push_upgrade_reaction(definition);
1248
1249        // Step 2. Enqueue an element on the appropriate element queue given element.
1250        self.enqueue_element(element);
1251    }
1252}
1253
1254/// <https://html.spec.whatwg.org/multipage/#element-queue>
1255#[derive(JSTraceable, MallocSizeOf)]
1256#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1257struct ElementQueue {
1258    queue: DomRefCell<VecDeque<Dom<Element>>>,
1259}
1260
1261impl ElementQueue {
1262    fn new() -> ElementQueue {
1263        ElementQueue {
1264            queue: Default::default(),
1265        }
1266    }
1267
1268    /// <https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions>
1269    fn invoke_reactions(&self, can_gc: CanGc) {
1270        // Steps 1-2
1271        while let Some(element) = self.next_element() {
1272            element.invoke_reactions(can_gc)
1273        }
1274        self.queue.borrow_mut().clear();
1275    }
1276
1277    fn next_element(&self) -> Option<DomRoot<Element>> {
1278        self.queue
1279            .borrow_mut()
1280            .pop_front()
1281            .as_deref()
1282            .map(DomRoot::from_ref)
1283    }
1284
1285    fn append_element(&self, element: &Element) {
1286        self.queue.borrow_mut().push_back(Dom::from_ref(element));
1287    }
1288}
1289
1290/// <https://html.spec.whatwg.org/multipage/#valid-custom-element-name>
1291pub(crate) fn is_valid_custom_element_name(name: &str) -> bool {
1292    // Custom elment names must match:
1293    // PotentialCustomElementName ::= [a-z] (PCENChar)* '-' (PCENChar)*
1294
1295    let mut chars = name.chars();
1296    if !chars.next().is_some_and(|c| c.is_ascii_lowercase()) {
1297        return false;
1298    }
1299
1300    let mut has_dash = false;
1301
1302    for c in chars {
1303        if c == '-' {
1304            has_dash = true;
1305            continue;
1306        }
1307
1308        if !is_potential_custom_element_char(c) {
1309            return false;
1310        }
1311    }
1312
1313    if !has_dash {
1314        return false;
1315    }
1316
1317    if name == "annotation-xml" ||
1318        name == "color-profile" ||
1319        name == "font-face" ||
1320        name == "font-face-src" ||
1321        name == "font-face-uri" ||
1322        name == "font-face-format" ||
1323        name == "font-face-name" ||
1324        name == "missing-glyph"
1325    {
1326        return false;
1327    }
1328
1329    true
1330}
1331
1332/// Check if this character is a PCENChar
1333/// <https://html.spec.whatwg.org/multipage/#prod-pcenchar>
1334fn is_potential_custom_element_char(c: char) -> bool {
1335    c == '-' ||
1336        c == '.' ||
1337        c == '_' ||
1338        c == '\u{B7}' ||
1339        c.is_ascii_digit() ||
1340        c.is_ascii_lowercase() ||
1341        ('\u{C0}'..='\u{D6}').contains(&c) ||
1342        ('\u{D8}'..='\u{F6}').contains(&c) ||
1343        ('\u{F8}'..='\u{37D}').contains(&c) ||
1344        ('\u{37F}'..='\u{1FFF}').contains(&c) ||
1345        ('\u{200C}'..='\u{200D}').contains(&c) ||
1346        ('\u{203F}'..='\u{2040}').contains(&c) ||
1347        ('\u{2070}'..='\u{218F}').contains(&c) ||
1348        ('\u{2C00}'..='\u{2FEF}').contains(&c) ||
1349        ('\u{3001}'..='\u{D7FF}').contains(&c) ||
1350        ('\u{F900}'..='\u{FDCF}').contains(&c) ||
1351        ('\u{FDF0}'..='\u{FFFD}').contains(&c) ||
1352        ('\u{10000}'..='\u{EFFFF}').contains(&c)
1353}
1354
1355fn is_extendable_element_interface(element: &str) -> bool {
1356    element == "a" ||
1357        element == "abbr" ||
1358        element == "acronym" ||
1359        element == "address" ||
1360        element == "area" ||
1361        element == "article" ||
1362        element == "aside" ||
1363        element == "audio" ||
1364        element == "b" ||
1365        element == "base" ||
1366        element == "bdi" ||
1367        element == "bdo" ||
1368        element == "big" ||
1369        element == "blockquote" ||
1370        element == "body" ||
1371        element == "br" ||
1372        element == "button" ||
1373        element == "canvas" ||
1374        element == "caption" ||
1375        element == "center" ||
1376        element == "cite" ||
1377        element == "code" ||
1378        element == "col" ||
1379        element == "colgroup" ||
1380        element == "data" ||
1381        element == "datalist" ||
1382        element == "dd" ||
1383        element == "del" ||
1384        element == "details" ||
1385        element == "dfn" ||
1386        element == "dialog" ||
1387        element == "dir" ||
1388        element == "div" ||
1389        element == "dl" ||
1390        element == "dt" ||
1391        element == "em" ||
1392        element == "embed" ||
1393        element == "fieldset" ||
1394        element == "figcaption" ||
1395        element == "figure" ||
1396        element == "font" ||
1397        element == "footer" ||
1398        element == "form" ||
1399        element == "frame" ||
1400        element == "frameset" ||
1401        element == "h1" ||
1402        element == "h2" ||
1403        element == "h3" ||
1404        element == "h4" ||
1405        element == "h5" ||
1406        element == "h6" ||
1407        element == "head" ||
1408        element == "header" ||
1409        element == "hgroup" ||
1410        element == "hr" ||
1411        element == "html" ||
1412        element == "i" ||
1413        element == "iframe" ||
1414        element == "img" ||
1415        element == "input" ||
1416        element == "ins" ||
1417        element == "kbd" ||
1418        element == "label" ||
1419        element == "legend" ||
1420        element == "li" ||
1421        element == "link" ||
1422        element == "listing" ||
1423        element == "main" ||
1424        element == "map" ||
1425        element == "mark" ||
1426        element == "marquee" ||
1427        element == "menu" ||
1428        element == "meta" ||
1429        element == "meter" ||
1430        element == "nav" ||
1431        element == "nobr" ||
1432        element == "noframes" ||
1433        element == "noscript" ||
1434        element == "object" ||
1435        element == "ol" ||
1436        element == "optgroup" ||
1437        element == "option" ||
1438        element == "output" ||
1439        element == "p" ||
1440        element == "param" ||
1441        element == "picture" ||
1442        element == "plaintext" ||
1443        element == "pre" ||
1444        element == "progress" ||
1445        element == "q" ||
1446        element == "rp" ||
1447        element == "rt" ||
1448        element == "ruby" ||
1449        element == "s" ||
1450        element == "samp" ||
1451        element == "script" ||
1452        element == "section" ||
1453        element == "select" ||
1454        element == "slot" ||
1455        element == "small" ||
1456        element == "source" ||
1457        element == "span" ||
1458        element == "strike" ||
1459        element == "strong" ||
1460        element == "style" ||
1461        element == "sub" ||
1462        element == "summary" ||
1463        element == "sup" ||
1464        element == "table" ||
1465        element == "tbody" ||
1466        element == "td" ||
1467        element == "template" ||
1468        element == "textarea" ||
1469        element == "tfoot" ||
1470        element == "th" ||
1471        element == "thead" ||
1472        element == "time" ||
1473        element == "title" ||
1474        element == "tr" ||
1475        element == "tt" ||
1476        element == "track" ||
1477        element == "u" ||
1478        element == "ul" ||
1479        element == "var" ||
1480        element == "video" ||
1481        element == "wbr" ||
1482        element == "xmp"
1483}