Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion components/script_bindings/codegen/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@
)

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',
Expand Down Expand Up @@ -3395,6 +3400,7 @@ def definition_body(self) -> CGThing:
JS_SetReservedSlot(obj.get(), DOM_WEAK_SLOT, &val);
"""

name = self.descriptor.name
return CGGeneric(f"""
let raw = Root::new(MaybeUnreflectedDom::from_box(object));

Expand All @@ -3409,6 +3415,7 @@ def definition_body(self) -> CGThing:

{create}
let root = raw.reflect_with(obj.get());
root.reflector().set_proto_id(PrototypeList::ID::{name} as u16);

{unforgeable}

Expand Down Expand Up @@ -3441,6 +3448,7 @@ def definition_body(self) -> CGThing:
members = [f"{function}::<D>(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 {{
Expand All @@ -3459,6 +3467,7 @@ def definition_body(self) -> CGThing:
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::<JSObject>());
Expand Down Expand Up @@ -3503,12 +3512,16 @@ def define(self) -> str:
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};
}}
"""

Expand Down Expand Up @@ -9142,8 +9155,51 @@ def bindingPath(pair: tuple[str, str, str]) -> str:
@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::<T>() 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()])
Expand Down
5 changes: 5 additions & 0 deletions components/script_bindings/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ impl<T: ToJSValConvertible + ?Sized> 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.
Expand Down
6 changes: 3 additions & 3 deletions components/script_bindings/inheritance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions components/script_bindings/reflector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub struct Reflector<T = ()> {
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<u16>,
}

unsafe impl<T> js::gc::Traceable for Reflector<T> {
Expand All @@ -62,6 +64,18 @@ impl<T> Reflector<T> {
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
Expand All @@ -88,6 +102,7 @@ impl<T: AssociatedMemorySize> Reflector<T> {
pub fn new() -> Reflector<T> {
Reflector {
object: Heap::default(),
proto_id: Cell::new(u16::MAX),
size: T::default(),
}
}
Expand Down
16 changes: 8 additions & 8 deletions tests/unit/script/size_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Loading