quickwin 2.2.0 → 2.3.1

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 (111) hide show
  1. package/README.en.md +7 -7
  2. package/README.md +7 -7
  3. package/constants.ts +1070 -0
  4. package/enum_map.d.ts +130 -0
  5. package/examples/PdfCanvas.js +2 -2
  6. package/examples/PdfCanvas.tsx +3 -2
  7. package/examples/{execd.js → execd/main.js} +717 -148
  8. package/examples/execd/tray.js +192 -0
  9. package/examples/{execd_worker.js → execd/worker.js} +7 -7
  10. package/examples/pdf_preview2.js +46 -46
  11. package/examples/pdf_preview2.ts +55 -54
  12. package/examples/pdf_viewer.js +34 -19
  13. package/examples/pdf_viewer.tsx +6 -5
  14. package/examples/test_gallery.js +35 -20
  15. package/examples/test_gallery.tsx +9 -8
  16. package/examples/test_react_lazy.js +34 -19
  17. package/examples/test_react_lazy.tsx +7 -6
  18. package/examples/test_react_listbox.js +34 -19
  19. package/examples/test_react_listbox.tsx +8 -7
  20. package/examples/test_root_window.js +34 -19
  21. package/examples/test_root_window.tsx +5 -5
  22. package/examples/test_tab.js +34 -19
  23. package/examples/test_tab.tsx +7 -6
  24. package/examples/tray_demo.js +8 -8
  25. package/examples/tray_demo.tsx +9 -8
  26. package/lib/dumpRects.js +2 -2
  27. package/lib/dumpRects.ts +3 -2
  28. package/lib/fetch.js +15 -15
  29. package/lib/fetch.ts +16 -15
  30. package/lib/ffi/bind.js +3 -3
  31. package/lib/ffi/bind.ts +3 -3
  32. package/lib/http-server.js +54 -29
  33. package/lib/http-server.ts +56 -27
  34. package/lib/react-qw/components/Button.js +2 -2
  35. package/lib/react-qw/components/CheckBox.js +4 -4
  36. package/lib/react-qw/components/ComboBox.js +10 -10
  37. package/lib/react-qw/components/DateTimePicker.js +12 -12
  38. package/lib/react-qw/components/Input.js +7 -7
  39. package/lib/react-qw/components/Link.js +4 -4
  40. package/lib/react-qw/components/ListBox.js +12 -12
  41. package/lib/react-qw/components/ListView.js +35 -35
  42. package/lib/react-qw/components/PathPicker.js +3 -3
  43. package/lib/react-qw/components/ProgressBar.js +4 -4
  44. package/lib/react-qw/components/RadioButton.js +4 -4
  45. package/lib/react-qw/components/ScrollView.js +28 -28
  46. package/lib/react-qw/components/Slider.js +11 -11
  47. package/lib/react-qw/components/Tab.js +10 -10
  48. package/lib/react-qw/components/Tooltip.js +8 -8
  49. package/lib/react-qw/components/TreeView.js +20 -20
  50. package/lib/react-qw/index.js +5 -5
  51. package/lib/react-qw/index.ts +6 -5
  52. package/lib/react-qw/props.js +4 -4
  53. package/lib/react-qw/props.ts +5 -4
  54. package/lib/react-qw/reconciler.js +10 -10
  55. package/lib/react-qw/reconciler.ts +11 -10
  56. package/lib/text-measure.js +3 -3
  57. package/lib/text-measure.ts +4 -3
  58. package/lib/websocket.js +17 -17
  59. package/lib/websocket.ts +18 -17
  60. package/lib/windows/advapi32.js +7 -7
  61. package/lib/windows/advapi32.ts +7 -7
  62. package/lib/windows/gdi32.js +16 -16
  63. package/lib/windows/gdi32.ts +16 -16
  64. package/lib/windows/kernel32.js +41 -2
  65. package/lib/windows/kernel32.ts +45 -2
  66. package/lib/windows/shell32.js +24 -3
  67. package/lib/windows/shell32.ts +27 -4
  68. package/lib/windows/structs.js +54 -0
  69. package/lib/windows/structs.ts +63 -1
  70. package/lib/windows/user32.js +18 -6
  71. package/lib/windows/user32.ts +18 -6
  72. package/lib/windows/winspool.js +3 -3
  73. package/lib/windows/winspool.ts +3 -3
  74. package/package.json +4 -3
  75. package/quickwin.d.ts +32 -10
  76. package/qwin-nowasm-x86.exe +0 -0
  77. package/qwin-nowasm.exe +0 -0
  78. package/qwin-x86.exe +0 -0
  79. package/qwin.exe +0 -0
  80. package/test/test_ffi_bind.js +7 -7
  81. package/test/test_ffi_bind.ts +8 -8
  82. package/test/test_ffi_struct.js +2 -2
  83. package/test/test_ffi_struct.ts +3 -3
  84. package/test/test_ffi_windows.js +1 -1
  85. package/test/test_ffi_windows.ts +1 -1
  86. package/test/test_files.js +7 -7
  87. package/test/test_files.ts +8 -8
  88. package/test/test_gdi_draw.js +51 -51
  89. package/test/test_gdi_draw.ts +57 -57
  90. package/test/test_http_server.js +3 -3
  91. package/test/test_http_server.ts +4 -3
  92. package/test/test_ipv6.js +3 -3
  93. package/test/test_ipv6.ts +4 -3
  94. package/test/test_net_event.js +3 -3
  95. package/test/test_net_event.ts +4 -3
  96. package/test/test_printer.js +4 -4
  97. package/test/test_printer.ts +5 -5
  98. package/test/test_react_complex.js +36 -21
  99. package/test/test_react_counter.js +34 -19
  100. package/test/test_react_flex.js +36 -21
  101. package/test/test_react_hidden.js +34 -19
  102. package/test/test_react_listview_link.js +35 -20
  103. package/test/test_react_notify.js +52 -37
  104. package/test/test_react_render.js +34 -19
  105. package/test/test_registry.js +6 -6
  106. package/test/test_registry.ts +8 -8
  107. package/test/test_wolfssl_read.js +8 -8
  108. package/test/test_wolfssl_read.ts +9 -8
  109. package/examples/execd.tsx +0 -798
  110. package/examples/execd_worker.ts +0 -127
  111. package/quickwin_const.d.ts +0 -1063
package/lib/fetch.js CHANGED
@@ -180,7 +180,7 @@ async function fetchRequest(req) {
180
180
  sock.set_on_event(fd, (event) => {
181
181
  if (state === ST_DONE)
182
182
  return;
183
- if (event.lNetworkEvents & 16 /* sock.FdEvent.FD_CONNECT */) {
183
+ if (event.lNetworkEvents & 16 /* FdEvent.FD_CONNECT */) {
184
184
  const err = event.iErrorCode[0];
185
185
  if (err !== 0) {
186
186
  doReject(new Error('Connection failed: ' + err));
@@ -194,10 +194,10 @@ async function fetchRequest(req) {
194
194
  return;
195
195
  }
196
196
  if (req.rejectUnauthorized === false) {
197
- wolfssl.wolfSSL_CTX_set_verify(ctx, 0 /* wolfssl.VerifyMode.SSL_VERIFY_NONE */);
197
+ wolfssl.wolfSSL_CTX_set_verify(ctx, 0 /* VerifyMode.SSL_VERIFY_NONE */);
198
198
  }
199
199
  else {
200
- wolfssl.wolfSSL_CTX_set_verify(ctx, 1 /* wolfssl.VerifyMode.SSL_VERIFY_PEER */);
200
+ wolfssl.wolfSSL_CTX_set_verify(ctx, 1 /* VerifyMode.SSL_VERIFY_PEER */);
201
201
  wolfssl.loadTrustedCerts(ctx);
202
202
  }
203
203
  ssl = wolfssl.wolfSSL_new(ctx);
@@ -207,7 +207,7 @@ async function fetchRequest(req) {
207
207
  }
208
208
  wolfssl.wolfSSL_set_fd(ssl, sock.get_fd(fd));
209
209
  if (parsedUrl.hostname) {
210
- wolfssl.wolfSSL_UseSNI(ssl, 0 /* wolfssl.SniType.WOLFSSL_SNI_HOST_NAME */, parsedUrl.hostname);
210
+ wolfssl.wolfSSL_UseSNI(ssl, 0 /* SniType.WOLFSSL_SNI_HOST_NAME */, parsedUrl.hostname);
211
211
  if (req.rejectUnauthorized !== false) {
212
212
  wolfssl.wolfSSL_check_domain_name(ssl, parsedUrl.hostname);
213
213
  }
@@ -219,21 +219,21 @@ async function fetchRequest(req) {
219
219
  state = ST_RECV_HEADERS;
220
220
  }
221
221
  }
222
- if ((event.lNetworkEvents & 1 /* sock.FdEvent.FD_READ */) || (event.lNetworkEvents & 2 /* sock.FdEvent.FD_WRITE */)) {
222
+ if ((event.lNetworkEvents & 1 /* FdEvent.FD_READ */) || (event.lNetworkEvents & 2 /* FdEvent.FD_WRITE */)) {
223
223
  if (state === ST_HANDSHAKE) {
224
224
  if (!ssl) {
225
225
  doReject(new Error('TLS not initialized'));
226
226
  return;
227
227
  }
228
228
  const ret = wolfssl.wolfSSL_connect(ssl);
229
- if (ret === 1 /* wolfssl.ReturnCode.SSL_SUCCESS */) {
229
+ if (ret === 1 /* ReturnCode.SSL_SUCCESS */) {
230
230
  wolfssl.wolfSSL_write(ssl, httpRequest);
231
231
  state = ST_RECV_HEADERS;
232
232
  }
233
233
  else {
234
234
  const err = wolfssl.wolfSSL_get_error(ssl, ret);
235
- if (err !== 2 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ */ &&
236
- err !== 3 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE */) {
235
+ if (err !== 2 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ */ &&
236
+ err !== 3 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE */) {
237
237
  doReject(new Error('TLS handshake failed: ' + err));
238
238
  }
239
239
  }
@@ -251,10 +251,10 @@ async function fetchRequest(req) {
251
251
  }
252
252
  else {
253
253
  const err = wolfssl.wolfSSL_get_error(ssl, n);
254
- if (err === 2 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ */ ||
255
- err === 3 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE */)
254
+ if (err === 2 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ */ ||
255
+ err === 3 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE */)
256
256
  break;
257
- if (n === 0 || err === 6 /* wolfssl.ErrorCode.WOLFSSL_ERROR_ZERO_RETURN */)
257
+ if (n === 0 || err === 6 /* WolfsslErrorCode.WOLFSSL_ERROR_ZERO_RETURN */)
258
258
  break;
259
259
  doReject(new Error('TLS read failed: ' + err));
260
260
  return;
@@ -338,10 +338,10 @@ async function fetchRequest(req) {
338
338
  }
339
339
  else {
340
340
  const err = wolfssl.wolfSSL_get_error(ssl, n);
341
- if (err === 2 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ */ ||
342
- err === 3 /* wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE */)
341
+ if (err === 2 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ */ ||
342
+ err === 3 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE */)
343
343
  break;
344
- if (n === 0 || err === 6 /* wolfssl.ErrorCode.WOLFSSL_ERROR_ZERO_RETURN */)
344
+ if (n === 0 || err === 6 /* WolfsslErrorCode.WOLFSSL_ERROR_ZERO_RETURN */)
345
345
  break;
346
346
  // Fatal TLS error mid-body: the response already
347
347
  // resolved, so fail the body stream instead of
@@ -390,7 +390,7 @@ async function fetchRequest(req) {
390
390
  }
391
391
  }
392
392
  }
393
- if (event.lNetworkEvents & 32 /* sock.FdEvent.FD_CLOSE */) {
393
+ if (event.lNetworkEvents & 32 /* FdEvent.FD_CLOSE */) {
394
394
  if (state === ST_DONE)
395
395
  return;
396
396
  if (state === ST_RECV_HEADERS) {
package/lib/fetch.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  RequestImpl,
13
13
  ResponseImpl,
14
14
  } from './http-common.js'
15
+ import { FdEvent, ReturnCode, SniType, VerifyMode, WolfsslErrorCode } from '../constants'
15
16
 
16
17
  // ── HTTP Response Parser ──
17
18
 
@@ -178,7 +179,7 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
178
179
  sock.set_on_event(fd, (event: { lNetworkEvents: number; iErrorCode: number[] }) => {
179
180
  if (state === ST_DONE) return
180
181
 
181
- if (event.lNetworkEvents & sock.FdEvent.FD_CONNECT) {
182
+ if (event.lNetworkEvents & FdEvent.FD_CONNECT) {
182
183
  const err = event.iErrorCode[0]
183
184
  if (err !== 0) { doReject(new Error('Connection failed: ' + err)); return }
184
185
 
@@ -187,16 +188,16 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
187
188
  ctx = wolfssl.wolfSSL_CTX_new(tlsMethod)
188
189
  if (!ctx) { doReject(new Error('SSL_CTX_new failed')); return }
189
190
  if (req.rejectUnauthorized === false) {
190
- wolfssl.wolfSSL_CTX_set_verify(ctx, wolfssl.VerifyMode.SSL_VERIFY_NONE)
191
+ wolfssl.wolfSSL_CTX_set_verify(ctx, VerifyMode.SSL_VERIFY_NONE)
191
192
  } else {
192
- wolfssl.wolfSSL_CTX_set_verify(ctx, wolfssl.VerifyMode.SSL_VERIFY_PEER)
193
+ wolfssl.wolfSSL_CTX_set_verify(ctx, VerifyMode.SSL_VERIFY_PEER)
193
194
  wolfssl.loadTrustedCerts(ctx)
194
195
  }
195
196
  ssl = wolfssl.wolfSSL_new(ctx)
196
197
  if (!ssl) { doReject(new Error('SSL_new failed')); return }
197
198
  wolfssl.wolfSSL_set_fd(ssl, sock.get_fd(fd))
198
199
  if (parsedUrl.hostname) {
199
- wolfssl.wolfSSL_UseSNI(ssl, wolfssl.SniType.WOLFSSL_SNI_HOST_NAME, parsedUrl.hostname)
200
+ wolfssl.wolfSSL_UseSNI(ssl, SniType.WOLFSSL_SNI_HOST_NAME, parsedUrl.hostname)
200
201
  if (req.rejectUnauthorized !== false) {
201
202
  wolfssl.wolfSSL_check_domain_name(ssl, parsedUrl.hostname)
202
203
  }
@@ -208,17 +209,17 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
208
209
  }
209
210
  }
210
211
 
211
- if ((event.lNetworkEvents & sock.FdEvent.FD_READ) || (event.lNetworkEvents & sock.FdEvent.FD_WRITE)) {
212
+ if ((event.lNetworkEvents & FdEvent.FD_READ) || (event.lNetworkEvents & FdEvent.FD_WRITE)) {
212
213
  if (state === ST_HANDSHAKE) {
213
214
  if (!ssl) { doReject(new Error('TLS not initialized')); return }
214
215
  const ret = wolfssl.wolfSSL_connect(ssl)
215
- if (ret === wolfssl.ReturnCode.SSL_SUCCESS) {
216
+ if (ret === ReturnCode.SSL_SUCCESS) {
216
217
  wolfssl.wolfSSL_write(ssl, httpRequest)
217
218
  state = ST_RECV_HEADERS
218
219
  } else {
219
220
  const err = wolfssl.wolfSSL_get_error(ssl, ret)
220
- if (err !== wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ &&
221
- err !== wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE) {
221
+ if (err !== WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ &&
222
+ err !== WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE) {
222
223
  doReject(new Error('TLS handshake failed: ' + err))
223
224
  }
224
225
  }
@@ -234,9 +235,9 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
234
235
  data = buf.slice(0, n)
235
236
  } else {
236
237
  const err = wolfssl.wolfSSL_get_error(ssl, n)
237
- if (err === wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ ||
238
- err === wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE) break
239
- if (n === 0 || err === wolfssl.ErrorCode.WOLFSSL_ERROR_ZERO_RETURN) break
238
+ if (err === WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ ||
239
+ err === WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE) break
240
+ if (n === 0 || err === WolfsslErrorCode.WOLFSSL_ERROR_ZERO_RETURN) break
240
241
  doReject(new Error('TLS read failed: ' + err))
241
242
  return
242
243
  }
@@ -315,9 +316,9 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
315
316
  data = buf.slice(0, n)
316
317
  } else {
317
318
  const err = wolfssl.wolfSSL_get_error(ssl, n)
318
- if (err === wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ ||
319
- err === wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE) break
320
- if (n === 0 || err === wolfssl.ErrorCode.WOLFSSL_ERROR_ZERO_RETURN) break
319
+ if (err === WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ ||
320
+ err === WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE) break
321
+ if (n === 0 || err === WolfsslErrorCode.WOLFSSL_ERROR_ZERO_RETURN) break
321
322
  // Fatal TLS error mid-body: the response already
322
323
  // resolved, so fail the body stream instead of
323
324
  // silently waiting for an FD event that never comes.
@@ -355,7 +356,7 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
355
356
  }
356
357
  }
357
358
 
358
- if (event.lNetworkEvents & sock.FdEvent.FD_CLOSE) {
359
+ if (event.lNetworkEvents & FdEvent.FD_CLOSE) {
359
360
  if (state === ST_DONE) return
360
361
  if (state === ST_RECV_HEADERS) {
361
362
  doReject(new Error('Connection closed before response'))
package/lib/ffi/bind.js CHANGED
@@ -73,7 +73,7 @@ function makeFn(proc, sig, encoders, decoders) {
73
73
  }
74
74
  return (...a) => callPacked(proc, ps, a, encoders, decoders);
75
75
  }
76
- // 每个参数在 argFrame 里占的字节数。与 quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
76
+ // 每个参数在 argFrame 里占的字节数。与 src/quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
77
77
  // 保持一致(ia32:≤4B 类型进 4 字节、更大的进 8 字节;x64:恒 8 字节槽)。
78
78
  // 注意这是「槽宽」,不是类型的 sizeof——同一定义也用于返回槽。指针布局恒指针宽。
79
79
  const IA32_ARG_SIZE = {
@@ -334,7 +334,7 @@ const leakRegistry = new FinalizationRegistry((held) => {
334
334
  // args/ret 为「底档 + '!' 绑定」的记录(与 ParsedSig 同形态——修饰随 token 走,
335
335
  // 消费者要么整体拿到、要么整体拿不到)。'!' 违约(C 传入 0 / fn 返回 0)打印诊断并
336
336
  // 中止本次分发(fn 不被调用 / 结果槽保持 0),与 fn-throw 同风格:此处不 throw,
337
- // 异常通道只留给用户回调(C 桩捕获 wrapper 异常并清零,quickjs-ffi-closure.c)。
337
+ // 异常通道只留给用户回调(C 桩捕获 wrapper 异常并清零,src/quickjs-ffi-closure.c)。
338
338
  function dispatchClosure(args, ret, fn, frameBuf, retBuf, sig) {
339
339
  const dv = new DataView(retBuf);
340
340
  const is64 = os.arch === 'x64';
@@ -409,7 +409,7 @@ export function closure(sig, fn, opts) {
409
409
  const retSpec = { tok: cTokenToType(retToken), nonnull: retToken.endsWith('!') };
410
410
  const is64 = os.arch === 'x64';
411
411
  const stackCapture = argSpecs.reduce((s, x) => s + IA32_ARG_SIZE[x.tok], 0);
412
- // C 桩捕获窗定长(quickjs-ffi-closure.c:x64 栈区 96B=12 槽,第 5 参起在栈上,
412
+ // C 桩捕获窗定长(src/quickjs-ffi-closure.c:x64 栈区 96B=12 槽,第 5 参起在栈上,
413
413
  // 即至多 16 参;ia32 16×4B)——超限 dispatch 会读到捕获缓冲之外的垃圾,或
414
414
  // RangeError 被吞成返回 0,故创建期 fail-fast。扩容改 C 侧 buf 尺寸并同步放宽。
415
415
  if (is64 ? argSpecs.length > 16 : stackCapture > 64) {
package/lib/ffi/bind.ts CHANGED
@@ -123,7 +123,7 @@ function makeFn(proc: number, sig: string, encoders: CodecMap, decoders: CodecMa
123
123
  return (...a: unknown[]) => callPacked(proc, ps, a, encoders, decoders)
124
124
  }
125
125
 
126
- // 每个参数在 argFrame 里占的字节数。与 quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
126
+ // 每个参数在 argFrame 里占的字节数。与 src/quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
127
127
  // 保持一致(ia32:≤4B 类型进 4 字节、更大的进 8 字节;x64:恒 8 字节槽)。
128
128
  // 注意这是「槽宽」,不是类型的 sizeof——同一定义也用于返回槽。指针布局恒指针宽。
129
129
  const IA32_ARG_SIZE: Record<C_BasicType_No_Void, number> = {
@@ -429,7 +429,7 @@ const leakRegistry: FinalizationRegistry<{ sig: string, ptr: number }> =
429
429
  // args/ret 为「底档 + '!' 绑定」的记录(与 ParsedSig 同形态——修饰随 token 走,
430
430
  // 消费者要么整体拿到、要么整体拿不到)。'!' 违约(C 传入 0 / fn 返回 0)打印诊断并
431
431
  // 中止本次分发(fn 不被调用 / 结果槽保持 0),与 fn-throw 同风格:此处不 throw,
432
- // 异常通道只留给用户回调(C 桩捕获 wrapper 异常并清零,quickjs-ffi-closure.c)。
432
+ // 异常通道只留给用户回调(C 桩捕获 wrapper 异常并清零,src/quickjs-ffi-closure.c)。
433
433
  function dispatchClosure(
434
434
  args: { tok: C_BasicType_No_Void, nonnull: boolean }[],
435
435
  ret: { tok: C_BasicType, nonnull: boolean },
@@ -508,7 +508,7 @@ export function closure<S extends string>(sig: S, fn: BindFn<S, {}, {}>,
508
508
  const retSpec = { tok: cTokenToType(retToken), nonnull: retToken.endsWith('!') }
509
509
  const is64 = os.arch === 'x64'
510
510
  const stackCapture = argSpecs.reduce((s, x) => s + IA32_ARG_SIZE[x.tok], 0)
511
- // C 桩捕获窗定长(quickjs-ffi-closure.c:x64 栈区 96B=12 槽,第 5 参起在栈上,
511
+ // C 桩捕获窗定长(src/quickjs-ffi-closure.c:x64 栈区 96B=12 槽,第 5 参起在栈上,
512
512
  // 即至多 16 参;ia32 16×4B)——超限 dispatch 会读到捕获缓冲之外的垃圾,或
513
513
  // RangeError 被吞成返回 0,故创建期 fail-fast。扩容改 C 侧 buf 尺寸并同步放宽。
514
514
  if (is64 ? argSpecs.length > 16 : stackCapture > 64) {
@@ -24,6 +24,11 @@ function concatU8(parts) {
24
24
  return out;
25
25
  }
26
26
  function toArrayBuffer(u8) {
27
+ // 已经是完整 buffer(byteOffset=0、无尾部余量)时零复制返回——发送大
28
+ // body 的主路径,避免每片 send 前白拷一份。视图场景仍 slice 出独立副本
29
+ //(FFI 持有的 buffer 生命周期不受 view 约束)。
30
+ if (u8.byteOffset === 0 && u8.byteLength === u8.buffer.byteLength)
31
+ return u8.buffer;
27
32
  return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
28
33
  }
29
34
  function findCrLfCrLf(buf) {
@@ -163,7 +168,7 @@ export class HttpServer {
163
168
  }
164
169
  this.serverSock = s;
165
170
  sock.set_on_event(s, (event) => {
166
- if (event.lNetworkEvents & 8 /* sock.FdEvent.FD_ACCEPT */)
171
+ if (event.lNetworkEvents & 8 /* FdEvent.FD_ACCEPT */)
167
172
  this.acceptLoop();
168
173
  });
169
174
  return 0;
@@ -190,6 +195,7 @@ export class HttpServer {
190
195
  const c = {
191
196
  sock: ac.handle,
192
197
  parts: [],
198
+ partsLen: 0,
193
199
  pending: null,
194
200
  current: null,
195
201
  outQueue: null,
@@ -207,11 +213,11 @@ export class HttpServer {
207
213
  onConnEvent(c, event) {
208
214
  if (c.closed)
209
215
  return;
210
- if (event.lNetworkEvents & 1 /* sock.FdEvent.FD_READ */)
216
+ if (event.lNetworkEvents & 1 /* FdEvent.FD_READ */)
211
217
  onRead(c);
212
- if (event.lNetworkEvents & 2 /* sock.FdEvent.FD_WRITE */)
218
+ if (event.lNetworkEvents & 2 /* FdEvent.FD_WRITE */)
213
219
  flushQueue(c);
214
- if (event.lNetworkEvents & 32 /* sock.FdEvent.FD_CLOSE */) {
220
+ if (event.lNetworkEvents & 32 /* FdEvent.FD_CLOSE */) {
215
221
  onRead(c);
216
222
  abortConn(c);
217
223
  closeConn(c);
@@ -229,7 +235,9 @@ function onRead(c) {
229
235
  const d = sock.recv(c.sock, 8192);
230
236
  if (!d)
231
237
  break;
232
- c.parts.push(new Uint8Array(d));
238
+ const u8 = new Uint8Array(d);
239
+ c.parts.push(u8);
240
+ c.partsLen += u8.length;
233
241
  }
234
242
  if (!c.closed)
235
243
  tryProcess(c);
@@ -334,9 +342,19 @@ async function writeStreamingBody(c, body, chunked, trailer) {
334
342
  if (done)
335
343
  break;
336
344
  if (value && value.byteLength > 0) {
337
- const frame = chunked ? encodeChunkFrame(value, enc) : value;
338
- if (!queueSend(c, frame)) {
339
- await new Promise((resolve) => { c.resumeRead = resolve; });
345
+ // 切成 ≤64KB 再入 queueSend:单发 200MB 级大 chunk 时
346
+ // toArrayBuffer 会全量 slice 复制,partial drain 又每次复制
347
+ // 剩余(O(n²)),XP 32 位 1GB 上直接把进程水位顶穿导致断连;
348
+ // 小片让每次复制/发送恒为常数大小,backpressure 语义不变。
349
+ const MAX_SEND = 65536;
350
+ for (let off = 0; off < value.byteLength; off += MAX_SEND) {
351
+ const piece = value.byteLength <= MAX_SEND ? value : value.subarray(off, Math.min(off + MAX_SEND, value.byteLength));
352
+ const frame = chunked ? encodeChunkFrame(piece, enc) : piece;
353
+ if (!queueSend(c, frame)) {
354
+ await new Promise((resolve) => { c.resumeRead = resolve; });
355
+ }
356
+ if (c.closed)
357
+ break;
340
358
  }
341
359
  }
342
360
  }
@@ -383,38 +401,43 @@ function whole(c) {
383
401
  function trimParts(c, buf, consumed) {
384
402
  if (consumed >= buf.length) {
385
403
  c.parts = [];
404
+ c.partsLen = 0;
386
405
  }
387
406
  else {
388
407
  c.parts = [buf.subarray(consumed)];
408
+ c.partsLen = buf.length - consumed;
389
409
  }
390
410
  }
391
411
  function tryProcess(c) {
392
412
  if (c.closed || c.current)
393
413
  return;
394
- const buf = whole(c);
395
414
  if (c.pending) {
396
415
  const bodyStart = c.pending.bodyStart;
397
416
  const parsed = c.pending.parsed;
398
- let body;
399
- let end;
400
- if (parsed.chunked) {
401
- const dec = decodeChunked(buf, bodyStart);
402
- if (!dec)
403
- return;
404
- body = dec.data;
405
- end = dec.end;
406
- }
407
- else {
408
- const needed = parsed.contentLength;
409
- if (bodyStart + needed > buf.length)
417
+ if (!parsed.chunked) {
418
+ // O(1) length check while the body is still arriving — only
419
+ // concatenate once, when the last byte is in.
420
+ const end = bodyStart + parsed.contentLength;
421
+ if (end > c.partsLen)
410
422
  return;
411
- body = buf.subarray(bodyStart, bodyStart + needed);
412
- end = bodyStart + needed;
423
+ const buf = whole(c);
424
+ c.pending = null;
425
+ dispatchRequest(c, parsed, buf.subarray(bodyStart, end), end, buf);
426
+ return;
413
427
  }
428
+ // chunked: no total length up front, so a decode attempt still needs
429
+ // the concatenated view (re-tried per read event as before).
430
+ const buf = whole(c);
431
+ const dec = decodeChunked(buf, bodyStart);
432
+ if (!dec)
433
+ return;
414
434
  c.pending = null;
415
- dispatchRequest(c, parsed, body, end);
435
+ dispatchRequest(c, parsed, dec.data, dec.end, buf);
416
436
  return;
417
437
  }
438
+ // Header phase: bodies are checked against `partsLen` above, so a full
439
+ // concatenation is only needed here — once per request, not per read.
440
+ const buf = whole(c);
418
441
  const headerEnd = findCrLfCrLf(buf);
419
442
  if (headerEnd < 0)
420
443
  return;
@@ -432,20 +455,20 @@ function tryProcess(c) {
432
455
  c.pending = { parsed, bodyStart };
433
456
  return;
434
457
  }
435
- dispatchRequest(c, parsed, dec.data, dec.end);
458
+ dispatchRequest(c, parsed, dec.data, dec.end, buf);
436
459
  }
437
460
  else if (parsed.contentLength > 0) {
438
461
  if (bodyStart + parsed.contentLength > buf.length) {
439
462
  c.pending = { parsed, bodyStart };
440
463
  return;
441
464
  }
442
- dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength);
465
+ dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength, buf);
443
466
  }
444
467
  else {
445
- dispatchRequest(c, parsed, new Uint8Array(0), bodyStart);
468
+ dispatchRequest(c, parsed, new Uint8Array(0), bodyStart, buf);
446
469
  }
447
470
  }
448
- function dispatchRequest(c, parsed, body, end) {
471
+ function dispatchRequest(c, parsed, body, end, wholeBuf) {
449
472
  const connHeader = (parsed.headers['connection'] || '').toLowerCase();
450
473
  if (parsed.version === 'HTTP/1.1')
451
474
  c.keepAlive = connHeader.indexOf('close') < 0;
@@ -468,7 +491,9 @@ function dispatchRequest(c, parsed, body, end) {
468
491
  });
469
492
  c.ac = ac;
470
493
  c.current = { req };
471
- trimParts(c, whole(c), end);
494
+ // `wholeBuf` is the concatenation the caller already produced — reuse it
495
+ // instead of concatenating the very same parts a second time.
496
+ trimParts(c, wholeBuf ?? whole(c), end);
472
497
  void handleRequest(c);
473
498
  }
474
499
  async function handleRequest(c) {
@@ -2,6 +2,7 @@ import './polyfill.js'
2
2
  import * as sock from 'sock'
3
3
  import { HeadersImpl, RequestImpl, ResponseImpl, _readStream } from './http-common.js'
4
4
  import { AbortController } from './abort.js'
5
+ import { FdEvent } from '../constants'
5
6
 
6
7
  type SockHandle = import('sock').SockHandle
7
8
 
@@ -34,6 +35,10 @@ interface ActiveReq {
34
35
  interface Conn {
35
36
  sock: SockHandle
36
37
  parts: Uint8Array[]
38
+ /** Cached total length of `parts` — lets the pending-body wait check
39
+ * "have we received enough yet" in O(1) instead of re-concatenating
40
+ * every received byte on each read event (O(n²) for large bodies). */
41
+ partsLen: number
37
42
  pending: PendingReq | null
38
43
  current: ActiveReq | null
39
44
  outQueue: Uint8Array | null
@@ -68,6 +73,10 @@ function concatU8(parts: Uint8Array[]): Uint8Array {
68
73
  }
69
74
 
70
75
  function toArrayBuffer(u8: Uint8Array): ArrayBuffer {
76
+ // 已经是完整 buffer(byteOffset=0、无尾部余量)时零复制返回——发送大
77
+ // body 的主路径,避免每片 send 前白拷一份。视图场景仍 slice 出独立副本
78
+ //(FFI 持有的 buffer 生命周期不受 view 约束)。
79
+ if (u8.byteOffset === 0 && u8.byteLength === u8.buffer.byteLength) return u8.buffer as ArrayBuffer
71
80
  return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer
72
81
  }
73
82
 
@@ -191,7 +200,7 @@ export class HttpServer {
191
200
  if (sock.listen(s, backlog ?? 64) !== 0) { sock.closesocket(s); return -1 }
192
201
  this.serverSock = s
193
202
  sock.set_on_event(s, (event: NetEvent) => {
194
- if (event.lNetworkEvents & sock.FdEvent.FD_ACCEPT) this.acceptLoop()
203
+ if (event.lNetworkEvents & FdEvent.FD_ACCEPT) this.acceptLoop()
195
204
  })
196
205
  return 0
197
206
  }
@@ -212,6 +221,7 @@ export class HttpServer {
212
221
  const c: Conn = {
213
222
  sock: ac.handle,
214
223
  parts: [],
224
+ partsLen: 0,
215
225
  pending: null,
216
226
  current: null,
217
227
  outQueue: null,
@@ -229,9 +239,9 @@ export class HttpServer {
229
239
 
230
240
  private onConnEvent(c: Conn, event: NetEvent): void {
231
241
  if (c.closed) return
232
- if (event.lNetworkEvents & sock.FdEvent.FD_READ) onRead(c)
233
- if (event.lNetworkEvents & sock.FdEvent.FD_WRITE) flushQueue(c)
234
- if (event.lNetworkEvents & sock.FdEvent.FD_CLOSE) {
242
+ if (event.lNetworkEvents & FdEvent.FD_READ) onRead(c)
243
+ if (event.lNetworkEvents & FdEvent.FD_WRITE) flushQueue(c)
244
+ if (event.lNetworkEvents & FdEvent.FD_CLOSE) {
235
245
  onRead(c)
236
246
  abortConn(c)
237
247
  closeConn(c)
@@ -250,7 +260,9 @@ function onRead(c: Conn): void {
250
260
  while (true) {
251
261
  const d = sock.recv(c.sock, 8192)
252
262
  if (!d) break
253
- c.parts.push(new Uint8Array(d))
263
+ const u8 = new Uint8Array(d)
264
+ c.parts.push(u8)
265
+ c.partsLen += u8.length
254
266
  }
255
267
  if (!c.closed) tryProcess(c)
256
268
  }
@@ -349,9 +361,18 @@ async function writeStreamingBody(c: Conn, body: ReadableStream<Uint8Array> | nu
349
361
  const { done, value } = await reader.read()
350
362
  if (done) break
351
363
  if (value && value.byteLength > 0) {
352
- const frame = chunked ? encodeChunkFrame(value, enc) : value
353
- if (!queueSend(c, frame)) {
354
- await new Promise<void>((resolve) => { c.resumeRead = resolve })
364
+ // 切成 ≤64KB 再入 queueSend:单发 200MB 级大 chunk 时
365
+ // toArrayBuffer 会全量 slice 复制,partial drain 又每次复制
366
+ // 剩余(O(n²)),XP 32 位 1GB 上直接把进程水位顶穿导致断连;
367
+ // 小片让每次复制/发送恒为常数大小,backpressure 语义不变。
368
+ const MAX_SEND = 65536
369
+ for (let off = 0; off < value.byteLength; off += MAX_SEND) {
370
+ const piece = value.byteLength <= MAX_SEND ? value : value.subarray(off, Math.min(off + MAX_SEND, value.byteLength))
371
+ const frame = chunked ? encodeChunkFrame(piece, enc) : piece
372
+ if (!queueSend(c, frame)) {
373
+ await new Promise<void>((resolve) => { c.resumeRead = resolve })
374
+ }
375
+ if (c.closed) break
355
376
  }
356
377
  }
357
378
  }
@@ -395,36 +416,42 @@ function whole(c: Conn): Uint8Array {
395
416
  function trimParts(c: Conn, buf: Uint8Array, consumed: number): void {
396
417
  if (consumed >= buf.length) {
397
418
  c.parts = []
419
+ c.partsLen = 0
398
420
  } else {
399
421
  c.parts = [buf.subarray(consumed)]
422
+ c.partsLen = buf.length - consumed
400
423
  }
401
424
  }
402
425
 
403
426
  function tryProcess(c: Conn): void {
404
427
  if (c.closed || c.current) return
405
- const buf = whole(c)
406
428
 
407
429
  if (c.pending) {
408
430
  const bodyStart = c.pending.bodyStart
409
431
  const parsed = c.pending.parsed
410
- let body: Uint8Array
411
- let end: number
412
- if (parsed.chunked) {
413
- const dec = decodeChunked(buf, bodyStart)
414
- if (!dec) return
415
- body = dec.data
416
- end = dec.end
417
- } else {
418
- const needed = parsed.contentLength
419
- if (bodyStart + needed > buf.length) return
420
- body = buf.subarray(bodyStart, bodyStart + needed)
421
- end = bodyStart + needed
432
+ if (!parsed.chunked) {
433
+ // O(1) length check while the body is still arriving — only
434
+ // concatenate once, when the last byte is in.
435
+ const end = bodyStart + parsed.contentLength
436
+ if (end > c.partsLen) return
437
+ const buf = whole(c)
438
+ c.pending = null
439
+ dispatchRequest(c, parsed, buf.subarray(bodyStart, end), end, buf)
440
+ return
422
441
  }
442
+ // chunked: no total length up front, so a decode attempt still needs
443
+ // the concatenated view (re-tried per read event as before).
444
+ const buf = whole(c)
445
+ const dec = decodeChunked(buf, bodyStart)
446
+ if (!dec) return
423
447
  c.pending = null
424
- dispatchRequest(c, parsed, body, end)
448
+ dispatchRequest(c, parsed, dec.data, dec.end, buf)
425
449
  return
426
450
  }
427
451
 
452
+ // Header phase: bodies are checked against `partsLen` above, so a full
453
+ // concatenation is only needed here — once per request, not per read.
454
+ const buf = whole(c)
428
455
  const headerEnd = findCrLfCrLf(buf)
429
456
  if (headerEnd < 0) return
430
457
  const absEnd = headerEnd + 4
@@ -439,19 +466,19 @@ function tryProcess(c: Conn): void {
439
466
  c.pending = { parsed, bodyStart }
440
467
  return
441
468
  }
442
- dispatchRequest(c, parsed, dec.data, dec.end)
469
+ dispatchRequest(c, parsed, dec.data, dec.end, buf)
443
470
  } else if (parsed.contentLength > 0) {
444
471
  if (bodyStart + parsed.contentLength > buf.length) {
445
472
  c.pending = { parsed, bodyStart }
446
473
  return
447
474
  }
448
- dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength)
475
+ dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength, buf)
449
476
  } else {
450
- dispatchRequest(c, parsed, new Uint8Array(0), bodyStart)
477
+ dispatchRequest(c, parsed, new Uint8Array(0), bodyStart, buf)
451
478
  }
452
479
  }
453
480
 
454
- function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: number): void {
481
+ function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: number, wholeBuf?: Uint8Array): void {
455
482
  const connHeader = (parsed.headers['connection'] || '').toLowerCase()
456
483
  if (parsed.version === 'HTTP/1.1') c.keepAlive = connHeader.indexOf('close') < 0
457
484
  else c.keepAlive = connHeader.indexOf('keep-alive') >= 0
@@ -476,7 +503,9 @@ function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: n
476
503
 
477
504
  c.ac = ac
478
505
  c.current = { req }
479
- trimParts(c, whole(c), end)
506
+ // `wholeBuf` is the concatenation the caller already produced — reuse it
507
+ // instead of concatenating the very same parts a second time.
508
+ trimParts(c, wholeBuf ?? whole(c), end)
480
509
  void handleRequest(c)
481
510
  }
482
511
 
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from 'react';
3
3
  export const Button = forwardRef(({ children, onClick, style, disabled }, ref) => {
4
- return (_jsx("w", { type: "BUTTON", text: children != null ? (Array.isArray(children) ? children.join('') : String(children)) : '', ws: 268435456 /* gui.WindowStyle.VISIBLE */, style: style, disabled: disabled, ref: ref, onEvent: (e) => {
5
- if (e.msg === 514 /* gui.WmMsg.LBUTTONUP */)
4
+ return (_jsx("w", { type: "BUTTON", text: children != null ? (Array.isArray(children) ? children.join('') : String(children)) : '', ws: 268435456 /* WindowStyle.VISIBLE */, style: style, disabled: disabled, ref: ref, onEvent: (e) => {
5
+ if (e.msg === 514 /* WmMsg.LBUTTONUP */)
6
6
  onClick?.();
7
7
  } }));
8
8
  });
@@ -10,17 +10,17 @@ export const CheckBox = forwardRef(({ checked: controlledChecked, defaultChecked
10
10
  const h = cbRef.current;
11
11
  if (!h)
12
12
  return;
13
- gui.SendMessage(h, 241 /* gui.ButtonMsg.SETCHECK */, displayChecked ? 1 /* gui.ButtonCheckState.CHECKED */ : 0 /* gui.ButtonCheckState.UNCHECKED */, 0);
13
+ gui.SendMessage(h, 241 /* ButtonMsg.SETCHECK */, displayChecked ? 1 /* ButtonCheckState.CHECKED */ : 0 /* ButtonCheckState.UNCHECKED */, 0);
14
14
  }, [displayChecked]);
15
- return (_jsx("w", { type: "BUTTON", text: label ?? '', ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 3 /* gui.ButtonStyle.AUTOCHECKBOX */, style: style, disabled: disabled, ref: (h) => {
15
+ return (_jsx("w", { type: "BUTTON", text: label ?? '', ws: 268435456 /* WindowStyle.VISIBLE */ | 3 /* ButtonStyle.AUTOCHECKBOX */, style: style, disabled: disabled, ref: (h) => {
16
16
  cbRef.current = h;
17
17
  if (typeof ref === 'function')
18
18
  ref(h);
19
19
  else if (ref)
20
20
  ref.current = h;
21
21
  }, onEvent: (e) => {
22
- if (e.msg === 514 /* gui.WmMsg.LBUTTONUP */) {
23
- const newChecked = gui.SendMessage(e.hwnd, 240 /* gui.ButtonMsg.GETCHECK */, 0, 0) !== 0;
22
+ if (e.msg === 514 /* WmMsg.LBUTTONUP */) {
23
+ const newChecked = gui.SendMessage(e.hwnd, 240 /* ButtonMsg.GETCHECK */, 0, 0) !== 0;
24
24
  if (newChecked !== displayChecked) {
25
25
  if (!isControlled)
26
26
  setInternalChecked(newChecked);