cj-tauri 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +64 -18
  2. package/cjpm.toml +52 -52
  3. package/cli/cjpm.toml +10 -1
  4. package/cli/src/doctor.cj +4 -2
  5. package/cli/src/main.cj +11 -3
  6. package/cli/src/manifest.cj +558 -0
  7. package/cli/src/project.cj +38 -22
  8. package/cli/src/scaffold.cj +369 -361
  9. package/cli/src/sync.cj +526 -0
  10. package/cli/src/util.cj +168 -159
  11. package/cli/templates/app/README.md +48 -80
  12. package/cli/templates/app/{capabilities → assets/capabilities}/default.json +7 -7
  13. package/cli/templates/app/{ui → assets/ui}/index.html +118 -118
  14. package/cli/templates/app/cj-tauri.toml +22 -0
  15. package/cli/templates/app/cjpm.toml +34 -17
  16. package/cli/templates/app/src/main.cj +90 -88
  17. package/cli/templates/app-react/README.md +56 -67
  18. package/cli/templates/app-react/{capabilities → assets/capabilities}/default.json +7 -7
  19. package/cli/templates/app-react/{ui → assets/ui}/package.json +19 -19
  20. package/cli/templates/app-react/{ui → assets/ui}/src/main.jsx +6 -6
  21. package/cli/templates/app-react/{ui → assets/ui}/vite.config.js +24 -24
  22. package/cli/templates/app-react/cj-tauri.toml +23 -0
  23. package/cli/templates/app-react/cjpm.toml +34 -17
  24. package/cli/templates/app-react/src/main.cj +91 -102
  25. package/cli/templates/app-vue/README.md +56 -60
  26. package/cli/templates/app-vue/{capabilities → assets/capabilities}/default.json +7 -7
  27. package/cli/templates/app-vue/{ui → assets/ui}/package.json +18 -18
  28. package/cli/templates/app-vue/{ui → assets/ui}/src/main.js +4 -4
  29. package/cli/templates/app-vue/{ui → assets/ui}/vite.config.js +24 -24
  30. package/cli/templates/app-vue/cj-tauri.toml +23 -0
  31. package/cli/templates/app-vue/cjpm.toml +34 -17
  32. package/cli/templates/app-vue/src/main.cj +91 -102
  33. package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/app.json5 +13 -13
  34. package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/element/string.json +8 -8
  35. package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/layered_image.json +6 -6
  36. package/cli/templates/{ohos-pc → ohos-skeleton}/README.md +79 -79
  37. package/cli/templates/{ohos-pc → ohos-skeleton}/build-profile.json5 +46 -46
  38. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/build-profile.json5 +26 -26
  39. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/cjpm.toml +43 -43
  40. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/hvigorfile.ts +6 -6
  41. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/oh-package.json5 +9 -9
  42. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/ability_stage.cj +17 -17
  43. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/app_host.cj +94 -94
  44. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/index.cj +167 -167
  45. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/main_ability.cj +139 -139
  46. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/module.json5 +49 -49
  47. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/color.json +8 -8
  48. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/string.json +16 -16
  49. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/layered_image.json +6 -6
  50. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/profile/main_pages.json +2 -2
  51. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/capabilities.json +6 -6
  52. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/index.html +92 -92
  53. package/cli/templates/{ohos-pc → ohos-skeleton}/hvigor/hvigor-config.json5 +5 -5
  54. package/cli/templates/{ohos-pc → ohos-skeleton}/hvigorfile.ts +6 -6
  55. package/cli/templates/{ohos-pc → ohos-skeleton}/oh-package.json5 +10 -10
  56. package/package.json +4 -2
  57. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  58. package/src/app.cj +49 -20
  59. package/src/app_spec.cj +76 -0
  60. package/src/arkweb_script.cj +142 -154
  61. package/src/host.cj +20 -17
  62. package/src/host_arkweb.cj +778 -740
  63. package/src/host_webkit.cj +22 -0
  64. package/src/host_webview2.cj +22 -0
  65. package/src/launch.cj +62 -0
  66. package/src/platform/platform_app_context.cj +77 -0
  67. package/src/platform/platform_assets.cj +33 -0
  68. package/src/platform/platform_discover.cj +176 -0
  69. package/src/platform/platform_env.cj +29 -0
  70. package/src/platform/platform_feature.cj +31 -0
  71. package/src/platform/platform_page_source.cj +87 -0
  72. package/src/platform/platform_target_info.cj +32 -0
  73. package/src/platform_host_provider.cj +87 -0
  74. package/src/plugin.cj +36 -2
  75. package/src/plugins/clipboard/plugin_clipboard.cj +8 -0
  76. package/src/plugins/menu/plugin_menu.cj +158 -150
  77. package/src/plugins/notification/plugin_notification.cj +8 -0
  78. package/src/plugins/serial/plugin_serial.cj +454 -442
  79. package/src/plugins/serial/serial_bridge.cj +194 -186
  80. package/src/plugins/serial/serial_ohos_stub.cj +61 -0
  81. package/src/plugins/shell/plugin_shell.cj +258 -230
  82. package/src/plugins/sqlite/plugin_sqlite.cj +710 -697
  83. package/src/plugins/sqlite/sqlite_ffi.cj +211 -206
  84. package/src/plugins/sqlite/sqlite_ohos_stub.cj +91 -0
  85. package/src/tests/app_spec_test.cj +127 -0
  86. package/src/tests/fake_host.cj +23 -8
  87. package/src/tests/platform_contract_test.cj +251 -0
  88. package/src/tests/plugin_test.cj +585 -542
  89. package/src/version.cj +1 -1
  90. package/src/host_factory.cj +0 -48
  91. /package/cli/templates/app-react/{ui → assets/ui}/index.html +0 -0
  92. /package/cli/templates/app-react/{ui → assets/ui}/src/App.css +0 -0
  93. /package/cli/templates/app-react/{ui → assets/ui}/src/App.jsx +0 -0
  94. /package/cli/templates/app-vue/{ui → assets/ui}/index.html +0 -0
  95. /package/cli/templates/app-vue/{ui → assets/ui}/src/App.vue +0 -0
  96. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/background.png +0 -0
  97. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/foreground.png +0 -0
  98. /package/cli/templates/{ohos-pc → ohos-skeleton}/build-hap.sh +0 -0
  99. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/background.png +0 -0
  100. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/foreground.png +0 -0
  101. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/startIcon.png +0 -0
@@ -1,154 +1,142 @@
1
- package cjTauri
2
-
3
- /**
4
- * 鸿蒙 ArkWeb 侧的页面桥脚本与注入片段
5
- *
6
- * ## 为什么要另存一份(而不是复用 `native/core/bridge_js.h`)
7
- *
8
- * 那一份是 **C 宏**(`CJ_BRIDGE_JS(POST_STMT, RELOAD_STMT)`),只有编 C 桥的平台能用;
9
- * 鸿蒙没有 C 桥(宿主在仓颉侧直接驱动 ArkWeb),拿不到宏展开结果。所以这里按同一份语义
10
- * 落成仓颉常量——**改动要两边同步**,判断「哪边漏了」的判据是:页面上 `invoke` / `emit` /
11
- * `listen` / `reload` / `_dispatch` 五个入口的行为是否与桌面一致(桥只负责路由与回执匹配,
12
- * 不含业务)。
13
- *
14
- * ## 与桌面版的唯一差异
15
- *
16
- * `post`:桌面走 `window.chrome.webview.postMessage` / `window.webkit.messageHandlers`,
17
- * 鸿蒙走 `javaScriptProxy` 注入的同步方法(`window.__CJ_TAURI_HOST__.post(json)`)。
18
- * 同步调用不携带返回值语义——它是「投递即返回」,回执仍由 `_dispatch` 从仓颉侧推回来。
19
- *
20
- * ## 转义口径
21
- *
22
- * 用 raw string(`#"..."#`):三引号字符串会先处理反斜杠转义(AGENTS §4 踩过,内联 JS 会被
23
- * 拆断而 stderr 一行提示都没有),raw string 原样保留。因此本文件里的 JS **不要**出现 `"#`
24
- * 这个序列(那是 raw string 的收尾)。
25
- */
26
- @When[env == "ohos"]
27
- public let ARKWEB_PROXY_NAME: String = "__CJ_TAURI_HOST__"
28
-
29
- @When[env == "ohos"]
30
- let ARKWEB_BRIDGE_JS: String = #"window.__CJ_TAURI__ = (function () {
31
- var seq = 0;
32
- var pending = {};
33
- var listeners = {};
34
- var coalesceSlots = Object.create(null);
35
- var coalesceTimer = null;
36
- var coalesceRaf = 0;
37
- function flushCoalesced() {
38
- if (coalesceTimer !== null) { clearTimeout(coalesceTimer); coalesceTimer = null; }
39
- coalesceRaf = 0;
40
- var due = coalesceSlots;
41
- coalesceSlots = Object.create(null);
42
- for (var name in due) {
43
- var arr = listeners[name] || [];
44
- for (var i = 0; i < arr.length; i++) arr[i](due[name]);
45
- }
46
- }
47
- function scheduleCoalesced() {
48
- if (coalesceTimer !== null || coalesceRaf) return;
49
- coalesceTimer = setTimeout(flushCoalesced, 50);
50
- }
51
- var cachedLabel = null;
52
- function labelOf() {
53
- if (cachedLabel === null) {
54
- var v = (typeof window !== 'undefined') ? window.__CJ_TAURI_LABEL__ : null;
55
- cachedLabel = (typeof v === 'string' && v.length > 0) ? v : 'main';
56
- }
57
- return cachedLabel;
58
- }
59
- function post(obj) {
60
- try {
61
- var host = window.__CJ_TAURI_HOST__;
62
- if (host && typeof host.post === 'function') host.post(JSON.stringify(obj));
63
- } catch (e) { }
64
- }
65
- /*
66
- * 原生 -> 页面的推送:页面**只负责叫一声**,脚本由原生在 post 回调里用
67
- * `controller.runJavaScript` 推回来。
68
- *
69
- * 为什么不是「页面 poll 取返回值」:2026-10-19 真机实测,`javaScriptProxy` 的
70
- * funcList 返回值**传不回 JS**(换上无条件返回固定串的桩,页面拿到的仍是 null),
71
- * 而 post 方向一切正常。所以同步取数模型不成立,只能反过来推。
72
- * 心跳周期的代价只是一次同步调用;队列空时原生什么都不做。
73
- */
74
- var pumping = false;
75
- function pump() {
76
- if (pumping) return;
77
- pumping = true;
78
- try {
79
- var host = window.__CJ_TAURI_HOST__;
80
- if (host && typeof host.post === 'function') {
81
- host.post('{"type":"pull","window":' + JSON.stringify(labelOf()) + '}');
82
- }
83
- } catch (e2) { } finally { pumping = false; }
84
- }
85
- try { setInterval(pump, 50); } catch (e3) { }
86
- return {
87
- invoke: function (cmd, args) {
88
- var id = ++seq;
89
- var p = new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });
90
- post({ type: 'invoke', id: id, cmd: cmd, args: args || {}, window: labelOf() });
91
- return p;
92
- },
93
- listen: function (event, cb) {
94
- if (!listeners[event]) listeners[event] = [];
95
- listeners[event].push(cb);
96
- return function () {
97
- var arr = listeners[event] || [];
98
- var i = arr.indexOf(cb);
99
- if (i >= 0) arr.splice(i, 1);
100
- };
101
- },
102
- emit: function (event, payload) {
103
- var id = ++seq;
104
- var p = new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });
105
- post({ type: 'emit', id: id, event: event, payload: payload === undefined ? {} : payload, window: labelOf() });
106
- return p;
107
- },
108
- reload: function () {
109
- post({ type: 'reload', window: labelOf() });
110
- },
111
- _dispatch: function (msg) {
112
- if (!msg) return;
113
- if (msg.type === 'resolve') {
114
- var p = pending[msg.id];
115
- if (!p) return;
116
- delete pending[msg.id];
117
- if (msg.ok) p.resolve(msg.data); else p.reject(new Error(msg.error || 'invoke failed'));
118
- } else if (msg.type === 'event') {
119
- if (msg.window && msg.window !== labelOf()) return;
120
- if (msg.coalesce) {
121
- coalesceSlots[msg.event] = msg.payload;
122
- scheduleCoalesced();
123
- return;
124
- }
125
- var arr = listeners[msg.event] || [];
126
- for (var i = 0; i < arr.length; i++) arr[i](msg.payload);
127
- }
128
- }
129
- };
130
- })();"#
131
-
132
- /**
133
- * 窗口 label 注入片段(等价于桌面 C 侧的 document-start 注入)
134
- *
135
- * 单引号包住 JSON 字符串:label 由应用给定,实际取值来自 `WindowConfig.label` / 默认 label,
136
- * 里层用 JSON 转义保证引号与反斜杠安全(不做手工拼接转义)。
137
- */
138
- @When[env == "ohos"]
139
- public func arkWebLabelScript(label: String): String {
140
- return "window.__CJ_TAURI_LABEL__ = ${jsStringLiteral(label)};"
141
- }
142
-
143
- /**
144
- * 把一段「已经是 JSON」的报文推给页面(不经 JSON 二次解析)
145
- *
146
- * 桌面两平台由 C 桥拼 `_dispatch(报文)` 并 ExecuteScript;这里产出同形状的脚本字符串,
147
- * 由 UI 线程的组件交给 `controller.runJavaScript`。报文本身来自 `IpcHub` 的序列化结果,
148
- * 所以这里**只包一层括号**、不做转义(做了会变成字符串字面量,页面收到的是文本而非对象)。
149
- */
150
- @When[env == "ohos"]
151
- func arkWebDispatchScript(payloadJson: String): String {
152
- return "window.__CJ_TAURI__ && window.__CJ_TAURI__._dispatch(${payloadJson});"
153
- }
154
-
1
+ package cjTauri
2
+
3
+ /**
4
+ * 鸿蒙 ArkWeb 侧的页面桥脚本与注入片段
5
+ *
6
+ * ## 为什么要另存一份(而不是复用 `native/core/bridge_js.h`)
7
+ *
8
+ * 那一份是 **C 宏**(`CJ_BRIDGE_JS(POST_STMT, RELOAD_STMT)`),只有编 C 桥的平台能用;
9
+ * 鸿蒙没有 C 桥(宿主在仓颉侧直接驱动 ArkWeb),拿不到宏展开结果。所以这里按同一份语义
10
+ * 落成仓颉常量——**改动要两边同步**,判断「哪边漏了」的判据是:页面上 `invoke` / `emit` /
11
+ * `listen` / `reload` / `_dispatch` 五个入口的行为是否与桌面一致(桥只负责路由与回执匹配,
12
+ * 不含业务)。
13
+ *
14
+ * ## 与桌面版的唯一差异
15
+ *
16
+ * `post`:桌面走 `window.chrome.webview.postMessage` / `window.webkit.messageHandlers`,
17
+ * 鸿蒙走 `javaScriptProxy` 注入的同步方法(`window.__CJ_TAURI_HOST__.post(json)`)。
18
+ * 同步调用不携带返回值语义——它是「投递即返回」,回执仍由 `_dispatch` 从仓颉侧推回来。
19
+ *
20
+ * ## 转义口径
21
+ *
22
+ * 用 raw string(`#"..."#`):三引号字符串会先处理反斜杠转义(AGENTS §4 踩过,内联 JS 会被
23
+ * 拆断而 stderr 一行提示都没有),raw string 原样保留。因此本文件里的 JS **不要**出现 `"#`
24
+ * 这个序列(那是 raw string 的收尾)。
25
+ */
26
+ @When[env == "ohos"]
27
+ public let ARKWEB_PROXY_NAME: String = "__CJ_TAURI_HOST__"
28
+
29
+ @When[env == "ohos"]
30
+ let ARKWEB_BRIDGE_JS: String = #"window.__CJ_TAURI__ = (function () {
31
+ var seq = 0;
32
+ var pending = {};
33
+ var listeners = {};
34
+ var coalesceSlots = Object.create(null);
35
+ var coalesceTimer = null;
36
+ var coalesceRaf = 0;
37
+ function flushCoalesced() {
38
+ if (coalesceTimer !== null) { clearTimeout(coalesceTimer); coalesceTimer = null; }
39
+ coalesceRaf = 0;
40
+ var due = coalesceSlots;
41
+ coalesceSlots = Object.create(null);
42
+ for (var name in due) {
43
+ var arr = listeners[name] || [];
44
+ for (var i = 0; i < arr.length; i++) arr[i](due[name]);
45
+ }
46
+ }
47
+ function scheduleCoalesced() {
48
+ if (coalesceTimer !== null || coalesceRaf) return;
49
+ coalesceTimer = setTimeout(flushCoalesced, 50);
50
+ }
51
+ var cachedLabel = null;
52
+ function labelOf() {
53
+ if (cachedLabel === null) {
54
+ var v = (typeof window !== 'undefined') ? window.__CJ_TAURI_LABEL__ : null;
55
+ cachedLabel = (typeof v === 'string' && v.length > 0) ? v : 'main';
56
+ }
57
+ return cachedLabel;
58
+ }
59
+ function post(obj) {
60
+ try {
61
+ var host = window.__CJ_TAURI_HOST__;
62
+ if (host && typeof host.post === 'function') host.post(JSON.stringify(obj));
63
+ } catch (e) { }
64
+ }
65
+ /*
66
+ * 原生 -> 页面的推送:页面**只负责叫一声**,脚本由原生在 post 回调里用
67
+ * `controller.runJavaScript` 推回来。
68
+ *
69
+ * 为什么不是「页面 poll 取返回值」:2026-10-19 真机实测,`javaScriptProxy` 的
70
+ * funcList 返回值**传不回 JS**(换上无条件返回固定串的桩,页面拿到的仍是 null),
71
+ * 而 post 方向一切正常。所以同步取数模型不成立,只能反过来推。
72
+ * 心跳周期的代价只是一次同步调用;队列空时原生什么都不做。
73
+ */
74
+ var pumping = false;
75
+ function pump() {
76
+ if (pumping) return;
77
+ pumping = true;
78
+ try {
79
+ var host = window.__CJ_TAURI_HOST__;
80
+ if (host && typeof host.post === 'function') {
81
+ host.post('{"type":"pull","window":' + JSON.stringify(labelOf()) + '}');
82
+ }
83
+ } catch (e2) { } finally { pumping = false; }
84
+ }
85
+ try { setInterval(pump, 50); } catch (e3) { }
86
+ return {
87
+ invoke: function (cmd, args) {
88
+ var id = ++seq;
89
+ var p = new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });
90
+ post({ type: 'invoke', id: id, cmd: cmd, args: args || {}, window: labelOf() });
91
+ return p;
92
+ },
93
+ listen: function (event, cb) {
94
+ if (!listeners[event]) listeners[event] = [];
95
+ listeners[event].push(cb);
96
+ return function () {
97
+ var arr = listeners[event] || [];
98
+ var i = arr.indexOf(cb);
99
+ if (i >= 0) arr.splice(i, 1);
100
+ };
101
+ },
102
+ emit: function (event, payload) {
103
+ var id = ++seq;
104
+ var p = new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });
105
+ post({ type: 'emit', id: id, event: event, payload: payload === undefined ? {} : payload, window: labelOf() });
106
+ return p;
107
+ },
108
+ reload: function () {
109
+ post({ type: 'reload', window: labelOf() });
110
+ },
111
+ _dispatch: function (msg) {
112
+ if (!msg) return;
113
+ if (msg.type === 'resolve') {
114
+ var p = pending[msg.id];
115
+ if (!p) return;
116
+ delete pending[msg.id];
117
+ if (msg.ok) p.resolve(msg.data); else p.reject(new Error(msg.error || 'invoke failed'));
118
+ } else if (msg.type === 'event') {
119
+ if (msg.window && msg.window !== labelOf()) return;
120
+ if (msg.coalesce) {
121
+ coalesceSlots[msg.event] = msg.payload;
122
+ scheduleCoalesced();
123
+ return;
124
+ }
125
+ var arr = listeners[msg.event] || [];
126
+ for (var i = 0; i < arr.length; i++) arr[i](msg.payload);
127
+ }
128
+ }
129
+ };
130
+ })();"#
131
+
132
+ /**
133
+ * 窗口 label 注入片段(等价于桌面 C 侧的 document-start 注入)
134
+ *
135
+ * 单引号包住 JSON 字符串:label 由应用给定,实际取值来自 `WindowConfig.label` / 默认 label,
136
+ * 里层用 JSON 转义保证引号与反斜杠安全(不做手工拼接转义)。
137
+ */
138
+ @When[env == "ohos"]
139
+ public func arkWebLabelScript(label: String): String {
140
+ return "window.__CJ_TAURI_LABEL__ = ${jsStringLiteral(label)};"
141
+ }
142
+
package/src/host.cj CHANGED
@@ -2,6 +2,7 @@ package cjTauri
2
2
 
3
3
  import std.collection.*
4
4
  import stdx.encoding.json.*
5
+ import cjTauri.platform.*
5
6
 
6
7
  /**
7
8
  * WebView 宿主层(对标 Tauri 2 的 tao + wry 抽象)
@@ -98,22 +99,24 @@ public interface WebViewHost {
98
99
  func label(): String
99
100
 
100
101
  /**
101
- * 启动宿主:创建窗口 + WebView,加载 HTML,进入事件循环
102
+ * 加载页面(唯一入口,RFC §4.3):Inline / Url / DevServer / Asset 四态
103
+ *
104
+ * 原 start(html) / startUrl(url) 双入口在此收敛。Asset 怎么落地是 L0 的事(RFC §6.2):
105
+ * 桌面经注入的 AssetResolver 读盘(未注入时按 cwd 相对路径读,与批次 1 行为等价),
106
+ * 鸿蒙由胶水印化的 rawfile 解析成 HTML 再落沙箱以 file:// 加载。
107
+ * URL 页面有真实来源,相对路径资源、动态 import、前端路由才成立(file:// 下禁止 fetch/XHR)。
102
108
  */
103
- func start(html: String): Unit
109
+ func load(source: PageSource): Unit
104
110
 
105
111
  /**
106
- * 按 URL 启动宿主(http(s):// 或 file://)
107
- *
108
- * 与 start(html) 二选一:同一次启动里两者都设时 URL 优先。
109
- * URL 页面有真实来源,相对路径资源、动态 import、前端路由才成立(file:// 下禁止 fetch/XHR)。
112
+ * 注入资产解析器(装配层在 load 之前调用;None = 各端默认读盘策略)
110
113
  */
111
- func startUrl(url: String): Unit
114
+ func setAssetResolver(resolver: Option<AssetResolver>): Unit
112
115
 
113
116
  /**
114
117
  * 注册一段「页面脚本执行前」注入的初始化脚本(多次调用按注册顺序注入)
115
118
  *
116
- * 必须在 start() / startUrl() 之前调用:宿主创建 WebView 时一次性交给 C 桥,桥以
119
+ * 必须在 load() 之前调用:宿主创建 WebView 时一次性交给 C 桥,桥以
117
120
  * document-start 注入(WebKit user script / WebView2 AddScriptToExecuteOnDocumentCreated)。
118
121
  * 因此它对 HTML 与 URL 两种页面来源**一视同仁**——插件的 jsShim 走的就是这条通道(RFC §5.4 的 v2 方案),
119
122
  * 不再靠字符串拼 HTML。空串忽略;脚本是普通 JS 片段,不要自带 <script> 标签。
@@ -128,12 +131,12 @@ public interface WebViewHost {
128
131
  func reload(): Unit
129
132
 
130
133
  /**
131
- * 应用窗口配置(标题 / 尺寸);必须在 start() 之前调用,宿主据此创建窗口
134
+ * 应用窗口配置(标题 / 尺寸);必须在 load() 之前调用,宿主据此创建窗口
132
135
  */
133
136
  func setWindowConfig(config: WindowConfig): Unit
134
137
 
135
138
  /**
136
- * 是否允许打开开发者工具;必须在 start() 之前调用
139
+ * 是否允许打开开发者工具;必须在 load() 之前调用
137
140
  */
138
141
  func setDevTools(enabled: Bool): Unit
139
142
 
@@ -175,7 +178,7 @@ public interface WebViewHost {
175
178
 
176
179
  /**
177
180
  * 设置拖放策略(RFC-002 §5):enabled=false 平台不注册 drop target。
178
- * 必须在 `start()` / `startUrl()` 之前调用(Linux 建 view 时决定挂不挂 drag dest);
181
+ * 必须在 `load()` 之前调用(Linux 建 view 时决定挂不挂 drag dest);
179
182
  * 之后调用在未就绪时只记 pending、建窗生效,已就绪平台投 UI 线程重挂。
180
183
  * 平台没有该能力(能力位不含 `CJ_CAP_DRAG_DROP`)时宿主只记日志,不报错。
181
184
  */
@@ -217,7 +220,7 @@ public interface WebViewHost {
217
220
  *
218
221
  * enabled=true 时,用户在标题栏点「最小化」不再缩到任务栏,而是**隐藏窗口**
219
222
  * (= `setWindowVisible(false)` 的效果);还原走应用自己的托盘菜单 / 全局快捷键。
220
- * 必须在 `start()` / `startUrl()` 之前调用(窗口过程要在建窗时就知道这条策略)。
223
+ * 必须在 `load()` 之前调用(窗口过程要在建窗时就知道这条策略)。
221
224
  * 真到最小化那一刻,平台会复核托盘是否**真的**装上了(能力位不含 `CJ_CAP_TRAY`,
222
225
  * 或应用压根没调 `setTray`):没装就如实降级为普通最小化并打一行 stderr——
223
226
  * 窗口藏起来却没有任何还原入口,是比「不支持」更糟的结果。
@@ -246,7 +249,7 @@ public interface WebViewHost {
246
249
  *
247
250
  * 多窗口下按宿主身份路由(C 侧回调首参是句柄),所以每扇窗口装自己的——回调不带参数,
248
251
  * 因为「是哪扇窗」在装配时就已确定(应用可经 `app.hostOf(label)` 逐窗安装)。
249
- * 与 `setShellHandler` 同为**可变槽位**:在 `start()` 之后再装同样生效(路由闭包读字段,不是快照)。
252
+ * 与 `setShellHandler` 同为**可变槽位**:在 `load()` 之后再装同样生效(路由闭包读字段,不是快照)。
250
253
  */
251
254
  func setDestroyHandler(handler: () -> Unit): Unit
252
255
 
@@ -255,7 +258,7 @@ public interface WebViewHost {
255
258
  *
256
259
  * filter 是「描述|模式|描述|模式」拼串(Windows 惯例;GTK 端按同一格式解析),
257
260
  * 例如 "文本文件|*.txt|所有文件|*.*";空串表示不限制。
258
- * 必须在 start() / startUrl() 之后调用——对话框要拿已创建的应用窗口当父窗口。
261
+ * 必须在 load() 之后调用——对话框要拿已创建的应用窗口当父窗口。
259
262
  */
260
263
  func showFileDialog(save: Bool, title: String, filter: String): String
261
264
 
@@ -275,7 +278,7 @@ public interface WebViewHost {
275
278
  * GTK 是另一个 chooser action,Windows 更是另一套 API(`IFileOpenDialog` +
276
279
  * `FOS_PICKFOLDERS`,`GetOpenFileName` 没有这个能力)。桥侧两者共用同名同签名的
277
280
  * `cj_bridge_show_dialog`,以 kind=6 区分(与文件类 / 消息类并列,见 `dlg_req`)。
278
- * 与 `showFileDialog` 同规矩:必须在 start() / startUrl() 之后调用(要拿应用窗口当父窗口)。
281
+ * 与 `showFileDialog` 同规矩:必须在 load() 之后调用(要拿应用窗口当父窗口)。
279
282
  */
280
283
  func showFolderDialog(title: String): String
281
284
 
@@ -290,7 +293,7 @@ public interface WebViewHost {
290
293
  /**
291
294
  * 写文本进系统剪贴板(上限 8 MiB,C 侧 core 校验)
292
295
  *
293
- * 与对话框同类:宿主能力,要在 start() 之后调用(未就绪时返回 false)。
296
+ * 与对话框同类:宿主能力,要在 load() 之后调用(未就绪时返回 false)。
294
297
  */
295
298
  func clipboardWriteText(text: String): Bool
296
299
 
@@ -378,7 +381,7 @@ public interface AppHost {
378
381
  * C 侧所有回调的入口都是模块级 @C 函数(每个宿主注册的是同一个函数指针),句柄是唯一能分辨
379
382
  * 「哪个窗口」的凭据——三类回调都按句柄查表,查到谁就派发给谁。
380
383
  *
381
- * 闭包都是可变的:读的是宿主实例的字段(不是快照),所以应用在 `start()` 之后再调
384
+ * 闭包都是可变的:读的是宿主实例的字段(不是快照),所以应用在 `load()` 之后再调
382
385
  * `setShellHandler` / `setDestroyHandler` 同样生效。
383
386
  */
384
387
  class HostRoute {