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
13// Copyright (c) 2026 Innovation Trigger B.V.
14//
15// PDFluent is available under two licences, at your option: the GNU AGPLv3, or
16// the PDFluent Commercial Licence. See the LICENSE file in this repository --
17// that file travels with the copy you received, which a URL does not.
18
19use super::{RuntimeMetadata, RuntimeOutcome, SandboxError, XfaJsRuntime};
20
21/// Stub runtime that refuses every script call.
22#[derive(Debug, Default)]
23pub struct NullRuntime {
24    metadata: RuntimeMetadata,
25}
26
27impl NullRuntime {
28    /// Construct a new `NullRuntime` with empty metadata.
29    pub fn new() -> Self {
30        Self::default()
31    }
32}
33
34impl XfaJsRuntime for NullRuntime {
35    fn init(&mut self) -> Result<(), SandboxError> {
36        Ok(())
37    }
38
39    fn reset_for_new_document(&mut self) -> Result<(), SandboxError> {
40        self.metadata = RuntimeMetadata::default();
41        Ok(())
42    }
43
44    fn execute_script(
45        &mut self,
46        _activity: Option<&str>,
47        _body: &str,
48    ) -> Result<RuntimeOutcome, SandboxError> {
49        self.metadata.runtime_errors = self.metadata.runtime_errors.saturating_add(1);
50        Err(SandboxError::NotCompiledIn)
51    }
52
53    fn take_metadata(&mut self) -> RuntimeMetadata {
54        std::mem::take(&mut self.metadata)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn execute_always_returns_not_compiled_in() {
64        let mut rt = NullRuntime::new();
65        rt.init().unwrap();
66        rt.reset_for_new_document().unwrap();
67        let err = rt
68            .execute_script(Some("calculate"), "Total.rawValue = 42;")
69            .unwrap_err();
70        assert_eq!(err, SandboxError::NotCompiledIn);
71    }
72
73    #[test]
74    fn metadata_records_runtime_errors() {
75        let mut rt = NullRuntime::new();
76        rt.init().unwrap();
77        rt.reset_for_new_document().unwrap();
78        for _ in 0..3 {
79            let _ = rt.execute_script(Some("calculate"), "1+1");
80        }
81        let md = rt.take_metadata();
82        assert_eq!(md.runtime_errors, 3);
83        assert_eq!(md.executed, 0);
84        assert_eq!(md.timeouts, 0);
85        assert_eq!(md.oom, 0);
86    }
87
88    #[test]
89    fn take_metadata_resets_counters() {
90        let mut rt = NullRuntime::new();
91        let _ = rt.execute_script(None, "");
92        let _ = rt.take_metadata();
93        let md = rt.take_metadata();
94        assert_eq!(md, RuntimeMetadata::default());
95    }
96
97    #[test]
98    fn reset_clears_counters() {
99        let mut rt = NullRuntime::new();
100        let _ = rt.execute_script(None, "");
101        rt.reset_for_new_document().unwrap();
102        let md = rt.take_metadata();
103        assert_eq!(md, RuntimeMetadata::default());
104    }
105}