mirror of
https://github.com/lexmount/moli.git
synced 2026-09-25 08:01:28 +00:00
299 lines
9.9 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|