Skip to main content

bern_target_api/
script.rs

1//! Build-time selection and shared sources of the JavaScript host.
2use crate::BuildContext;
3pub fn dependency(context: &BuildContext) -> String {
4    let Some(backend) = context.target.javascript_backend() else {
5        return String::new();
6    };
7    format!(
8        "bern-script = {{ {}, features = [{}] }}\n",
9        context.framework.dependency("bern-script"),
10        if backend == "boa" { "\"boa\"" } else { "" }
11    )
12}
13pub fn webview(context: &BuildContext) -> bool {
14    context.target.javascript_backend() == Some("webview")
15}
16pub const RUNTIME: &str = bern_script::HOST_RUNTIME;
17/// What the Windows host is made from, for the identity of the
18/// precompiled Windows sources: this module, its C++ template and the
19/// runtime.
20pub const WINDOWS_SOURCES: [&str; 3] = [
21    include_str!("script.rs"),
22    include_str!("../templates/script/host.cpp"),
23    RUNTIME,
24];
25pub fn html(complete: &str) -> String {
26    format!(
27        "<!doctype html><meta charset=\"utf-8\"><body><script>function bernScriptComplete(value){{{complete}}}\n{RUNTIME}</script>"
28    )
29}
30pub const HEADER: &str = "\nvoid bern_script_start(void);\nvoid bern_script_stop(void);\nBernBuffer bern_script_poll(double now_ms);\nvoid bern_script_complete(const uint8_t *bytes, size_t len);\n";
31
32/// Add only the selected host to generated sources and dependencies.
33pub fn configure(files: &mut [crate::GeneratedFile], context: &BuildContext) {
34    let Some(backend) = context.target.javascript_backend() else {
35        return;
36    };
37    for file in files {
38        let path = file.path.to_string_lossy();
39        if path.ends_with("Cargo.toml") {
40            let source = String::from_utf8_lossy(&file.contents);
41            file.contents = source
42                .replacen(
43                    "[dependencies]\n",
44                    &format!("[dependencies]\n{}", dependency(context)),
45                    1,
46                )
47                .into_bytes();
48        }
49        if backend == "boa" {
50            continue;
51        }
52        let mut source = String::from_utf8_lossy(&file.contents).into_owned();
53        if path.ends_with("/bern.h") {
54            source.push_str(HEADER);
55        } else if path.ends_with("BernRuntime.swift") {
56            source = source.replace("        platformWillStart()", "        BernScriptHost.shared.start()\n        platformWillStart()")
57                .replace("        if let handle { bern_app_stop(handle) }", "        BernScriptHost.shared.stop()\n        if let handle { bern_app_stop(handle) }")
58                .replace("            startupError = String(decoding:", "            BernScriptHost.shared.stop()\n            startupError = String(decoding:");
59            let html = html("window.webkit.messageHandlers.bernScript.postMessage(value)");
60            source.push_str(&include_str!("../templates/script/host.swift").replace(
61                "/*bern:script-html*/",
62                &serde_json::to_string(&html).unwrap(),
63            ));
64        } else if path.ends_with("BernRuntime.kt") {
65            source = source.replace("object BernNative {", "object BernNative {\n    external fun scriptStart()\n    external fun scriptStop()\n    external fun scriptPoll(nowMs: Double): ByteArray\n    external fun scriptComplete(bytes: ByteArray)")
66                .replace("        val reader = BernReader(BernNative.start(", "        BernScriptHost.start(context)\n        val reader = BernReader(BernNative.start(")
67                .replace("            startupError = reader.text()", "            BernScriptHost.stop()\n            startupError = reader.text()");
68            let html = html("BernScriptBridge.complete(value)");
69            source.push_str(&include_str!("../templates/script/host.kt").replace(
70                "/*bern:script-html*/",
71                &serde_json::to_string(&html).unwrap().replace('$', "\\$"),
72            ));
73        } else if path.ends_with("bern_base.h") {
74            source = source.replace(
75                "void bern_buffer_free(BernBuffer buffer);",
76                &format!("void bern_buffer_free(BernBuffer buffer);{HEADER}"),
77            );
78        } else if path.ends_with("bern_pch.h") {
79            source.push_str("\n#include <WebView2.h>\n#include <wrl.h>\n#pragma comment(lib, \"WebView2LoaderStatic.lib\")\n");
80        } else if path.ends_with("bern_base.cpp") {
81            source = source
82                .replace(
83                    "namespace bern {",
84                    "namespace bern::Scripts { void Start(); void Stop(); }\nnamespace bern {",
85                )
86                .replace(
87                    "    catalog = &app;",
88                    "    Scripts::Start();\n    catalog = &app;",
89                )
90                .replace(
91                    "    auto app = handle;",
92                    "    Scripts::Stop();\n    auto app = handle;",
93                )
94                .replace(
95                    "    if (!app_handle) {",
96                    "    if (!app_handle) {\n        Scripts::Stop();",
97                );
98            let html = html("window.chrome.webview.postMessage(value)");
99            source.push_str(&include_str!("../templates/script/host.cpp").replace(
100                "/*bern:script-html*/",
101                &format!("R\"BERNJS({html})BERNJS\""),
102            ));
103        } else if path.ends_with("bern.js") && backend == "browser" {
104            source = source.replace(
105                "  start(fingerprint, resources) {",
106                "  start(fingerprint, resources) {\n    this.stopScripts = startScriptHost(this);",
107            );
108            source = source.replace(
109                "      throw new Error(message);",
110                "      this.stopScripts?.();\n      throw new Error(message);",
111            );
112            source.push_str(include_str!("../templates/script/host.js"));
113        } else {
114            continue;
115        }
116        file.contents = source.into_bytes();
117    }
118}