Files
moli/moli-script/src/lib.rs
T
2026-08-11 00:10:12 +08:00

299 lines
9.9 KiB
Rust

//! Shared script classification policy for parser and renderer code.
//!
//! This crate owns the renderer-neutral rules for script kind/mode decisions
//! so parser planning and runtime scheduling agree without copying
//! classification logic.
mod classify;
mod scheduling;
pub use classify::{
classify_script_element, classify_script_kind, classify_script_mode,
classify_script_preparation, html_script_element_supports_type,
};
pub use scheduling::{
ScriptElementClassification, ScriptElementClassificationInput, ScriptPreparationClassification,
ScriptPreparationClassificationInput, ScriptPreparationDisposition, ScriptSchedulingInput,
};
#[cfg(test)]
mod tests {
use moli_page_types::{ScriptKind, ScriptMode, ScriptSourceKind};
use crate::{
ScriptElementClassificationInput, ScriptPreparationClassificationInput,
ScriptPreparationDisposition, ScriptSchedulingInput, classify_script_element,
classify_script_kind, classify_script_mode, classify_script_preparation,
html_script_element_supports_type,
};
#[test]
fn classifies_module_and_dynamic_external_classic_modes() {
assert_eq!(
classify_script_mode(ScriptSchedulingInput {
parser_inserted: true,
allow_parser_blocking_modes: true,
force_async: false,
async_attribute_present: false,
defer_attribute_present: false,
kind: ScriptKind::Module,
source_kind: ScriptSourceKind::External,
}),
Some(ScriptMode::ModuleDefer)
);
assert_eq!(
classify_script_mode(ScriptSchedulingInput {
parser_inserted: false,
allow_parser_blocking_modes: false,
force_async: false,
async_attribute_present: false,
defer_attribute_present: false,
kind: ScriptKind::Classic,
source_kind: ScriptSourceKind::External,
}),
Some(ScriptMode::InOrder)
);
}
#[test]
fn classifies_script_type_attribute_values() {
assert_eq!(classify_script_kind(None), ScriptKind::Classic);
assert_eq!(classify_script_kind(Some("")), ScriptKind::Classic);
assert_eq!(classify_script_kind(Some(" ")), ScriptKind::DataBlock);
assert_eq!(
classify_script_kind(Some("text/javascript")),
ScriptKind::Classic
);
assert_eq!(
classify_script_kind(Some(" text/javascript\t")),
ScriptKind::Classic
);
assert_eq!(
classify_script_kind(Some("text/javascript; charset=utf-8")),
ScriptKind::DataBlock
);
assert_eq!(
classify_script_kind(Some("application/x-javascript")),
ScriptKind::Classic
);
assert_eq!(
classify_script_kind(Some("TEXT/JAVASCRIPT1.5")),
ScriptKind::Classic
);
assert_eq!(
classify_script_kind(Some("javascript")),
ScriptKind::DataBlock
);
assert_eq!(
classify_script_kind(Some("text/javascript\u{000B}")),
ScriptKind::DataBlock
);
assert_eq!(
classify_script_kind(Some("text/javascript\u{00A0}")),
ScriptKind::DataBlock
);
assert_eq!(
classify_script_kind(Some("text/javascript1.6")),
ScriptKind::DataBlock
);
assert_eq!(classify_script_kind(Some("module")), ScriptKind::Module);
assert_eq!(
classify_script_kind(Some("importmap")),
ScriptKind::ImportMap
);
assert_eq!(
classify_script_kind(Some("application/json")),
ScriptKind::DataBlock
);
}
#[test]
fn html_script_element_supports_uses_exact_supported_type_tokens() {
assert!(html_script_element_supports_type("classic"));
assert!(html_script_element_supports_type("module"));
assert!(html_script_element_supports_type("importmap"));
for unsupported in [
"",
" ",
" classic ",
"module ",
"Classic",
"Module",
"text/javascript",
"application/javascript",
"speculationrules",
"unsupported",
] {
assert!(
!html_script_element_supports_type(unsupported),
"{unsupported:?} should not be supported"
);
}
}
#[test]
fn classifies_language_only_script_attribute_values() {
assert_eq!(
classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: Some("javascript"),
event: None,
for_attribute: None,
})
.kind,
ScriptKind::Classic
);
assert_eq!(
classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: Some("JAVASCRIPT1.5"),
event: None,
for_attribute: None,
})
.kind,
ScriptKind::Classic
);
assert_eq!(
classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: Some(" javascript "),
event: None,
for_attribute: None,
})
.kind,
ScriptKind::DataBlock
);
assert_eq!(
classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: Some("javascript "),
event: None,
for_attribute: None,
})
.kind,
ScriptKind::DataBlock
);
assert_eq!(
classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: Some("javascript\t"),
event: None,
for_attribute: None,
})
.kind,
ScriptKind::DataBlock
);
assert_eq!(
classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: Some("javascript1.6"),
event: None,
for_attribute: None,
})
.kind,
ScriptKind::DataBlock
);
assert_eq!(
classify_script_element(ScriptElementClassificationInput {
script_type: Some("application/javascript"),
language: Some("unknown"),
event: None,
for_attribute: None,
})
.kind,
ScriptKind::Classic
);
}
#[test]
fn classifies_legacy_for_event_script_execution_gate() {
assert!(
!classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: None,
event: Some(" onload() "),
for_attribute: Some(" window "),
})
.legacy_event_for_mismatch
);
assert!(
classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: None,
event: Some("onclick"),
for_attribute: Some("window"),
})
.legacy_event_for_mismatch
);
assert!(
classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: None,
event: Some("\u{a0}onload"),
for_attribute: Some("window"),
})
.legacy_event_for_mismatch
);
assert!(
!classify_script_element(ScriptElementClassificationInput {
script_type: None,
language: None,
event: Some("handler"),
for_attribute: None,
})
.legacy_event_for_mismatch
);
}
#[test]
fn preparation_classification_combines_kind_and_scheduling_policy() {
let classification = classify_script_preparation(ScriptPreparationClassificationInput {
element: ScriptElementClassificationInput {
script_type: Some("module"),
language: None,
event: None,
for_attribute: None,
},
parser_inserted: true,
allow_parser_blocking_modes: true,
force_async: false,
async_attribute_present: false,
defer_attribute_present: false,
source_kind: ScriptSourceKind::External,
});
assert_eq!(
classification.disposition,
ScriptPreparationDisposition::Module(ScriptMode::ModuleDefer)
);
assert!(!classification.legacy_event_for_mismatch);
}
#[test]
fn non_executable_script_kinds_do_not_receive_scheduling_modes() {
for (script_type, expected) in [
("importmap", ScriptPreparationDisposition::ImportMap),
("application/json", ScriptPreparationDisposition::DataBlock),
] {
let classification =
classify_script_preparation(ScriptPreparationClassificationInput {
element: ScriptElementClassificationInput {
script_type: Some(script_type),
language: None,
event: None,
for_attribute: None,
},
parser_inserted: true,
allow_parser_blocking_modes: true,
force_async: false,
async_attribute_present: true,
defer_attribute_present: true,
source_kind: ScriptSourceKind::External,
});
assert_eq!(classification.disposition, expected);
}
}
}