script: Reduce ScriptThread TLS usage (#38875)

We store a pointer to the ScriptThread singleton for a thread in
thread-local storage. While we don't have yet have profiles pointing to
this TLS access as a hot spot, we can remove a potential performance
footgun without a lot of effort by passing around small pieces of data
that we otherwise need to fetch from the ScriptThread.

Testing: Existing WPT is sufficient
Fixes: part of #37969

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-08-30 12:51:40 -04:00 committed by GitHub
parent d1da1a995c
commit c97ec1b2fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 129 additions and 68 deletions

View file

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashSet;
use crossbeam_channel::{Receiver, select}; use crossbeam_channel::{Receiver, select};
use devtools_traits::DevtoolScriptControlMsg; use devtools_traits::DevtoolScriptControlMsg;
@ -53,7 +55,7 @@ pub(crate) fn run_worker_event_loop<T, WorkerMsg, Event>(
let event = select! { let event = select! {
recv(worker_scope.control_receiver()) -> msg => T::from_control_msg(msg.unwrap()), recv(worker_scope.control_receiver()) -> msg => T::from_control_msg(msg.unwrap()),
recv(task_queue.select()) -> msg => { recv(task_queue.select()) -> msg => {
task_queue.take_tasks(msg.unwrap()); task_queue.take_tasks(msg.unwrap(), &HashSet::new());
T::from_worker_msg(task_queue.recv().unwrap()) T::from_worker_msg(task_queue.recv().unwrap())
}, },
recv(devtools_receiver) -> msg => T::from_devtools_msg(msg.unwrap()), recv(devtools_receiver) -> msg => T::from_devtools_msg(msg.unwrap()),
@ -72,7 +74,7 @@ pub(crate) fn run_worker_event_loop<T, WorkerMsg, Event>(
while !scope.is_closing() { while !scope.is_closing() {
// Batch all events that are ready. // Batch all events that are ready.
// The task queue will throttle non-priority tasks if necessary. // The task queue will throttle non-priority tasks if necessary.
match task_queue.take_tasks_and_recv() { match task_queue.take_tasks_and_recv(&HashSet::new()) {
Err(_) => match devtools_receiver.try_recv() { Err(_) => match devtools_receiver.try_recv() {
Ok(message) => sequential.push(T::from_devtools_msg(message)), Ok(message) => sequential.push(T::from_devtools_msg(message)),
Err(_) => break, Err(_) => break,

View file

@ -52,7 +52,6 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlelement::HTMLElement;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::script_runtime::{CanGc, JSContext, JSContext as SafeJSContext}; use crate::script_runtime::{CanGc, JSContext, JSContext as SafeJSContext};
use crate::script_thread::ScriptThread;
/// <https://html.spec.whatwg.org/multipage/#htmlconstructor> /// <https://html.spec.whatwg.org/multipage/#htmlconstructor>
fn html_constructor( fn html_constructor(
@ -391,14 +390,6 @@ fn get_constructor_object_from_local_name(
true true
} }
pub(crate) fn pop_current_element_queue(can_gc: CanGc) {
ScriptThread::pop_current_element_queue(can_gc);
}
pub(crate) fn push_new_element_queue() {
ScriptThread::push_new_element_queue();
}
pub(crate) fn call_html_constructor<T: DerivedFrom<Element> + DomObject>( pub(crate) fn call_html_constructor<T: DerivedFrom<Element> + DomObject>(
cx: JSContext, cx: JSContext,
args: &CallArgs, args: &CallArgs,

View file

@ -19,9 +19,7 @@ use script_bindings::settings_stack::StackEntry;
use crate::DomTypes; use crate::DomTypes;
use crate::dom::bindings::codegen::{InterfaceObjectMap, PrototypeList}; use crate::dom::bindings::codegen::{InterfaceObjectMap, PrototypeList};
use crate::dom::bindings::constructor::{ use crate::dom::bindings::constructor::call_html_constructor;
call_html_constructor, pop_current_element_queue, push_new_element_queue,
};
use crate::dom::bindings::conversions::DerivedFrom; use crate::dom::bindings::conversions::DerivedFrom;
use crate::dom::bindings::error::{Error, report_pending_exception, throw_dom_exception}; use crate::dom::bindings::error::{Error, report_pending_exception, throw_dom_exception};
use crate::dom::bindings::principals::PRINCIPALS_CALLBACKS; use crate::dom::bindings::principals::PRINCIPALS_CALLBACKS;
@ -33,6 +31,7 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::windowproxy::WindowProxyHandler; use crate::dom::windowproxy::WindowProxyHandler;
use crate::realms::InRealm; use crate::realms::InRealm;
use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::script_thread::ScriptThread;
#[derive(JSTraceable, MallocSizeOf)] #[derive(JSTraceable, MallocSizeOf)]
/// Static data associated with a global object. /// Static data associated with a global object.
@ -185,10 +184,10 @@ impl DomHelpers<crate::DomTypeHolder> for crate::DomTypeHolder {
} }
fn push_new_element_queue() { fn push_new_element_queue() {
push_new_element_queue() ScriptThread::custom_element_reaction_stack().push_new_element_queue()
} }
fn pop_current_element_queue(can_gc: CanGc) { fn pop_current_element_queue(can_gc: CanGc) {
pop_current_element_queue(can_gc) ScriptThread::custom_element_reaction_stack().pop_current_element_queue(can_gc)
} }
fn reflect_dom_object<T, U>(obj: Box<T>, global: &U, can_gc: CanGc) -> DomRoot<T> fn reflect_dom_object<T, U>(obj: Box<T>, global: &U, can_gc: CanGc) -> DomRoot<T>

View file

@ -1044,8 +1044,12 @@ enum BackupElementQueueFlag {
} }
/// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack> /// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack>
/// # Safety
/// This can be shared inside an Rc because one of those Rc copies lives
/// inside ScriptThread, so the GC can always reach this structure.
#[derive(JSTraceable, MallocSizeOf)] #[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
pub(crate) struct CustomElementReactionStack { pub(crate) struct CustomElementReactionStack {
stack: DomRefCell<Vec<ElementQueue>>, stack: DomRefCell<Vec<ElementQueue>>,
backup_queue: ElementQueue, backup_queue: ElementQueue,

View file

@ -124,7 +124,7 @@ use crate::dom::cdatasection::CDATASection;
use crate::dom::comment::Comment; use crate::dom::comment::Comment;
use crate::dom::compositionevent::CompositionEvent; use crate::dom::compositionevent::CompositionEvent;
use crate::dom::cssstylesheet::CSSStyleSheet; use crate::dom::cssstylesheet::CSSStyleSheet;
use crate::dom::customelementregistry::CustomElementDefinition; use crate::dom::customelementregistry::{CustomElementDefinition, CustomElementReactionStack};
use crate::dom::customevent::CustomEvent; use crate::dom::customevent::CustomEvent;
use crate::dom::document_event_handler::DocumentEventHandler; use crate::dom::document_event_handler::DocumentEventHandler;
use crate::dom::documentfragment::DocumentFragment; use crate::dom::documentfragment::DocumentFragment;
@ -565,6 +565,10 @@ pub(crate) struct Document {
/// this `Document` will not perform any more rendering updates. /// this `Document` will not perform any more rendering updates.
#[no_trace] #[no_trace]
current_canvas_epoch: RefCell<Epoch>, current_canvas_epoch: RefCell<Epoch>,
/// The global custom element reaction stack for this script thread.
#[conditional_malloc_size_of]
custom_element_reaction_stack: Rc<CustomElementReactionStack>,
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -3261,6 +3265,7 @@ impl Document {
allow_declarative_shadow_roots: bool, allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool, has_trustworthy_ancestor_origin: bool,
custom_element_reaction_stack: Rc<CustomElementReactionStack>,
) -> Document { ) -> Document {
let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap()); let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
@ -3434,6 +3439,7 @@ impl Document {
resize_observer_started_observing_target: Cell::new(false), resize_observer_started_observing_target: Cell::new(false),
waiting_on_canvas_image_updates: Cell::new(false), waiting_on_canvas_image_updates: Cell::new(false),
current_canvas_epoch: RefCell::new(Epoch(0)), current_canvas_epoch: RefCell::new(Epoch(0)),
custom_element_reaction_stack,
} }
} }
@ -3536,6 +3542,7 @@ impl Document {
allow_declarative_shadow_roots: bool, allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool, has_trustworthy_ancestor_origin: bool,
custom_element_reaction_stack: Rc<CustomElementReactionStack>,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<Document> { ) -> DomRoot<Document> {
Self::new_with_proto( Self::new_with_proto(
@ -3557,6 +3564,7 @@ impl Document {
allow_declarative_shadow_roots, allow_declarative_shadow_roots,
inherited_insecure_requests_policy, inherited_insecure_requests_policy,
has_trustworthy_ancestor_origin, has_trustworthy_ancestor_origin,
custom_element_reaction_stack,
can_gc, can_gc,
) )
} }
@ -3581,6 +3589,7 @@ impl Document {
allow_declarative_shadow_roots: bool, allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool, has_trustworthy_ancestor_origin: bool,
custom_element_reaction_stack: Rc<CustomElementReactionStack>,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<Document> { ) -> DomRoot<Document> {
let document = reflect_dom_object_with_proto( let document = reflect_dom_object_with_proto(
@ -3602,6 +3611,7 @@ impl Document {
allow_declarative_shadow_roots, allow_declarative_shadow_roots,
inherited_insecure_requests_policy, inherited_insecure_requests_policy,
has_trustworthy_ancestor_origin, has_trustworthy_ancestor_origin,
custom_element_reaction_stack,
)), )),
window, window,
proto, proto,
@ -3736,6 +3746,7 @@ impl Document {
self.allow_declarative_shadow_roots(), self.allow_declarative_shadow_roots(),
Some(self.insecure_requests_policy()), Some(self.insecure_requests_policy()),
self.has_trustworthy_ancestor_or_current_origin(), self.has_trustworthy_ancestor_or_current_origin(),
self.custom_element_reaction_stack.clone(),
can_gc, can_gc,
); );
new_doc new_doc
@ -4413,6 +4424,10 @@ impl Document {
pub(crate) fn highlighted_dom_node(&self) -> Option<DomRoot<Node>> { pub(crate) fn highlighted_dom_node(&self) -> Option<DomRoot<Node>> {
self.highlighted_dom_node.get() self.highlighted_dom_node.get()
} }
pub(crate) fn custom_element_reaction_stack(&self) -> Rc<CustomElementReactionStack> {
self.custom_element_reaction_stack.clone()
}
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -4444,6 +4459,7 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
doc.allow_declarative_shadow_roots(), doc.allow_declarative_shadow_roots(),
Some(doc.insecure_requests_policy()), Some(doc.insecure_requests_policy()),
doc.has_trustworthy_ancestor_or_current_origin(), doc.has_trustworthy_ancestor_or_current_origin(),
doc.custom_element_reaction_stack(),
can_gc, can_gc,
)) ))
} }

View file

@ -117,6 +117,7 @@ impl DOMImplementationMethods<crate::DomTypeHolder> for DOMImplementation {
loader, loader,
Some(self.document.insecure_requests_policy()), Some(self.document.insecure_requests_policy()),
self.document.has_trustworthy_ancestor_or_current_origin(), self.document.has_trustworthy_ancestor_or_current_origin(),
self.document.custom_element_reaction_stack(),
can_gc, can_gc,
); );
@ -184,6 +185,7 @@ impl DOMImplementationMethods<crate::DomTypeHolder> for DOMImplementation {
self.document.allow_declarative_shadow_roots(), self.document.allow_declarative_shadow_roots(),
Some(self.document.insecure_requests_policy()), Some(self.document.insecure_requests_policy()),
self.document.has_trustworthy_ancestor_or_current_origin(), self.document.has_trustworthy_ancestor_or_current_origin(),
self.document.custom_element_reaction_stack(),
can_gc, can_gc,
); );

View file

@ -104,6 +104,7 @@ impl DOMParserMethods<crate::DomTypeHolder> for DOMParser {
false, false,
Some(doc.insecure_requests_policy()), Some(doc.insecure_requests_policy()),
doc.has_trustworthy_ancestor_or_current_origin(), doc.has_trustworthy_ancestor_or_current_origin(),
doc.custom_element_reaction_stack(),
can_gc, can_gc,
); );
// Step switch-1. Parse HTML from a string given document and compliantString. // Step switch-1. Parse HTML from a string given document and compliantString.
@ -131,6 +132,7 @@ impl DOMParserMethods<crate::DomTypeHolder> for DOMParser {
false, false,
Some(doc.insecure_requests_policy()), Some(doc.insecure_requests_policy()),
doc.has_trustworthy_ancestor_or_current_origin(), doc.has_trustworthy_ancestor_or_current_origin(),
doc.custom_element_reaction_stack(),
can_gc, can_gc,
); );
// Step switch-1. Create an XML parser parser, associated with document, // Step switch-1. Create an XML parser parser, associated with document,

View file

@ -326,7 +326,7 @@ impl Node {
/// Implements the "unsafely set HTML" algorithm as specified in: /// Implements the "unsafely set HTML" algorithm as specified in:
/// <https://html.spec.whatwg.org/multipage/#concept-unsafely-set-html> /// <https://html.spec.whatwg.org/multipage/#concept-unsafely-set-html>
pub fn unsafely_set_html( pub(crate) fn unsafely_set_html(
target: &Node, target: &Node,
context_element: &Element, context_element: &Element,
html: DOMString, html: DOMString,
@ -2926,6 +2926,7 @@ impl Node {
document.allow_declarative_shadow_roots(), document.allow_declarative_shadow_roots(),
Some(document.insecure_requests_policy()), Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(), document.has_trustworthy_ancestor_or_current_origin(),
document.custom_element_reaction_stack(),
can_gc, can_gc,
); );
DomRoot::upcast::<Node>(document) DomRoot::upcast::<Node>(document)

View file

@ -8,6 +8,7 @@ use std::borrow::Cow;
use std::cell::{Cell, Ref, RefCell, RefMut}; use std::cell::{Cell, Ref, RefCell, RefMut};
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::vec_deque::VecDeque; use std::collections::vec_deque::VecDeque;
use std::rc::Rc;
use std::thread; use std::thread;
use crossbeam_channel::{Receiver, Sender, unbounded}; use crossbeam_channel::{Receiver, Sender, unbounded};
@ -29,6 +30,7 @@ use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString; use crate::dom::bindings::str::DOMString;
use crate::dom::comment::Comment; use crate::dom::comment::Comment;
use crate::dom::customelementregistry::CustomElementReactionStack;
use crate::dom::document::Document; use crate::dom::document::Document;
use crate::dom::documenttype::DocumentType; use crate::dom::documenttype::DocumentType;
use crate::dom::element::{Element, ElementCreator}; use crate::dom::element::{Element, ElementCreator};
@ -228,6 +230,8 @@ pub(crate) struct Tokenizer {
#[no_trace] #[no_trace]
url: ServoUrl, url: ServoUrl,
parsing_algorithm: ParsingAlgorithm, parsing_algorithm: ParsingAlgorithm,
#[conditional_malloc_size_of]
custom_element_reaction_stack: Rc<CustomElementReactionStack>,
} }
impl Tokenizer { impl Tokenizer {
@ -246,6 +250,7 @@ impl Tokenizer {
None => ParsingAlgorithm::Normal, None => ParsingAlgorithm::Normal,
}; };
let custom_element_reaction_stack = document.custom_element_reaction_stack();
let tokenizer = Tokenizer { let tokenizer = Tokenizer {
document: Dom::from_ref(document), document: Dom::from_ref(document),
receiver: tokenizer_receiver, receiver: tokenizer_receiver,
@ -253,6 +258,7 @@ impl Tokenizer {
nodes: RefCell::new(HashMap::new()), nodes: RefCell::new(HashMap::new()),
url, url,
parsing_algorithm: algorithm, parsing_algorithm: algorithm,
custom_element_reaction_stack,
}; };
tokenizer.insert_node(0, Dom::from_ref(document.upcast())); tokenizer.insert_node(0, Dom::from_ref(document.upcast()));
@ -397,7 +403,14 @@ impl Tokenizer {
.GetParentNode() .GetParentNode()
.expect("append_before_sibling called on node without parent"); .expect("append_before_sibling called on node without parent");
super::insert(parent, Some(sibling), node, self.parsing_algorithm, can_gc); super::insert(
parent,
Some(sibling),
node,
self.parsing_algorithm,
&self.custom_element_reaction_stack,
can_gc,
);
} }
fn append(&self, parent: ParseNodeId, node: NodeOrText, can_gc: CanGc) { fn append(&self, parent: ParseNodeId, node: NodeOrText, can_gc: CanGc) {
@ -409,7 +422,14 @@ impl Tokenizer {
}; };
let parent = &**self.get_node(&parent); let parent = &**self.get_node(&parent);
super::insert(parent, None, node, self.parsing_algorithm, can_gc); super::insert(
parent,
None,
node,
self.parsing_algorithm,
&self.custom_element_reaction_stack,
can_gc,
);
} }
fn has_parent_node(&self, node: ParseNodeId) -> bool { fn has_parent_node(&self, node: ParseNodeId) -> bool {
@ -454,6 +474,7 @@ impl Tokenizer {
&self.document, &self.document,
ElementCreator::ParserCreated(current_line), ElementCreator::ParserCreated(current_line),
ParsingAlgorithm::Normal, ParsingAlgorithm::Normal,
&self.custom_element_reaction_stack,
can_gc, can_gc,
); );
self.insert_node(node, Dom::from_ref(element.upcast())); self.insert_node(node, Dom::from_ref(element.upcast()));

View file

@ -52,12 +52,14 @@ impl Tokenizer {
fragment_context: Option<super::FragmentContext>, fragment_context: Option<super::FragmentContext>,
parsing_algorithm: ParsingAlgorithm, parsing_algorithm: ParsingAlgorithm,
) -> Self { ) -> Self {
let custom_element_reaction_stack = document.custom_element_reaction_stack();
let sink = Sink { let sink = Sink {
base_url: url, base_url: url,
document: Dom::from_ref(document), document: Dom::from_ref(document),
current_line: Cell::new(1), current_line: Cell::new(1),
script: Default::default(), script: Default::default(),
parsing_algorithm, parsing_algorithm,
custom_element_reaction_stack,
}; };
let quirks_mode = match document.quirks_mode() { let quirks_mode = match document.quirks_mode() {

View file

@ -4,6 +4,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::Cell; use std::cell::Cell;
use std::rc::Rc;
use base::cross_process_instant::CrossProcessInstant; use base::cross_process_instant::CrossProcessInstant;
use base::id::PipelineId; use base::id::PipelineId;
@ -57,6 +58,7 @@ use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::characterdata::CharacterData; use crate::dom::characterdata::CharacterData;
use crate::dom::comment::Comment; use crate::dom::comment::Comment;
use crate::dom::csp::{CspReporting, GlobalCspReporting, Violation, parse_csp_list_from_metadata}; use crate::dom::csp::{CspReporting, GlobalCspReporting, Violation, parse_csp_list_from_metadata};
use crate::dom::customelementregistry::CustomElementReactionStack;
use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
use crate::dom::documentfragment::DocumentFragment; use crate::dom::documentfragment::DocumentFragment;
use crate::dom::documenttype::DocumentType; use crate::dom::documenttype::DocumentType;
@ -244,6 +246,7 @@ impl ServoParser {
allow_declarative_shadow_roots, allow_declarative_shadow_roots,
Some(context_document.insecure_requests_policy()), Some(context_document.insecure_requests_policy()),
context_document.has_trustworthy_ancestor_or_current_origin(), context_document.has_trustworthy_ancestor_or_current_origin(),
context_document.custom_element_reaction_stack(),
can_gc, can_gc,
); );
@ -1156,6 +1159,7 @@ fn insert(
reference_child: Option<&Node>, reference_child: Option<&Node>,
child: NodeOrText<Dom<Node>>, child: NodeOrText<Dom<Node>>,
parsing_algorithm: ParsingAlgorithm, parsing_algorithm: ParsingAlgorithm,
custom_element_reaction_stack: &CustomElementReactionStack,
can_gc: CanGc, can_gc: CanGc,
) { ) {
match child { match child {
@ -1166,11 +1170,11 @@ fn insert(
let element_in_non_fragment = let element_in_non_fragment =
parsing_algorithm != ParsingAlgorithm::Fragment && n.is::<Element>(); parsing_algorithm != ParsingAlgorithm::Fragment && n.is::<Element>();
if element_in_non_fragment { if element_in_non_fragment {
ScriptThread::push_new_element_queue(); custom_element_reaction_stack.push_new_element_queue();
} }
parent.InsertBefore(&n, reference_child, can_gc).unwrap(); parent.InsertBefore(&n, reference_child, can_gc).unwrap();
if element_in_non_fragment { if element_in_non_fragment {
ScriptThread::pop_current_element_queue(can_gc); custom_element_reaction_stack.pop_current_element_queue(can_gc);
} }
}, },
NodeOrText::AppendText(t) => { NodeOrText::AppendText(t) => {
@ -1201,6 +1205,8 @@ pub(crate) struct Sink {
current_line: Cell<u64>, current_line: Cell<u64>,
script: MutNullableDom<HTMLScriptElement>, script: MutNullableDom<HTMLScriptElement>,
parsing_algorithm: ParsingAlgorithm, parsing_algorithm: ParsingAlgorithm,
#[conditional_malloc_size_of]
custom_element_reaction_stack: Rc<CustomElementReactionStack>,
} }
impl Sink { impl Sink {
@ -1278,6 +1284,7 @@ impl TreeSink for Sink {
&self.document, &self.document,
ElementCreator::ParserCreated(self.current_line.get()), ElementCreator::ParserCreated(self.current_line.get()),
parsing_algorithm, parsing_algorithm,
&self.custom_element_reaction_stack,
CanGc::note(), CanGc::note(),
); );
Dom::from_ref(element.upcast()) Dom::from_ref(element.upcast())
@ -1347,6 +1354,7 @@ impl TreeSink for Sink {
Some(sibling), Some(sibling),
new_node, new_node,
self.parsing_algorithm, self.parsing_algorithm,
&self.custom_element_reaction_stack,
CanGc::note(), CanGc::note(),
); );
} }
@ -1366,7 +1374,14 @@ impl TreeSink for Sink {
#[cfg_attr(crown, allow(crown::unrooted_must_root))] #[cfg_attr(crown, allow(crown::unrooted_must_root))]
fn append(&self, parent: &Dom<Node>, child: NodeOrText<Dom<Node>>) { fn append(&self, parent: &Dom<Node>, child: NodeOrText<Dom<Node>>) {
insert(parent, None, child, self.parsing_algorithm, CanGc::note()); insert(
parent,
None,
child,
self.parsing_algorithm,
&self.custom_element_reaction_stack,
CanGc::note(),
);
} }
#[cfg_attr(crown, allow(crown::unrooted_must_root))] #[cfg_attr(crown, allow(crown::unrooted_must_root))]
@ -1479,6 +1494,7 @@ fn create_element_for_token(
document: &Document, document: &Document,
creator: ElementCreator, creator: ElementCreator,
parsing_algorithm: ParsingAlgorithm, parsing_algorithm: ParsingAlgorithm,
custom_element_reaction_stack: &CustomElementReactionStack,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<Element> { ) -> DomRoot<Element> {
// Step 3. // Step 3.
@ -1506,7 +1522,7 @@ fn create_element_for_token(
.perform_a_microtask_checkpoint(can_gc); .perform_a_microtask_checkpoint(can_gc);
} }
// Step 6.3 // Step 6.3
ScriptThread::push_new_element_queue() custom_element_reaction_stack.push_new_element_queue()
} }
// Step 7. // Step 7.
@ -1544,7 +1560,7 @@ fn create_element_for_token(
// Step 9. // Step 9.
if will_execute_script { if will_execute_script {
// Steps 9.1 - 9.2. // Steps 9.1 - 9.2.
ScriptThread::pop_current_element_queue(can_gc); custom_element_reaction_stack.pop_current_element_queue(can_gc);
// Step 9.3. // Step 9.3.
document.decrement_throw_on_dynamic_markup_insertion_counter(); document.decrement_throw_on_dynamic_markup_insertion_counter();
} }

View file

@ -35,6 +35,7 @@ impl Tokenizer {
current_line: Cell::new(1), current_line: Cell::new(1),
script: Default::default(), script: Default::default(),
parsing_algorithm: ParsingAlgorithm::Normal, parsing_algorithm: ParsingAlgorithm::Normal,
custom_element_reaction_stack: document.custom_element_reaction_stack(),
}; };
let tb = XmlTreeBuilder::new(sink, Default::default()); let tb = XmlTreeBuilder::new(sink, Default::default());

View file

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use data_url::mime::Mime; use data_url::mime::Mime;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use net_traits::request::InsecureRequestsPolicy; use net_traits::request::InsecureRequestsPolicy;
@ -17,6 +19,7 @@ use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::reflect_dom_object; use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString; use crate::dom::bindings::str::DOMString;
use crate::dom::customelementregistry::CustomElementReactionStack;
use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
use crate::dom::location::Location; use crate::dom::location::Location;
use crate::dom::node::Node; use crate::dom::node::Node;
@ -44,6 +47,7 @@ impl XMLDocument {
doc_loader: DocumentLoader, doc_loader: DocumentLoader,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool, has_trustworthy_ancestor_origin: bool,
custom_element_reaction_stack: Rc<CustomElementReactionStack>,
) -> XMLDocument { ) -> XMLDocument {
XMLDocument { XMLDocument {
document: Document::new_inherited( document: Document::new_inherited(
@ -64,6 +68,7 @@ impl XMLDocument {
false, false,
inherited_insecure_requests_policy, inherited_insecure_requests_policy,
has_trustworthy_ancestor_origin, has_trustworthy_ancestor_origin,
custom_element_reaction_stack,
), ),
} }
} }
@ -82,6 +87,7 @@ impl XMLDocument {
doc_loader: DocumentLoader, doc_loader: DocumentLoader,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>, inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
has_trustworthy_ancestor_origin: bool, has_trustworthy_ancestor_origin: bool,
custom_element_reaction_stack: Rc<CustomElementReactionStack>,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<XMLDocument> { ) -> DomRoot<XMLDocument> {
let doc = reflect_dom_object( let doc = reflect_dom_object(
@ -98,6 +104,7 @@ impl XMLDocument {
doc_loader, doc_loader,
inherited_insecure_requests_policy, inherited_insecure_requests_policy,
has_trustworthy_ancestor_origin, has_trustworthy_ancestor_origin,
custom_element_reaction_stack,
)), )),
window, window,
can_gc, can_gc,

View file

@ -1535,6 +1535,7 @@ impl XMLHttpRequest {
false, false,
Some(doc.insecure_requests_policy()), Some(doc.insecure_requests_policy()),
doc.has_trustworthy_ancestor_origin(), doc.has_trustworthy_ancestor_origin(),
doc.custom_element_reaction_stack(),
can_gc, can_gc,
) )
} }

View file

@ -5,6 +5,7 @@
use core::fmt; use core::fmt;
#[cfg(feature = "webgpu")] #[cfg(feature = "webgpu")]
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashSet;
use std::option::Option; use std::option::Option;
use std::result::Result; use std::result::Result;
@ -397,10 +398,11 @@ impl ScriptThreadReceivers {
&self, &self,
task_queue: &TaskQueue<MainThreadScriptMsg>, task_queue: &TaskQueue<MainThreadScriptMsg>,
timer_scheduler: &TimerScheduler, timer_scheduler: &TimerScheduler,
fully_active: &HashSet<PipelineId>,
) -> MixedMessage { ) -> MixedMessage {
select! { select! {
recv(task_queue.select()) -> msg => { recv(task_queue.select()) -> msg => {
task_queue.take_tasks(msg.unwrap()); task_queue.take_tasks(msg.unwrap(), fully_active);
let event = task_queue let event = task_queue
.recv() .recv()
.expect("Spurious wake-up of the event-loop, task-queue has no tasks available"); .expect("Spurious wake-up of the event-loop, task-queue has no tasks available");
@ -437,6 +439,7 @@ impl ScriptThreadReceivers {
pub(crate) fn try_recv( pub(crate) fn try_recv(
&self, &self,
task_queue: &TaskQueue<MainThreadScriptMsg>, task_queue: &TaskQueue<MainThreadScriptMsg>,
fully_active: &HashSet<PipelineId>,
) -> Option<MixedMessage> { ) -> Option<MixedMessage> {
if let Ok(message) = self.constellation_receiver.try_recv() { if let Ok(message) = self.constellation_receiver.try_recv() {
let message = message let message = message
@ -449,7 +452,7 @@ impl ScriptThreadReceivers {
.ok()?; .ok()?;
return MixedMessage::FromConstellation(message).into(); return MixedMessage::FromConstellation(message).into();
} }
if let Ok(message) = task_queue.take_tasks_and_recv() { if let Ok(message) = task_queue.take_tasks_and_recv(fully_active) {
return MixedMessage::FromScript(message).into(); return MixedMessage::FromScript(message).into();
} }
if let Ok(message) = self.devtools_server_receiver.try_recv() { if let Ok(message) = self.devtools_server_receiver.try_recv() {

View file

@ -279,7 +279,7 @@ pub struct ScriptThread {
docs_with_no_blocking_loads: DomRefCell<HashSet<Dom<Document>>>, docs_with_no_blocking_loads: DomRefCell<HashSet<Dom<Document>>>,
/// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack> /// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack>
custom_element_reaction_stack: CustomElementReactionStack, custom_element_reaction_stack: Rc<CustomElementReactionStack>,
/// Cross-process access to the compositor's API. /// Cross-process access to the compositor's API.
#[no_trace] #[no_trace]
@ -723,10 +723,8 @@ impl ScriptThread {
with_script_thread(|script_thread| script_thread.is_user_interacting.get()) with_script_thread(|script_thread| script_thread.is_user_interacting.get())
} }
pub(crate) fn get_fully_active_document_ids() -> HashSet<PipelineId> { pub(crate) fn get_fully_active_document_ids(&self) -> HashSet<PipelineId> {
with_script_thread(|script_thread| { self.documents
script_thread
.documents
.borrow() .borrow()
.iter() .iter()
.filter_map(|(id, document)| { .filter_map(|(id, document)| {
@ -740,7 +738,6 @@ impl ScriptThread {
let _ = set.insert(id); let _ = set.insert(id);
set set
}) })
})
} }
pub(crate) fn find_window_proxy(id: BrowsingContextId) -> Option<DomRoot<WindowProxy>> { pub(crate) fn find_window_proxy(id: BrowsingContextId) -> Option<DomRoot<WindowProxy>> {
@ -810,19 +807,13 @@ impl ScriptThread {
.register_paint_worklet_modules(name, properties, painter); .register_paint_worklet_modules(name, properties, painter);
} }
pub(crate) fn push_new_element_queue() { pub(crate) fn custom_element_reaction_stack() -> Rc<CustomElementReactionStack> {
with_script_thread(|script_thread| { with_optional_script_thread(|script_thread| {
script_thread script_thread
.as_ref()
.unwrap()
.custom_element_reaction_stack .custom_element_reaction_stack
.push_new_element_queue(); .clone()
})
}
pub(crate) fn pop_current_element_queue(can_gc: CanGc) {
with_script_thread(|script_thread| {
script_thread
.custom_element_reaction_stack
.pop_current_element_queue(can_gc);
}) })
} }
@ -1006,7 +997,7 @@ impl ScriptThread {
webxr_registry: state.webxr_registry, webxr_registry: state.webxr_registry,
worklet_thread_pool: Default::default(), worklet_thread_pool: Default::default(),
docs_with_no_blocking_loads: Default::default(), docs_with_no_blocking_loads: Default::default(),
custom_element_reaction_stack: CustomElementReactionStack::new(), custom_element_reaction_stack: Rc::new(CustomElementReactionStack::new()),
compositor_api: state.compositor_api, compositor_api: state.compositor_api,
profile_script_events: opts.debug.profile_script_events, profile_script_events: opts.debug.profile_script_events,
print_pwm: opts.print_pwm, print_pwm: opts.print_pwm,
@ -1352,9 +1343,12 @@ impl ScriptThread {
// Receive at least one message so we don't spinloop. // Receive at least one message so we don't spinloop.
debug!("Waiting for event."); debug!("Waiting for event.");
let mut event = self let fully_active = self.get_fully_active_document_ids();
.receivers let mut event = self.receivers.recv(
.recv(&self.task_queue, &self.timer_scheduler.borrow()); &self.task_queue,
&self.timer_scheduler.borrow(),
&fully_active,
);
loop { loop {
debug!("Handling event: {event:?}"); debug!("Handling event: {event:?}");
@ -1464,7 +1458,7 @@ impl ScriptThread {
// If any of our input sources has an event pending, we'll perform another iteration // If any of our input sources has an event pending, we'll perform another iteration
// and check for more resize events. If there are no events pending, we'll move // and check for more resize events. If there are no events pending, we'll move
// on and execute the sequential non-resize events we've seen. // on and execute the sequential non-resize events we've seen.
match self.receivers.try_recv(&self.task_queue) { match self.receivers.try_recv(&self.task_queue, &fully_active) {
Some(new_event) => event = new_event, Some(new_event) => event = new_event,
None => break, None => break,
} }
@ -3422,6 +3416,7 @@ impl ScriptThread {
true, true,
incomplete.load_data.inherited_insecure_requests_policy, incomplete.load_data.inherited_insecure_requests_policy,
incomplete.load_data.has_trustworthy_ancestor_origin, incomplete.load_data.has_trustworthy_ancestor_origin,
self.custom_element_reaction_stack.clone(),
can_gc, can_gc,
); );

View file

@ -15,7 +15,6 @@ use strum::VariantArray;
use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::cell::DomRefCell;
use crate::dom::worker::TrustedWorkerAddress; use crate::dom::worker::TrustedWorkerAddress;
use crate::script_runtime::ScriptThreadEventCategory; use crate::script_runtime::ScriptThreadEventCategory;
use crate::script_thread::ScriptThread;
use crate::task::TaskBox; use crate::task::TaskBox;
use crate::task_source::TaskSourceName; use crate::task_source::TaskSourceName;
@ -197,19 +196,18 @@ impl<T: QueuedTaskConversion> TaskQueue<T> {
} }
/// Take all tasks again and then run `recv()`. /// Take all tasks again and then run `recv()`.
pub(crate) fn take_tasks_and_recv(&self) -> Result<T, ()> { pub(crate) fn take_tasks_and_recv(&self, fully_active: &HashSet<PipelineId>) -> Result<T, ()> {
self.take_tasks(T::wake_up_msg()); self.take_tasks(T::wake_up_msg(), fully_active);
self.recv() self.recv()
} }
/// Drain the queue for the current iteration of the event-loop. /// Drain the queue for the current iteration of the event-loop.
/// Holding-back throttles above a given high-water mark. /// Holding-back throttles above a given high-water mark.
pub(crate) fn take_tasks(&self, first_msg: T) { pub(crate) fn take_tasks(&self, first_msg: T, fully_active: &HashSet<PipelineId>) {
// High-watermark: once reached, throttled tasks will be held-back. // High-watermark: once reached, throttled tasks will be held-back.
const PER_ITERATION_MAX: u64 = 5; const PER_ITERATION_MAX: u64 = 5;
let fully_active = ScriptThread::get_fully_active_document_ids();
// Always first check for new tasks, but don't reset 'taken_task_counter'. // Always first check for new tasks, but don't reset 'taken_task_counter'.
self.process_incoming_tasks(first_msg, &fully_active); self.process_incoming_tasks(first_msg, fully_active);
let mut throttled = self.throttled.borrow_mut(); let mut throttled = self.throttled.borrow_mut();
let mut throttled_length: usize = throttled.values().map(|queue| queue.len()).sum(); let mut throttled_length: usize = throttled.values().map(|queue| queue.len()).sum();
let mut task_source_cycler = TaskSourceName::VARIANTS.iter().cycle(); let mut task_source_cycler = TaskSourceName::VARIANTS.iter().cycle();