diff --git a/wpt/runner/src/main.rs b/wpt/runner/src/main.rs
index 80be76ed0..3fd4888a7 100644
--- a/wpt/runner/src/main.rs
+++ b/wpt/runner/src/main.rs
@@ -40,6 +40,7 @@ mod test_runners;
mod net_provider;
mod panic_backtrace;
mod report;
+mod script_detection;
use net_provider::WptNetProvider;
diff --git a/wpt/runner/src/script_detection.rs b/wpt/runner/src/script_detection.rs
new file mode 100644
index 000000000..f6ba77432
--- /dev/null
+++ b/wpt/runner/src/script_detection.rs
@@ -0,0 +1,210 @@
+//! Detection of tests that depend on JavaScript to render correctly.
+//!
+//! Blitz does not execute JavaScript, so ref tests whose rendering depends on script
+//! (e.g. tests that mutate the DOM from a `"#).unwrap());
+static SRC_RE: LazyLock =
+ LazyLock::new(|| Regex::new(r#"(?i)src\s*=\s*["']?([^"'\s>]+)"#).unwrap());
+static EVENT_HANDLER_RE: LazyLock =
+ LazyLock::new(|| Regex::new(r#"(?i)\son[a-z]+\s*=\s*("[^"]*"|'[^']*')"#).unwrap());
+static COMMENT_RE: LazyLock =
+ LazyLock::new(|| Regex::new(r#"(?s)//[^\n]*|/\*.*?\*/"#).unwrap());
+static STRING_RE: LazyLock =
+ LazyLock::new(|| Regex::new(r#""([^"\\]*)"|'([^'\\]*)'"#).unwrap());
+static IDENT_RE: LazyLock =
+ LazyLock::new(|| Regex::new(r#"[A-Za-z_$][A-Za-z0-9_$]*"#).unwrap());
+
+/// External scripts that only provide wait/screenshot helpers and have no
+/// rendering side-effects of their own.
+const TRIVIAL_SCRIPT_SRCS: &[&str] = &["/reftest-wait.js", "/rendering-utils.js"];
+
+/// Identifiers allowed in a trivial "wait then screenshot" inline script.
+const TRIVIAL_IDENTS: &[&str] = &[
+ // Keywords / declarations
+ "async",
+ "await",
+ "const",
+ "function",
+ "let",
+ "new",
+ "return",
+ "var",
+ // Waiting vocabulary
+ "Promise",
+ "addEventListener",
+ "document",
+ "fonts",
+ "load",
+ "onload",
+ "ready",
+ "requestAnimationFrame",
+ "resolve",
+ "then",
+ "finally",
+ "window",
+ // reftest-wait helpers (defined by /common/reftest-wait.js and
+ // /common/rendering-utils.js)
+ "takeScreenshot",
+ "waitForAtLeastOneFrame",
+ // Removing the `reftest-wait` class
+ "body",
+ "classList",
+ "documentElement",
+ "remove",
+];
+
+/// String literals allowed in a trivial inline script.
+const TRIVIAL_STRINGS: &[&str] = &["", "reftest-wait", "load", "DOMContentLoaded"];
+
+fn src_is_trivial(src: &str) -> bool {
+ TRIVIAL_SCRIPT_SRCS
+ .iter()
+ .any(|suffix| src.ends_with(suffix))
+}
+
+fn inline_script_is_trivial(body: &str) -> bool {
+ let body = COMMENT_RE.replace_all(body, "");
+ for caps in STRING_RE.captures_iter(&body) {
+ let string = caps
+ .get(1)
+ .or_else(|| caps.get(2))
+ .map_or("", |m| m.as_str());
+ if !TRIVIAL_STRINGS.contains(&string) {
+ return false;
+ }
+ }
+ let without_strings = STRING_RE.replace_all(&body, "\"\"");
+ IDENT_RE
+ .find_iter(&without_strings)
+ .all(|ident| TRIVIAL_IDENTS.contains(&ident.as_str()))
+}
+
+/// Returns true if the document depends on JavaScript to render correctly
+/// (i.e. contains any script that is not a trivial "wait then screenshot" script).
+pub fn uses_nontrivial_script(html: &str) -> bool {
+ for caps in SCRIPT_RE.captures_iter(html) {
+ let attrs = caps.get(1).unwrap().as_str();
+ let body = caps.get(2).unwrap().as_str();
+ if let Some(src) = SRC_RE.captures(attrs).and_then(|c| c.get(1)) {
+ if !src_is_trivial(src.as_str()) {
+ return true;
+ }
+ } else if !body.trim().is_empty() && !inline_script_is_trivial(body) {
+ return true;
+ }
+ }
+
+ // Content outside of well-formed elements: an unclosed
+ //
+ "#;
+ assert!(uses_nontrivial_script(html));
+ }
+
+ #[test]
+ fn font_wait_screenshot() {
+ let html = r#"
+
+
+
+ "#;
+ assert!(!uses_nontrivial_script(html));
+ }
+
+ #[test]
+ fn remove_reftest_wait_on_font_ready() {
+ let html = r#"
+
+ "#;
+ assert!(!uses_nontrivial_script(html));
+ }
+
+ #[test]
+ fn onload_raf_screenshot() {
+ let html = r#"
+
+
+ "#;
+ assert!(!uses_nontrivial_script(html));
+ }
+
+ #[test]
+ fn external_helper_script() {
+ let html = r#""#;
+ assert!(uses_nontrivial_script(html));
+ }
+
+ #[test]
+ fn nontrivial_event_handler_attribute() {
+ let html = r#""#;
+ assert!(uses_nontrivial_script(html));
+ }
+
+ #[test]
+ fn nontrivial_string_literal() {
+ let html = r#""#;
+ assert!(uses_nontrivial_script(html));
+ }
+
+ #[test]
+ fn unclosed_script_tag() {
+ assert!(uses_nontrivial_script("