- Wrap eval scripts in async IIFE that detects and awaits thenables, using callAsyncJavaScript when available (macOS 11+) (#603) - Register console/error telemetry hooks as WKUserScript at document start so they survive navigation and are active before page JS (#604) - Return typed envelope {__cmux_t, __cmux_v} from eval to distinguish undefined from no return value; CLI prints "undefined" (#605) - Keep dialog hooks as lazy injection only (not document-start) to avoid suppressing WKUIDelegate native dialogs - Add regression tests for async wrapper and undefined CLI rendering
128 lines
5 KiB
Python
128 lines
5 KiB
Python
#!/usr/bin/env python3
|
|
"""Static regression guard for browser eval async wrapping + telemetry injection."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def repo_root() -> Path:
|
|
result = subprocess.run(
|
|
["git", "rev-parse", "--show-toplevel"],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if result.returncode == 0:
|
|
return Path(result.stdout.strip())
|
|
return Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def extract_block(source: str, signature: str) -> str:
|
|
start = source.find(signature)
|
|
if start < 0:
|
|
raise ValueError(f"Missing signature: {signature}")
|
|
brace_start = source.find("{", start)
|
|
if brace_start < 0:
|
|
raise ValueError(f"Missing opening brace for: {signature}")
|
|
depth = 0
|
|
for idx in range(brace_start, len(source)):
|
|
char = source[idx]
|
|
if char == "{":
|
|
depth += 1
|
|
elif char == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return source[brace_start : idx + 1]
|
|
raise ValueError(f"Unbalanced braces for: {signature}")
|
|
|
|
|
|
def extract_span(source: str, start_marker: str, end_marker: str) -> str:
|
|
start = source.find(start_marker)
|
|
if start < 0:
|
|
raise ValueError(f"Missing start marker: {start_marker}")
|
|
end = source.find(end_marker, start)
|
|
if end < 0:
|
|
raise ValueError(f"Missing end marker: {end_marker}")
|
|
return source[start:end]
|
|
|
|
|
|
def main() -> int:
|
|
root = repo_root()
|
|
failures: list[str] = []
|
|
|
|
terminal_path = root / "Sources" / "TerminalController.swift"
|
|
panel_path = root / "Sources" / "Panels" / "BrowserPanel.swift"
|
|
terminal_source = terminal_path.read_text(encoding="utf-8")
|
|
panel_source = panel_path.read_text(encoding="utf-8")
|
|
|
|
if "preferAsync: Bool = false" not in terminal_source:
|
|
failures.append("v2RunJavaScript() no longer exposes preferAsync toggle")
|
|
run_js_block = extract_block(terminal_source, "private func v2RunJavaScript(")
|
|
if "callAsyncJavaScript" not in run_js_block:
|
|
failures.append("v2RunJavaScript() no longer uses callAsyncJavaScript for async JS")
|
|
|
|
run_browser_js_block = extract_block(terminal_source, "private func v2RunBrowserJavaScript(")
|
|
required_wrapper_tokens = [
|
|
"let asyncFunctionBody =",
|
|
"__cmuxMaybeAwait",
|
|
"__cmux_t",
|
|
"__cmux_v",
|
|
"return await __cmuxEvalInFrame();",
|
|
"preferAsync: true",
|
|
]
|
|
for token in required_wrapper_tokens:
|
|
if token not in run_browser_js_block:
|
|
failures.append(f"v2RunBrowserJavaScript() missing async eval wrapper token: {token}")
|
|
|
|
if "v2BrowserUndefinedSentinel" not in terminal_source:
|
|
failures.append("TerminalController is missing undefined sentinel handling")
|
|
if "v2BrowserEvalEnvelopeTypeUndefined" not in terminal_source:
|
|
failures.append("TerminalController is missing undefined envelope decode constant")
|
|
|
|
hook_block = extract_block(terminal_source, "private func v2BrowserEnsureTelemetryHooks(")
|
|
if "BrowserPanel.telemetryHookBootstrapScriptSource" not in hook_block:
|
|
failures.append("v2BrowserEnsureTelemetryHooks() no longer uses shared BrowserPanel telemetry source")
|
|
|
|
if "static let telemetryHookBootstrapScriptSource" not in panel_source:
|
|
failures.append("BrowserPanel is missing telemetryHookBootstrapScriptSource")
|
|
if "static let dialogTelemetryHookBootstrapScriptSource" not in panel_source:
|
|
failures.append("BrowserPanel is missing dialogTelemetryHookBootstrapScriptSource")
|
|
|
|
base_script_span = extract_span(
|
|
panel_source,
|
|
"static let telemetryHookBootstrapScriptSource =",
|
|
"static let dialogTelemetryHookBootstrapScriptSource =",
|
|
)
|
|
if "window.alert = function(message)" in base_script_span:
|
|
failures.append("Document-start telemetry script should not override alert dialogs")
|
|
if "window.confirm = function(message)" in base_script_span:
|
|
failures.append("Document-start telemetry script should not override confirm dialogs")
|
|
if "window.prompt = function(message, defaultValue)" in base_script_span:
|
|
failures.append("Document-start telemetry script should not override prompt dialogs")
|
|
|
|
panel_init_block = extract_block(
|
|
panel_source,
|
|
"init(workspaceId: UUID, initialURL: URL? = nil, bypassInsecureHTTPHostOnce: String? = nil)",
|
|
)
|
|
required_init_tokens = [
|
|
"config.userContentController.addUserScript(",
|
|
"source: Self.telemetryHookBootstrapScriptSource",
|
|
"injectionTime: .atDocumentStart",
|
|
]
|
|
for token in required_init_tokens:
|
|
if token not in panel_init_block:
|
|
failures.append(f"BrowserPanel init() missing telemetry user-script token: {token}")
|
|
|
|
if failures:
|
|
print("FAIL: browser eval async wrapper / telemetry injection regression guard failed")
|
|
for item in failures:
|
|
print(f" - {item}")
|
|
return 1
|
|
|
|
print("PASS: browser eval async wrapper / telemetry injection regression guard is in place")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|