1use std::cell::RefCell;
4use std::rc::Rc;
5use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender, channel};
6use std::sync::{Arc, Mutex};
7use std::task::{Context as TaskContext, Waker};
8
9use blitz_dom::{
10 BaseDocument, DEFAULT_CSS, DocGuard, DocGuardMut, Document, DocumentConfig, EventDriver, NodeId,
11};
12use blitz_html::stream::StreamingParser;
13use blitz_html::{DocumentHtmlParser, HtmlProvider};
14use blitz_traits::events::{BlitzPointerEvent, DomEvent, UiEvent};
15use url::Url;
16use web_time::{Duration, Instant};
17
18use crate::event_handler::ScriptEventHandler;
19use crate::fetch::{DefaultScriptFetcher, ScriptFetcher};
20use crate::module::SharedFetcher;
21use crate::runtime::ScriptRuntime;
22
23type PollHook =
24 Box<dyn for<'a> FnMut(&mut ScriptDocument, Option<&TaskContext<'a>>) -> bool + 'static>;
25
26#[derive(Clone, Copy, PartialEq, Eq)]
28enum ScriptKind {
29 Classic,
30 Module,
33 ImportMap,
36}
37
38struct PendingScript {
40 node_id: NodeId,
41 src: Option<String>,
42 inline_text: String,
43 kind: ScriptKind,
44 deferred: bool,
50}
51
52pub struct ScriptDocument {
61 inner: Rc<RefCell<BaseDocument>>,
62 runtime: ScriptRuntime,
63 base_url: Option<Url>,
64 fetcher: SharedFetcher,
71 scripts_executed: bool,
72 startup_events_pending: bool,
73 load_event_pending: bool,
74 executed_scripts: std::collections::HashSet<NodeId>,
81 poll_hook: Option<PollHook>,
82
83 waker: Arc<Mutex<Option<Waker>>>,
86 timer_thread: Option<Sender<Instant>>,
87}
88
89impl ScriptDocument {
90 pub fn from_html(html: &str, mut config: DocumentConfig) -> Self {
99 if let Some(ss) = &mut config.ua_stylesheets {
100 if !ss.iter().any(|s| s == DEFAULT_CSS) {
101 ss.push(String::from(DEFAULT_CSS));
102 }
103 } else {
104 config.ua_stylesheets = Some(vec![String::from(DEFAULT_CSS)]);
105 }
106 if config.html_parser_provider.is_none() {
107 config.html_parser_provider = Some(Arc::new(HtmlProvider));
108 }
109
110 let base_url = config
111 .base_url
112 .as_deref()
113 .and_then(|url| Url::parse(url).ok());
114
115 let is_xml = DocumentHtmlParser::is_xhtml_document(html);
116 let mut doc = BaseDocument::new(config);
117 if is_xml {
118 let mut mutr = doc.mutate();
119 DocumentHtmlParser::parse_into_mutator(&mut mutr, html);
120 drop(mutr);
121 }
122
123 let inner = Rc::new(RefCell::new(doc));
124 let parser = if is_xml {
125 None
126 } else {
127 let mut parser = StreamingParser::new(Rc::clone(&inner), html);
128 if parser.next_script().is_some() {
129 Some(parser)
130 } else {
131 parser.finish();
132 None
133 }
134 };
135 let fetcher: SharedFetcher = Rc::new(RefCell::new(Rc::new(DefaultScriptFetcher)));
136 let runtime = ScriptRuntime::new(Rc::clone(&inner), base_url.as_ref(), Rc::clone(&fetcher));
137 crate::docwrite::prepare(&runtime.context, html, Rc::clone(&fetcher), parser);
138
139 Self {
140 inner,
141 runtime,
142 base_url,
143 fetcher,
144 scripts_executed: false,
145 startup_events_pending: false,
146 load_event_pending: false,
147 executed_scripts: std::collections::HashSet::new(),
148 poll_hook: None,
149 waker: Arc::new(Mutex::new(None)),
150 timer_thread: None,
151 }
152 }
153
154 pub fn set_job_budget(&mut self, duration: Duration) {
160 self.runtime.set_job_budget(duration);
161 }
162
163 pub fn job_budget(&self) -> Duration {
165 self.runtime.job_budget()
166 }
167
168 pub fn has_pending_jobs(&self) -> bool {
172 self.runtime.jobs_remain()
173 }
174
175 pub fn pending_task_count(&self) -> u64 {
178 self.runtime.pending_task_count()
179 }
180
181 pub fn run_until_idle(&mut self) -> Result<(), &'static str> {
206 const MODULE_TIMEOUT: Duration = Duration::from_secs(60);
207 const LOAD_POLL_INTERVAL: Duration = Duration::from_millis(5);
208 let module_deadline = Instant::now() + MODULE_TIMEOUT;
209
210 loop {
211 let progressed = self.poll(None);
212 let now = Instant::now();
213 let next_timer = self.runtime.next_timer_deadline();
214 let loads_pending = self.runtime.has_pending_module_loads();
215 let evaluations_pending = self.runtime.has_pending_module_evaluations();
216
217 if now >= module_deadline {
220 if loads_pending {
221 return Err("module loading did not complete within sixty seconds");
222 }
223 if evaluations_pending {
224 return Err("module evaluation did not complete within sixty seconds");
225 }
226 }
227
228 if self.runtime.jobs_remain()
229 || next_timer.is_some_and(|deadline| deadline <= now)
230 || progressed
231 {
232 continue;
233 }
234
235 if loads_pending || evaluations_pending {
236 let wait = if loads_pending {
240 LOAD_POLL_INTERVAL
241 } else {
242 next_timer.map_or(LOAD_POLL_INTERVAL, |deadline| {
243 deadline.saturating_duration_since(now)
244 })
245 };
246 let wait = next_timer.map_or(wait, |deadline| {
247 wait.min(deadline.saturating_duration_since(now))
248 });
249 std::thread::sleep(wait.min(module_deadline.saturating_duration_since(now)));
250 continue;
251 }
252
253 if crate::docwrite::active(&self.runtime.context)
254 || self.startup_events_pending
255 || self.collect_scripts().iter().any(|script| {
256 !self.executed_scripts.contains(&script.node_id)
257 && !crate::docwrite::was_executed(&self.runtime.context, script.node_id)
258 })
259 {
260 return Err("script startup cannot make progress");
261 }
262
263 return Ok(());
264 }
265 }
266
267 pub fn next_timer_deadline(&self) -> Option<std::time::Instant> {
271 self.runtime.next_timer_deadline()
272 }
273
274 pub fn enter_execution_time_limit(&self) -> boa_engine::vm::ExecutionScope {
279 self.runtime.context.enter_execution_time_limit()
280 }
281 pub fn with_fetcher(self, fetcher: impl ScriptFetcher) -> Self {
284 *self.fetcher.borrow_mut() = Rc::new(fetcher);
285 self
286 }
287
288 pub fn set_ipc_handler(&mut self, handler: impl Fn(String) + 'static) {
293 self.runtime.ctx.state.borrow_mut().ipc_handler = Some(Rc::new(handler));
294 }
295
296 pub fn set_poll_hook(
300 &mut self,
301 hook: impl for<'a> FnMut(&mut ScriptDocument, Option<&TaskContext<'a>>) -> bool + 'static,
302 ) {
303 self.poll_hook = Some(Box::new(hook));
304 }
305
306 pub fn add_poll_hook(
309 &mut self,
310 mut hook: impl for<'a> FnMut(&mut ScriptDocument, Option<&TaskContext<'a>>) -> bool + 'static,
311 ) {
312 let Some(mut existing) = self.poll_hook.take() else {
313 self.poll_hook = Some(Box::new(hook));
314 return;
315 };
316 self.poll_hook = Some(Box::new(move |document, task_context| {
317 let ran_existing = existing(document, task_context);
318 let ran_added = hook(document, task_context);
319 ran_existing | ran_added
320 }));
321 }
322
323 pub fn execute_scripts(&mut self) {
328 let _execution = self.enter_execution_time_limit();
329 let _profiling = self.runtime.ctx.enter_profiling_boundary();
330 let _slice = self.runtime.begin_job_slice();
331 if self.scripts_executed {
332 return;
333 }
334 self.scripts_executed = true;
335 self.startup_events_pending = true;
336
337 crate::docwrite::begin(&mut self.runtime.context);
338 self.resume_parser_and_scripts();
339 self.finish_startup_events();
340
341 self.request_redraw();
342 self.arm_timer_thread();
343 }
344
345 pub fn external_script_urls(&self) -> Vec<Url> {
353 if let Some(original) = crate::docwrite::original(&self.runtime.context) {
354 return blitz_html::scan_script_tags(&original)
357 .into_iter()
358 .filter(|script| script.is_javascript())
359 .filter_map(|script| script.src)
360 .filter_map(|src| self.resolve_script_url(src.as_ref()))
361 .collect();
362 }
363 self.collect_scripts()
364 .iter()
365 .filter(|script| script.kind != ScriptKind::ImportMap)
368 .filter_map(|script| script.src.as_deref())
369 .filter_map(|src| self.resolve_script_url(src))
370 .collect()
371 }
372
373 fn resolve_script_url(&self, src: &str) -> Option<Url> {
375 match &self.base_url {
376 Some(base) => base.join(src).ok(),
377 None => Url::parse(src).ok(),
378 }
379 }
380
381 pub fn with_js_context<R>(
387 &mut self,
388 callback: impl FnOnce(&mut boa_engine::Context) -> R,
389 ) -> R {
390 let _execution = self.enter_execution_time_limit();
391 callback(&mut self.runtime.context)
392 }
393
394 pub fn eval(&mut self, code: &str) {
396 let _execution = self.enter_execution_time_limit();
397 let _profiling = self.runtime.ctx.enter_profiling_boundary();
398 self.runtime.eval(code, "<eval>");
399 self.request_redraw();
400 self.arm_timer_thread();
401 }
402
403 pub fn eval_named(&mut self, code: &str, name: &str) {
406 let _execution = self.enter_execution_time_limit();
407 let _profiling = self.runtime.ctx.enter_profiling_boundary();
408 self.runtime.eval_internal(code, name);
409 self.runtime.run_jobs(name);
410 self.request_redraw();
411 self.arm_timer_thread();
412 }
413
414 pub fn eval_json(&mut self, code: &str) -> Result<serde_json::Value, String> {
420 let _execution = self.enter_execution_time_limit();
421 let _profiling = self.runtime.ctx.enter_profiling_boundary();
422 let result = self.runtime.eval_json(code, "<eval with result>");
423 self.request_redraw();
424 self.arm_timer_thread();
425 result
426 }
427
428 #[cfg(feature = "debug-control")]
429 pub fn diagnostic_batch(
430 &self,
431 console_after: u64,
432 errors_after: u64,
433 limit: usize,
434 ) -> crate::DiagnosticBatch {
435 self.runtime
436 .diagnostic_batch(console_after, errors_after, limit)
437 }
438
439 #[cfg(feature = "debug-control")]
440 pub(crate) fn console_entries_after(
441 &self,
442 sequence: u64,
443 ) -> Vec<crate::runtime::DiagnosticEntry> {
444 self.runtime.console_entries_after(sequence)
445 }
446
447 #[cfg(feature = "debug-control")]
448 pub(crate) fn runtime_errors_after(
449 &self,
450 sequence: u64,
451 ) -> Vec<crate::runtime::DiagnosticEntry> {
452 self.runtime.runtime_errors_after(sequence)
453 }
454
455 pub fn current_url(&self) -> Option<&Url> {
457 self.base_url.as_ref()
458 }
459
460 pub fn page_source(&self) -> String {
462 self.inner.borrow().root_element().outer_html()
463 }
464
465 pub fn dispatch_dom_event(&mut self, event: DomEvent) {
470 let _execution = self.enter_execution_time_limit();
471 let profiling_boundary = self.runtime.ctx.enter_profiling_boundary();
472 let profiling = profiling_boundary.enabled();
473 let handler = ScriptEventHandler {
474 runtime: &mut self.runtime,
475 profiling,
476 };
477 let mut driver = EventDriver::new(&mut self.inner, handler);
478 driver.handle_dom_event(event);
479
480 self.request_redraw();
481 self.arm_timer_thread();
482 }
483
484 fn resume_parser_and_scripts(&mut self) -> bool {
490 let _slice = self.runtime.begin_job_slice();
491 let mut ran = false;
492 loop {
493 let progressed = self.run_pending_scripts();
494 ran |= progressed;
495 if !progressed
496 || self.runtime.jobs_remain()
497 || !crate::docwrite::active(&self.runtime.context)
498 {
499 break;
500 }
501 }
502 ran
503 }
504
505 fn run_pending_scripts(&mut self) -> bool {
518 let _slice = self.runtime.begin_job_slice();
519 if self.runtime.jobs_remain() || self.runtime.job_slice_exhausted() {
520 return false;
521 }
522 if crate::docwrite::take_restart(&self.runtime.context) {
523 self.executed_scripts.clear();
524 self.startup_events_pending = true;
525 self.load_event_pending = false;
526 }
527 let parsed = crate::docwrite::advance(&mut self.runtime.context);
528 let generation = crate::docwrite::generation(&self.runtime.context);
529 if parsed {
530 self.runtime.ctx.mark_layout_dirty();
531 }
532 let pending: Vec<PendingScript> = self
535 .collect_scripts()
536 .into_iter()
537 .filter(|script| !self.executed_scripts.contains(&script.node_id))
538 .filter(|script| {
539 !crate::docwrite::was_executed(&self.runtime.context, script.node_id)
540 && (!script.deferred || !crate::docwrite::active(&self.runtime.context))
541 })
542 .collect();
543 let ran = parsed || !pending.is_empty();
544
545 for script in &pending {
550 if script.kind == ScriptKind::ImportMap && !script.inline_text.trim().is_empty() {
551 self.runtime
552 .set_import_map(&script.inline_text, self.base_url.as_ref());
553 }
554 }
555
556 let (deferred, immediate): (Vec<_>, Vec<_>) =
567 pending.into_iter().partition(|script| script.deferred);
568
569 for script in immediate.into_iter().chain(deferred) {
570 if self.runtime.job_slice_exhausted() {
571 break;
572 }
573 if generation != crate::docwrite::generation(&self.runtime.context) {
574 break;
575 }
576 if self.startup_events_pending && script.deferred {
577 self.runtime.set_document_ready_state("interactive");
578 if self.runtime.jobs_remain() {
579 break;
580 }
581 }
582 self.executed_scripts.insert(script.node_id);
586 if script.kind == ScriptKind::ImportMap {
587 continue;
588 }
589 match script.src {
590 Some(src) => {
591 let Some(url) = self.resolve_script_url(&src) else {
592 eprintln!("blitz-script: could not resolve script URL {src:?}");
593 self.runtime.dispatch_node_event(script.node_id, "error");
594 if self.runtime.jobs_remain() {
595 break;
596 }
597 continue;
598 };
599 let fetcher = Rc::clone(&self.fetcher.borrow());
600 match fetcher.fetch(&url) {
601 Ok(code) => {
602 if script.kind == ScriptKind::Module {
603 self.runtime.eval_module(&code, Some(&url), url.as_str());
604 } else {
605 crate::domc::set_current_script(
606 &self.runtime.context,
607 Some(script.node_id),
608 );
609 self.runtime.eval_at(&code, Some(&url), url.as_str());
610 }
611 self.runtime.dispatch_node_event(script.node_id, "load");
612 }
613 Err(error) => {
614 eprintln!("blitz-script: failed to fetch script {url}: {error}");
615 self.runtime.dispatch_node_event(script.node_id, "error");
616 }
617 }
618 }
619 None => {
620 if !script.inline_text.trim().is_empty() {
621 let base_url = self.base_url.clone();
625 if script.kind == ScriptKind::Module {
626 self.runtime.eval_module(
627 &script.inline_text,
628 base_url.as_ref(),
629 "<inline module>",
630 );
631 } else {
632 crate::domc::set_current_script(
633 &self.runtime.context,
634 Some(script.node_id),
635 );
636 self.runtime.eval_at(
637 &script.inline_text,
638 base_url.as_ref(),
639 "<inline script>",
640 );
641 }
642 }
643 }
644 }
645 if self.runtime.jobs_remain() {
648 break;
649 }
650 }
651 ran
652 }
653
654 fn finish_startup_events(&mut self) -> bool {
655 if !(self.startup_events_pending || self.load_event_pending)
656 || self.runtime.jobs_remain()
657 || crate::docwrite::active(&self.runtime.context)
658 || self.runtime.job_slice_exhausted()
659 {
660 return false;
661 }
662 if self.startup_events_pending
663 && (self.runtime.has_pending_module_evaluations()
664 || self.collect_scripts().iter().any(|script| {
665 !self.executed_scripts.contains(&script.node_id)
666 && !crate::docwrite::was_executed(&self.runtime.context, script.node_id)
667 }))
668 {
669 return false;
670 }
671
672 let mut ran = false;
673 if self.startup_events_pending {
674 ran |= self.runtime.set_document_ready_state("interactive");
675 if self.runtime.jobs_remain() {
676 return ran;
677 }
678 self.startup_events_pending = false;
679 self.load_event_pending = true;
680 self.runtime.dispatch_document_event("DOMContentLoaded");
681 ran = true;
682 }
683 let resources_pending = {
684 let doc = self.inner.borrow();
685 doc.has_pending_critical_resources() || doc.pending_image_count() != 0
686 };
687 if self.load_event_pending
688 && !self.runtime.job_slice_exhausted()
689 && !self.runtime.jobs_remain()
690 && !resources_pending
691 && !self.runtime.has_pending_module_evaluations()
692 {
693 ran |= self.runtime.set_document_ready_state("complete");
694 if !self.runtime.jobs_remain() {
695 self.load_event_pending = false;
696 self.runtime.dispatch_window_event("load");
697 ran = true;
698 }
699 }
700 ran
701 }
702
703 fn collect_scripts(&self) -> Vec<PendingScript> {
705 let doc = self.inner.borrow();
706 let mut scripts = Vec::new();
707 let mut stack = vec![doc.root_node().id];
708
709 while let Some(node_id) = stack.pop() {
710 let Some(node) = doc.get_node(node_id) else {
711 continue;
712 };
713
714 if let Some(element) = node.element_data() {
715 if element.name.local == blitz_dom::local_name!("script") {
716 if node
717 .flags
718 .contains(blitz_dom::node::NodeFlags::IS_PARSER_INERT_SCRIPT)
719 {
720 continue;
721 }
722 let script_type = element
724 .attr(blitz_dom::local_name!("type"))
725 .unwrap_or("")
726 .trim()
727 .to_ascii_lowercase();
728 let kind = match script_type.as_str() {
729 "module" => Some(ScriptKind::Module),
730 "importmap" => Some(ScriptKind::ImportMap),
731 "" | "text/javascript" | "application/javascript" => {
732 if element.attr(blitz_dom::local_name!("nomodule")).is_some() {
737 None
738 } else {
739 Some(ScriptKind::Classic)
740 }
741 }
742 _ => None,
743 };
744
745 if let Some(kind) = kind {
746 let src = element
747 .attr(blitz_dom::local_name!("src"))
748 .map(str::to_string);
749 let is_async = element.attr(blitz_dom::local_name!("async")).is_some();
750
751 let deferred = !is_async
755 && match kind {
756 ScriptKind::Module => true,
757 ScriptKind::Classic => {
758 src.is_some()
759 && element.attr(blitz_dom::local_name!("defer")).is_some()
760 }
761 ScriptKind::ImportMap => false,
762 };
763
764 scripts.push(PendingScript {
765 node_id,
766 src,
767 inline_text: node.text_content(),
768 kind,
769 deferred,
770 });
771 }
772 continue;
773 }
774 }
775
776 stack.extend(node.children.iter().rev().copied());
777 }
778
779 scripts
780 }
781
782 fn request_redraw(&self) {
783 self.inner.borrow().shell_provider.request_redraw();
784 }
785
786 fn arm_timer_thread(&mut self) {
789 let Some(deadline) = self.runtime.next_timer_deadline() else {
790 return;
791 };
792
793 let sender = self.timer_thread.get_or_insert_with(|| {
794 let (tx, rx) = channel::<Instant>();
795 let waker = Arc::clone(&self.waker);
796 std::thread::Builder::new()
797 .name("blitz-script-timers".to_string())
798 .spawn(move || timer_thread_main(rx, waker))
799 .expect("failed to spawn timer thread");
800 tx
801 });
802
803 if sender.send(deadline).is_err() {
806 self.timer_thread = None;
807 }
808 }
809}
810
811fn timer_thread_main(rx: Receiver<Instant>, waker: Arc<Mutex<Option<Waker>>>) {
813 let mut deadline: Option<Instant> = None;
814
815 loop {
816 match deadline {
817 None => match rx.recv() {
818 Ok(new_deadline) => deadline = Some(new_deadline),
819 Err(_) => return,
820 },
821 Some(current) => {
822 let now = Instant::now();
823 if current <= now {
824 if let Some(waker) = waker.lock().unwrap().as_ref() {
825 waker.wake_by_ref();
826 }
827 deadline = None;
828 continue;
829 }
830 match rx.recv_timeout(current - now) {
831 Ok(new_deadline) => deadline = Some(new_deadline.min(current)),
832 Err(RecvTimeoutError::Timeout) => {
833 if let Some(waker) = waker.lock().unwrap().as_ref() {
834 waker.wake_by_ref();
835 }
836 deadline = None;
837 }
838 Err(RecvTimeoutError::Disconnected) => return,
839 }
840 }
841 }
842 }
843}
844
845impl Document for ScriptDocument {
846 fn inner(&self) -> DocGuard<'_> {
847 DocGuard::RefCell(self.inner.borrow())
848 }
849
850 fn inner_mut(&mut self) -> DocGuardMut<'_> {
851 DocGuardMut::RefCell(self.inner.borrow_mut())
852 }
853
854 fn handle_ui_event(&mut self, event: UiEvent) {
855 let _execution = self.enter_execution_time_limit();
856 let profiling_boundary = self.runtime.ctx.enter_profiling_boundary();
857 let profiling = profiling_boundary.enabled();
858 let handler = ScriptEventHandler {
859 runtime: &mut self.runtime,
860 profiling,
861 };
862 let mut driver = EventDriver::new(&mut self.inner, handler);
863 driver.handle_ui_event(event);
864
865 self.request_redraw();
867 self.arm_timer_thread();
868 }
869
870 fn poll(&mut self, task_context: Option<TaskContext>) -> bool {
871 self.poll_with_timer_policy(task_context, None)
872 }
873
874 fn poll_for_settle(&mut self, task_context: Option<TaskContext>, run_timers: bool) -> bool {
875 self.poll_with_timer_policy(task_context, Some(run_timers))
876 }
877}
878
879impl ScriptDocument {
880 fn poll_with_timer_policy(
882 &mut self,
883 task_context: Option<TaskContext>,
884 settle_timers: Option<bool>,
885 ) -> bool {
886 let _execution = self.enter_execution_time_limit();
887 let profiling_boundary = self.runtime.ctx.enter_profiling_boundary();
888 let profiling = profiling_boundary.enabled();
889 let poll_started = profiling.then(std::time::Instant::now);
890 let _slice = self.runtime.begin_job_slice();
891 let ran = self.poll_inner(task_context, profiling, settle_timers);
892 if let Some(started) = poll_started {
893 crate::script_stats::record_poll(started.elapsed(), ran);
894 }
895 ran
896 }
897
898 pub fn handle_pointer_move_to_node(&mut self, event: BlitzPointerEvent, node_id: NodeId) {
905 let _execution = self.enter_execution_time_limit();
906 let profiling_boundary = self.runtime.ctx.enter_profiling_boundary();
907 let profiling = profiling_boundary.enabled();
908 let handler = ScriptEventHandler {
909 runtime: &mut self.runtime,
910 profiling,
911 };
912 let mut driver = EventDriver::new(&mut self.inner, handler);
913 driver.handle_pointer_move_to_node(&event, node_id);
914
915 self.request_redraw();
916 self.arm_timer_thread();
917 }
918
919 pub fn handle_ui_event_to_node(&mut self, event: UiEvent, node_id: NodeId) {
923 let _execution = self.enter_execution_time_limit();
924 let profiling_boundary = self.runtime.ctx.enter_profiling_boundary();
925 let profiling = profiling_boundary.enabled();
926 let handler = ScriptEventHandler {
927 runtime: &mut self.runtime,
928 profiling,
929 };
930 let mut driver = EventDriver::new(&mut self.inner, handler);
931 driver.handle_ui_event_to_node(event, node_id);
932
933 self.request_redraw();
934 self.arm_timer_thread();
935 }
936
937 fn poll_inner(
940 &mut self,
941 task_context: Option<TaskContext>,
942 profiling: bool,
943 settle_timers: Option<bool>,
944 ) -> bool {
945 if let Some(cx) = &task_context {
947 let mut waker = self.waker.lock().unwrap();
948 let stale = waker
949 .as_ref()
950 .map(|old| !old.will_wake(cx.waker()))
951 .unwrap_or(true);
952 if stale {
953 *waker = Some(cx.waker().clone());
954 }
955 }
956
957 let subdocument_changes = {
962 let mut inner = self.inner.borrow_mut();
963 let waker = task_context.as_ref().map(TaskContext::waker);
964 match settle_timers {
965 Some(run_timers) => inner.poll_subdocuments_for_settle(waker, run_timers),
966 None => inner.poll_subdocuments(waker),
967 }
968 };
969
970 let mut ran = subdocument_changes;
971 ran |= self.runtime.poll_domc();
972 ran |= self.runtime.run_jobs_with_progress("poll microtasks");
975
976 if !self.scripts_executed {
978 let started = profiling.then(std::time::Instant::now);
982 self.execute_scripts();
983 if let Some(started) = started {
984 crate::script_stats::record_work("startup:execute_scripts", started.elapsed());
985 }
986 ran = true;
987 } else {
988 ran |= self.resume_parser_and_scripts();
993 }
994 ran |= self.finish_startup_events();
995
996 if settle_timers.unwrap_or(true) {
997 ran |= self.runtime.run_due_timers(profiling);
998 }
999
1000 self.runtime.poll_module_evaluations();
1004
1005 if let Some(mut hook) = self.poll_hook.take() {
1006 let started = profiling.then(std::time::Instant::now);
1010 ran |= hook(self, task_context.as_ref());
1011 if let Some(started) = started {
1012 crate::script_stats::record_work("poll_hook", started.elapsed());
1013 }
1014 self.poll_hook = Some(hook);
1015 }
1016
1017 self.runtime.sweep_detached_nodes();
1022
1023 if self.runtime.jobs_remain() {
1024 ran = true;
1025 self.request_redraw();
1026 if let Some(cx) = &task_context {
1027 cx.waker().wake_by_ref();
1028 }
1029 }
1030 self.arm_timer_thread();
1031 ran
1032 }
1033}