diff --git a/components/script_bindings/codegen/codegen.py b/components/script_bindings/codegen/codegen.py index cb954f4361d..57453c0089d 100644 --- a/components/script_bindings/codegen/codegen.py +++ b/components/script_bindings/codegen/codegen.py @@ -72,6 +72,11 @@ from configuration import ( ) AUTOGENERATED_WARNING_COMMENT = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n" + +# DFS-ordered prototype ID ranges, populated by GlobalGenRoots.PrototypeList() +# and consumed by CGIDLInterface to generate PROTO_FIRST/PROTO_LAST constants. +_proto_ranges: dict[str, tuple[int, int]] = {} + ALLOWED_WARNING_LIST = [ 'non_camel_case_types', 'non_upper_case_globals', @@ -3395,6 +3400,7 @@ let val = PrivateValue(ptr::null()); JS_SetReservedSlot(obj.get(), DOM_WEAK_SLOT, &val); """ + name = self.descriptor.name return CGGeneric(f""" let raw = Root::new(MaybeUnreflectedDom::from_box(object)); @@ -3409,6 +3415,7 @@ assert!(!canonical_proto.is_null()); {create} let root = raw.reflect_with(obj.get()); +root.reflector().set_proto_id(PrototypeList::ID::{name} as u16); {unforgeable} @@ -3441,6 +3448,7 @@ class CGWrapGlobalMethod(CGAbstractMethod): members = [f"{function}::(cx.into(), obj.handle(), {array.variableName()}.get(), obj.handle());" for (function, array) in pairs if array.length() > 0] membersStr = "\n".join(members) + name = self.descriptor.name return CGGeneric(f""" unsafe {{ @@ -3459,6 +3467,7 @@ unsafe {{ assert!(!obj.is_null()); let root = raw.reflect_with(obj.get()); + root.reflector().set_proto_id(PrototypeList::ID::{name} as u16); let _ac = JSAutoRealm::new(cx.raw_cx(), obj.get()); rooted!(&in(cx) let mut canonical_proto = ptr::null_mut::()); @@ -3503,12 +3512,16 @@ class CGIDLInterface(CGThing): check = f"ptr::eq(class, unsafe {{ {bindingModule}::Class.get() }})" else: check = f"ptr::eq(class, unsafe {{ &{bindingModule}::Class.get().dom_class }})" + # Get DFS-ordered ID range for this interface (set by PrototypeList generation). + proto_first, proto_last = _proto_ranges.get(name, (0, 65535)) return f""" impl IDLInterface for {name} {{ #[inline] fn derives(class: &'static DOMClass) -> bool {{ {check} }} + const PROTO_FIRST: u16 = {proto_first}; + const PROTO_LAST: u16 = {proto_last}; }} """ @@ -9142,8 +9155,51 @@ class GlobalGenRoots(): @staticmethod def PrototypeList(config: Configuration) -> CGThing: # Prototype ID enum. + # Assign IDs in DFS preorder of the inheritance tree so that + # all descendants of a type occupy a contiguous ID range. + # This enables O(1) is::() checks via range comparison. interfaces = config.getDescriptors(isCallback=False, isNamespace=False) - protos = [d.name for d in interfaces] + + # Build a tree: parent_name -> sorted list of child names + children: dict[str | None, list[str]] = {} + name_set = set() + for d in interfaces: + name = d.name + name_set.add(name) + parent_name = d.prototypeChain[-2] if len(d.prototypeChain) >= 2 else None + children.setdefault(parent_name, []).append(name) + + # Sort children alphabetically for determinism within each level + for key in children: + children[key].sort() + + # DFS preorder traversal + protos: list[str] = [] + # Track range for each prototype: (first_id, last_id) + proto_ranges: dict[str, tuple[int, int]] = {} + + def dfs(name: str) -> None: + first_id = len(protos) + protos.append(name) + for child in children.get(name, []): + dfs(child) + proto_ranges[name] = (first_id, len(protos) - 1) + + # Start from roots (interfaces with no parent in our set) + roots = sorted(children.get(None, [])) + for root in roots: + dfs(root) + + # Any interfaces not in the tree (shouldn't happen, but be safe) + for d in interfaces: + if d.name not in proto_ranges: + first_id = len(protos) + protos.append(d.name) + proto_ranges[d.name] = (first_id, first_id) + + # Store ranges at module level for use by CGIDLInterface. + global _proto_ranges + _proto_ranges = proto_ranges constructors = sorted([MakeNativeName(d.name) for d in config.getDescriptors(hasInterfaceObject=True) if d.shouldHaveGetConstructorObjectMethod()]) diff --git a/components/script_bindings/conversions.rs b/components/script_bindings/conversions.rs index 7dc896b671a..77c73775137 100644 --- a/components/script_bindings/conversions.rs +++ b/components/script_bindings/conversions.rs @@ -51,6 +51,11 @@ impl SafeToJSValConvertible for T { pub trait IDLInterface { /// Returns whether the given DOM class derives that interface. fn derives(_: &'static DOMClass) -> bool; + + /// First prototype ID in the DFS-ordered range for this interface and its descendants. + const PROTO_FIRST: u16 = 0; + /// Last prototype ID in the DFS-ordered range for this interface and its descendants. + const PROTO_LAST: u16 = u16::MAX; } /// A trait to mark an IDL interface as deriving from another one. diff --git a/components/script_bindings/inheritance.rs b/components/script_bindings/inheritance.rs index db50a7a82bf..bcf04537fe8 100644 --- a/components/script_bindings/inheritance.rs +++ b/components/script_bindings/inheritance.rs @@ -6,7 +6,7 @@ use std::mem; -use crate::conversions::{DerivedFrom, IDLInterface, get_dom_class}; +use crate::conversions::{DerivedFrom, IDLInterface}; use crate::reflector::DomObject; use crate::script_runtime::runtime_is_alive; @@ -26,8 +26,8 @@ pub trait Castable: IDLInterface + DomObject + Sized { "Attempting to interact with DOM objects after JS runtime has shut down." ); - let class = unsafe { get_dom_class(self.reflector().get_jsobject().get()).unwrap() }; - T::derives(class) + let id = self.reflector().proto_id(); + id >= T::PROTO_FIRST && id <= T::PROTO_LAST } /// Cast a DOM object upwards to one of the interfaces it derives from. diff --git a/components/script_bindings/reflector.rs b/components/script_bindings/reflector.rs index b94cf571c5d..20b19f001d7 100644 --- a/components/script_bindings/reflector.rs +++ b/components/script_bindings/reflector.rs @@ -42,6 +42,8 @@ pub struct Reflector { object: Heap<*mut JSObject>, /// Associated memory size (of rust side). Used for memory reporting to SM. size: T, + /// Cached prototype ID for fast type checks. + proto_id: Cell, } unsafe impl js::gc::Traceable for Reflector { @@ -62,6 +64,18 @@ impl Reflector { unsafe { HandleObject::from_raw(self.object.handle()) } } + /// Get the cached prototype ID. + #[inline] + pub fn proto_id(&self) -> u16 { + self.proto_id.get() + } + + /// Set the cached prototype ID. + #[inline] + pub fn set_proto_id(&self, id: u16) { + self.proto_id.set(id); + } + /// Initialize the reflector. (May be called only once.) /// /// # Safety @@ -88,6 +102,7 @@ impl Reflector { pub fn new() -> Reflector { Reflector { object: Heap::default(), + proto_id: Cell::new(u16::MAX), size: T::default(), } } diff --git a/tests/unit/script/size_of.rs b/tests/unit/script/size_of.rs index 8cd6c5f31d6..ba2924d5d90 100644 --- a/tests/unit/script/size_of.rs +++ b/tests/unit/script/size_of.rs @@ -29,11 +29,11 @@ macro_rules! sizeof_checker ( ); // Update the sizes here -sizeof_checker!(size_event_target, EventTarget, 48); -sizeof_checker!(size_node, Node, 152); -sizeof_checker!(size_element, Element, 344); -sizeof_checker!(size_htmlelement, HTMLElement, 360); -sizeof_checker!(size_div, HTMLDivElement, 360); -sizeof_checker!(size_span, HTMLSpanElement, 360); -sizeof_checker!(size_text, Text, 184); -sizeof_checker!(size_characterdata, CharacterData, 184); +sizeof_checker!(size_event_target, EventTarget, 56); +sizeof_checker!(size_node, Node, 160); +sizeof_checker!(size_element, Element, 352); +sizeof_checker!(size_htmlelement, HTMLElement, 368); +sizeof_checker!(size_div, HTMLDivElement, 368); +sizeof_checker!(size_span, HTMLSpanElement, 368); +sizeof_checker!(size_text, Text, 192); +sizeof_checker!(size_characterdata, CharacterData, 192);