@yufengtadian/freedom-cli 1.12.14 → 1.12.15
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.
package/README.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# freedom-cli
|
|
2
2
|
|
|
3
|
-
Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌面应用(v1.12.
|
|
3
|
+
Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌面应用(v1.12.15)。
|
|
4
4
|
|
|
5
5
|
基于自研 Freedom WebView 壳层(对标 Wails / Tauri):前端完全自由、后端可任意语言、渲染复用系统 WebView(Windows WebView2 / macOS WKWebView / Linux WebKitGTK),产物为单个可执行文件 + resources 目录,前端页面内存加载,不占本地端口。
|
|
6
6
|
|
|
7
|
+
**v1.12.15 补发壳生命周期稳定性修复**:v1.12.14 的 npm 上架时间早于修复提交,上架版预编译壳未包含悬垂指针修复(窗口操作 / 代理端口触发随机退出的根因);本版正式把含 B50 修复的预编译壳随 npm 包分发——
|
|
8
|
+
- 壳销毁生命周期修复:`webview.Destroy()` 后经 Emit / Quit / WindowHandle / binding 回调访问已释放 C 对象(悬垂指针)导致随机退出;新增 `destroyed` 原子标记,Dispatch / Eval / binding 回调销毁后一律跳过原生调用,`Run` 在 `Destroy` 前清空 `App.view`,事件与绑定回调加 `recover` 兜底;
|
|
9
|
+
- 使用全局包构建的下游应用(如 DSH)升级到本版后,随机退出问题一并消除(可用二进制字符串指纹 `freedom: binding panic` 核对)。
|
|
10
|
+
|
|
7
11
|
**v1.12.14 稳定性 / 兼容性 / 打包链路修复**:
|
|
8
12
|
- **Go 壳层并发安全**:`Dispatch` 修复解锁后读共享 index 的数据竞争(消除 UI 事件丢失 / 偶发 panic);`App.view` 增加互斥锁,`Emit / Quit / WindowHandle` 可在任意 goroutine 安全调用(后端事件推送不再与 Run 竞态);
|
|
9
13
|
- **多屏适配**:窗口居中改为按窗口所在监视器工作区(rcWork)居中,副屏 / 负坐标 / 任务栏遮挡下均正确;实现改用纯 syscall,不再依赖 `x/sys/windows` 新版 API(老版本 Go 亦可编译);
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -231,7 +231,12 @@ func (p *ProcBackend) dispatchEvent(event string, dataJSON json.RawMessage) {
|
|
|
231
231
|
fn := p.onEvent
|
|
232
232
|
p.mu.Unlock()
|
|
233
233
|
if fn != nil {
|
|
234
|
-
|
|
234
|
+
// 事件回调在 readLoop goroutine 执行;内部若 panic(如推送期间窗口已销毁)
|
|
235
|
+
// 一旦外泄到 readLoop 会崩掉整个壳进程,此处统一兜底。
|
|
236
|
+
func() {
|
|
237
|
+
defer func() { _ = recover() }()
|
|
238
|
+
fn(event, data)
|
|
239
|
+
}()
|
|
235
240
|
}
|
|
236
241
|
}
|
|
237
242
|
|
|
@@ -158,6 +158,10 @@ func (a *App) Run() {
|
|
|
158
158
|
a.setView(w)
|
|
159
159
|
defer func() {
|
|
160
160
|
_ = a.backend.Close()
|
|
161
|
+
// 先清空视图引用再销毁:Destroy 后 C 层 webview 对象已被 delete,
|
|
162
|
+
// 若此时仍有并发 goroutine(如后端事件推送触发的 Emit/Quit/WindowHandle)
|
|
163
|
+
// 持旧引用调用会访问悬垂指针导致随机崩溃。
|
|
164
|
+
a.setView(nil)
|
|
161
165
|
w.Destroy()
|
|
162
166
|
}()
|
|
163
167
|
|
|
@@ -31,9 +31,11 @@ import (
|
|
|
31
31
|
_ "github.com/webview/webview_go/libs/webview/include"
|
|
32
32
|
"encoding/json"
|
|
33
33
|
"errors"
|
|
34
|
+
"fmt"
|
|
34
35
|
"reflect"
|
|
35
36
|
"runtime"
|
|
36
37
|
"sync"
|
|
38
|
+
"sync/atomic"
|
|
37
39
|
"unsafe"
|
|
38
40
|
)
|
|
39
41
|
|
|
@@ -170,6 +172,11 @@ var (
|
|
|
170
172
|
dispatch = map[uintptr]func(){}
|
|
171
173
|
bindings = map[uintptr]func(id, req string) (interface{}, error){}
|
|
172
174
|
bindNames = map[string]uintptr{} // name -> index,供 Unbind 清理 bindings 条目
|
|
175
|
+
// destroyed 标记 C 层 webview 对象已被销毁。Destroy() 置位后:
|
|
176
|
+
// - Dispatch / Eval 直接跳过 C 调用,避免向已释放的 webview_t 发消息(悬垂指针崩溃);
|
|
177
|
+
// - binding 回调跳过 C.webview_return,避免访问已释放的 C 内存。
|
|
178
|
+
// 壳为单窗口单 webview,全局单例标记即可。
|
|
179
|
+
destroyed atomic.Bool
|
|
173
180
|
)
|
|
174
181
|
|
|
175
182
|
func boolToInt(b bool) C.int {
|
|
@@ -196,6 +203,8 @@ func NewWindow(debug bool, window unsafe.Pointer) WebView {
|
|
|
196
203
|
}
|
|
197
204
|
|
|
198
205
|
func (w *webview) Destroy() {
|
|
206
|
+
// 先置销毁标记:销毁后任何并发 Dispatch/Eval/binding 回调不得再触碰 C 内存。
|
|
207
|
+
destroyed.Store(true)
|
|
199
208
|
C.webview_destroy(w.w)
|
|
200
209
|
}
|
|
201
210
|
|
|
@@ -252,12 +261,18 @@ func (w *webview) Init(js string) {
|
|
|
252
261
|
}
|
|
253
262
|
|
|
254
263
|
func (w *webview) Eval(js string) {
|
|
264
|
+
if destroyed.Load() {
|
|
265
|
+
return
|
|
266
|
+
}
|
|
255
267
|
s := C.CString(js)
|
|
256
268
|
defer C.free(unsafe.Pointer(s))
|
|
257
269
|
C.webview_eval(w.w, s)
|
|
258
270
|
}
|
|
259
271
|
|
|
260
272
|
func (w *webview) Dispatch(f func()) {
|
|
273
|
+
if destroyed.Load() {
|
|
274
|
+
return
|
|
275
|
+
}
|
|
261
276
|
m.Lock()
|
|
262
277
|
for ; dispatch[index] != nil; index++ {
|
|
263
278
|
}
|
|
@@ -272,11 +287,15 @@ func (w *webview) Dispatch(f func()) {
|
|
|
272
287
|
|
|
273
288
|
//export _webviewDispatchGoCallback
|
|
274
289
|
func _webviewDispatchGoCallback(index unsafe.Pointer) {
|
|
290
|
+
// 兜底:回调内任何 panic 不得外泄到 C 层崩掉整个进程。
|
|
291
|
+
defer func() { _ = recover() }()
|
|
275
292
|
m.Lock()
|
|
276
293
|
f := dispatch[uintptr(index)]
|
|
277
294
|
delete(dispatch, uintptr(index))
|
|
278
295
|
m.Unlock()
|
|
279
|
-
f
|
|
296
|
+
if f != nil {
|
|
297
|
+
f()
|
|
298
|
+
}
|
|
280
299
|
}
|
|
281
300
|
|
|
282
301
|
//export _webviewBindingGoCallback
|
|
@@ -286,15 +305,29 @@ func _webviewBindingGoCallback(w C.webview_t, id *C.char, req *C.char, index uin
|
|
|
286
305
|
m.Unlock()
|
|
287
306
|
jsString := func(v interface{}) string { b, _ := json.Marshal(v); return string(b) }
|
|
288
307
|
status, result := 0, ""
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
308
|
+
// 兜底:绑定回调内任何 panic 转为错误回传前端,绝不外泄到 C 层崩进程。
|
|
309
|
+
func() {
|
|
310
|
+
defer func() {
|
|
311
|
+
if r := recover(); r != nil {
|
|
312
|
+
status = -1
|
|
313
|
+
result = jsString(fmt.Sprintf("freedom: binding panic: %v", r))
|
|
314
|
+
}
|
|
315
|
+
}()
|
|
316
|
+
if res, err := f(C.GoString(id), C.GoString(req)); err != nil {
|
|
317
|
+
status = -1
|
|
318
|
+
result = jsString(err.Error())
|
|
319
|
+
} else if b, err := json.Marshal(res); err != nil {
|
|
320
|
+
status = -1
|
|
321
|
+
result = jsString(err.Error())
|
|
322
|
+
} else {
|
|
323
|
+
status = 0
|
|
324
|
+
result = string(b)
|
|
325
|
+
}
|
|
326
|
+
}()
|
|
327
|
+
// 窗口已销毁:C 层 webview 对象已被 delete,不能再调用 webview_return,
|
|
328
|
+
// 否则访问悬垂 webview_t 导致进程随机崩溃。
|
|
329
|
+
if destroyed.Load() {
|
|
330
|
+
return
|
|
298
331
|
}
|
|
299
332
|
s := C.CString(result)
|
|
300
333
|
defer C.free(unsafe.Pointer(s))
|