Skip to main content

pdf_xfa/js_runtime/
null.rs

1//! NullRuntime — always-available stub backend.
2//!
3//! Used when:
4//! - The Cargo feature `xfa-js-sandboxed` is not compiled in.
5//! - A caller selects `JsExecutionMode::SandboxedRuntime` but the
6//!   build did not link a real backend.
7//!
8//! Returning [`SandboxError::NotCompiledIn`] from every script call
9//! lets the dispatch site fall back to the existing best-effort skip
10//! path (`js_skipped += 1`, `js_runtime_errors += 1`) without changing
11//! pipeline behaviour.
12
13use super::{RuntimeMetadata, RuntimeOutcome, SandboxError, XfaJsRuntime};
14
15/// Stub runtime that refuses every script call.
16#[derive(Debug, Default)]
17pub struct NullRuntime {
18    metadata: RuntimeMetadata,
19}
20
21impl NullRuntime {
22    /// Construct a new `NullRuntime` with empty metadata.
23    pub fn new() -> Self {
24        Self::default()
25    }
26}
27
28impl XfaJsRuntime for NullRuntime {
29    fn init(&mut self) -> Result<(), SandboxError> {
30        Ok(())
31    }
32
33    fn reset_for_new_document(&mut self) -> Result<(), SandboxError> {
34        self.metadata = RuntimeMetadata::default();
35        Ok(())
36    }
37
38    fn execute_script(
39        &mut self,
40        _activity: Option<&str>,
41        _body: &str,
42    ) -> Result<RuntimeOutcome, SandboxError> {
43        self.metadata.runtime_errors = self.metadata.runtime_errors.saturating_add(1);
44        Err(SandboxError::NotCompiledIn)
45    }
46
47    fn take_metadata(&mut self) -> RuntimeMetadata {
48        std::mem::take(&mut self.metadata)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn execute_always_returns_not_compiled_in() {
58        let mut rt = NullRuntime::new();
59        rt.init().unwrap();
60        rt.reset_for_new_document().unwrap();
61        let err = rt
62            .execute_script(Some("calculate"), "Total.rawValue = 42;")
63            .unwrap_err();
64        assert_eq!(err, SandboxError::NotCompiledIn);
65    }
66
67    #[test]
68    fn metadata_records_runtime_errors() {
69        let mut rt = NullRuntime::new();
70        rt.init().unwrap();
71        rt.reset_for_new_document().unwrap();
72        for _ in 0..3 {
73            let _ = rt.execute_script(Some("calculate"), "1+1");
74        }
75        let md = rt.take_metadata();
76        assert_eq!(md.runtime_errors, 3);
77        assert_eq!(md.executed, 0);
78        assert_eq!(md.timeouts, 0);
79        assert_eq!(md.oom, 0);
80    }
81
82    #[test]
83    fn take_metadata_resets_counters() {
84        let mut rt = NullRuntime::new();
85        let _ = rt.execute_script(None, "");
86        let _ = rt.take_metadata();
87        let md = rt.take_metadata();
88        assert_eq!(md, RuntimeMetadata::default());
89    }
90
91    #[test]
92    fn reset_clears_counters() {
93        let mut rt = NullRuntime::new();
94        let _ = rt.execute_script(None, "");
95        rt.reset_for_new_document().unwrap();
96        let md = rt.take_metadata();
97        assert_eq!(md, RuntimeMetadata::default());
98    }
99}