Files
moli/moli-parser/src/lib.rs
T
ldm0 7cf5c836bb fix(parser): checkpoint microtasks before preparing parser scripts
Yield live HTML script preparation to the document owner after releasing
parser borrows. Prepare from the current DOM after the guarded microtask
checkpoint, and abandon stale handoffs when that checkpoint replaces the
document. Apply the boundary to main documents, child documents, and
document.write; preserve eager parsing when no runtime owner is installed.

Admit parser async classic scripts after preparation and keep nested
script cleanup from draining microtasks while an outer script is running.
Add main/child integration coverage and update the two repaired WPT cases.

Validation:
- cargo fmt --all
- cargo clippy --workspace --all-targets --all-features -- -D warnings
- cargo nextest run --no-fail-fast: 17,846 passed, 13 skipped
- 245 related WPT cases: all 231 previously passing cases remain passing
- Final CLI build: both repaired WPT cases pass on the validated sources
2026-09-13 17:02:39 +08:00

130 lines
4.6 KiB
Rust

//! HTML/XML parsing and parser-neutral handoff types for Moli.
//!
//! This crate owns document construction, parser streaming, and parse-time
//! discovery outputs without depending on the renderer implementation.
mod html;
mod html_input;
mod live_target;
mod script_planning;
mod session;
mod stream;
mod stylesheet_blocking;
mod xml;
mod xml_preprocess;
mod xml_stream;
mod xml_tree_viewer;
use std::collections::{HashMap, HashSet, VecDeque};
use html5ever::tree_builder::QuirksMode;
use moli_dom::native::NativeNodeId;
use moli_stylesheet_blocking::{
DocumentBlockingStylesheetSignature, DocumentOwnedBlockingStylesheetDiscoveryInput,
};
pub use html::{
DocumentStream, HtmlParser, ParserBlockingStylesheetPause,
ParserCustomElementConstructionHandoff, ParserFinishDiscoverySignals, ParserInputContext,
ParserInputQueue, ParserInputSession, ParserPumpOutcome, ParserPumpStep,
ParserScriptElementStateTransition, ParserScriptHandoff, ParserScriptNoExecutionOutcome,
ParserScriptPreparationFailure, ParserScriptPreparationRequest, ParserStreamDocumentSnapshot,
ParserYield,
};
pub use live_target::{
ParserDomMutation, ParserDomMutationConsumer, ParserDomReadConsumer,
ParserElementCreationConsumer, ParserElementCreationRequest, ParserMutationEffectConsumer,
};
pub use script_planning::{
ParserPlanningReadView, ParserScriptRead, PrepareScriptOutcome, PreparedImportMap,
PreparedImportMapSource, PreparedScript, ScriptFetchMetadata, ScriptFilterSkipReason,
ScriptSource, build_prepared_import_map, build_prepared_script, classify_parser_script,
};
pub use xml::XmlParser;
pub use xml_stream::XmlDocumentStream;
const STREAM_CHUNK_BYTES: usize = 8 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ParserSourcePosition {
line: u64,
column: u64,
}
impl ParserSourcePosition {
const UNKNOWN: Self = Self { line: 0, column: 0 };
const fn line_only(line: u64) -> Self {
Self { line, column: 0 }
}
}
impl Default for ParserSourcePosition {
fn default() -> Self {
Self::line_only(1)
}
}
#[derive(Debug)]
struct HtmlTreeSinkState {
html_quirks_mode: QuirksMode,
ready_parser_scripts: VecDeque<NativeNodeId>,
discovered_async_prefetch_candidates: VecDeque<NativeNodeId>,
discovered_modulepreload_link_candidates: VecDeque<NativeNodeId>,
parser_meta_csp_candidates: HashSet<NativeNodeId>,
discovered_parser_meta_csp_candidates: VecDeque<NativeNodeId>,
defined_autonomous_custom_elements: HashSet<String>,
pending_custom_element_construction_handoffs:
VecDeque<html::ParserCustomElementConstructionHandoff>,
pending_blocking_stylesheet_pause: Option<NativeNodeId>,
discovered_blocking_stylesheet_inputs: VecDeque<DocumentOwnedBlockingStylesheetDiscoveryInput>,
captured_blocking_stylesheet_nodes: HashSet<NativeNodeId>,
captured_blocking_stylesheet_signatures: HashSet<DocumentBlockingStylesheetSignature>,
finishing_tree_builder: bool,
non_nonceable_parser_scripts: HashSet<NativeNodeId>,
current_position: ParserSourcePosition,
script_start_positions: HashMap<NativeNodeId, ParserSourcePosition>,
}
impl Default for HtmlTreeSinkState {
fn default() -> Self {
Self {
html_quirks_mode: QuirksMode::NoQuirks,
ready_parser_scripts: VecDeque::new(),
discovered_async_prefetch_candidates: VecDeque::new(),
discovered_modulepreload_link_candidates: VecDeque::new(),
parser_meta_csp_candidates: HashSet::new(),
discovered_parser_meta_csp_candidates: VecDeque::new(),
defined_autonomous_custom_elements: HashSet::new(),
pending_custom_element_construction_handoffs: VecDeque::new(),
pending_blocking_stylesheet_pause: None,
discovered_blocking_stylesheet_inputs: VecDeque::new(),
captured_blocking_stylesheet_nodes: HashSet::new(),
captured_blocking_stylesheet_signatures: HashSet::new(),
finishing_tree_builder: false,
non_nonceable_parser_scripts: HashSet::new(),
current_position: ParserSourcePosition::default(),
script_start_positions: HashMap::new(),
}
}
}
pub(crate) fn html_chunks(html: &str) -> impl Iterator<Item = &str> {
let mut chunks = Vec::new();
let mut start = 0;
while start < html.len() {
let mut end = (start + STREAM_CHUNK_BYTES).min(html.len());
while end > start && !html.is_char_boundary(end) {
end -= 1;
}
if end == start {
end = html.len();
}
chunks.push(&html[start..end]);
start = end;
}
chunks.into_iter()
}