@next2d/builder 0.1.7 → 0.1.10

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 (75) hide show
  1. package/dist/cli.d.ts +59 -0
  2. package/dist/cli.js +141 -0
  3. package/dist/constants.d.ts +53 -0
  4. package/dist/constants.js +64 -0
  5. package/dist/context.d.ts +29 -0
  6. package/dist/context.js +19 -0
  7. package/dist/electron.d.ts +10 -0
  8. package/dist/electron.js +142 -0
  9. package/dist/index.js +18 -754
  10. package/dist/native.d.ts +27 -0
  11. package/dist/native.js +123 -0
  12. package/dist/utils.d.ts +27 -0
  13. package/dist/utils.js +40 -0
  14. package/dist/web.d.ts +18 -0
  15. package/dist/web.js +70 -0
  16. package/dist/xbox.d.ts +62 -0
  17. package/dist/xbox.js +426 -0
  18. package/package.json +12 -9
  19. package/templates/xbox/CMakeLists.txt +116 -11
  20. package/templates/xbox/README.md +34 -8
  21. package/templates/xbox/cmake/FetchDawn.cmake +47 -6
  22. package/templates/xbox/cmake/FindV8.cmake +15 -0
  23. package/templates/xbox/js/bootstrap.js +151 -28
  24. package/templates/xbox/js/selftest.js +740 -90
  25. package/templates/xbox/src/AssetLoader.cpp +115 -12
  26. package/templates/xbox/src/AssetLoader.h +4 -0
  27. package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
  28. package/templates/xbox/src/EmbeddedAssets.h +60 -0
  29. package/templates/xbox/src/EventLoop.cpp +13 -1
  30. package/templates/xbox/src/EventLoop.h +12 -0
  31. package/templates/xbox/src/bindings/Audio.cpp +211 -31
  32. package/templates/xbox/src/bindings/Bindings.h +10 -0
  33. package/templates/xbox/src/bindings/Canvas.cpp +77 -2
  34. package/templates/xbox/src/bindings/Canvas2D.cpp +496 -43
  35. package/templates/xbox/src/bindings/Console.cpp +6 -1
  36. package/templates/xbox/src/bindings/DomShims.cpp +143 -0
  37. package/templates/xbox/src/bindings/EventTarget.cpp +11 -0
  38. package/templates/xbox/src/bindings/Fetch.cpp +2 -8
  39. package/templates/xbox/src/bindings/Image.cpp +243 -15
  40. package/templates/xbox/src/bindings/Polyfills.cpp +1226 -0
  41. package/templates/xbox/src/bindings/RasterCore.h +105 -1
  42. package/templates/xbox/src/bindings/Video.cpp +153 -10
  43. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +844 -18
  44. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +7 -9
  45. package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
  46. package/templates/xbox/src/gpu/DawnContext.cpp +245 -7
  47. package/templates/xbox/src/gpu/DawnContext.h +18 -0
  48. package/templates/xbox/src/main.cpp +459 -8
  49. package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
  50. package/templates/xbox/src/platform/AudioDecoder.h +18 -0
  51. package/templates/xbox/src/platform/AudioEngine.cpp +37 -6
  52. package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
  53. package/templates/xbox/src/platform/DecodeQueue.h +27 -0
  54. package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
  55. package/templates/xbox/src/platform/ImageDecoder.h +18 -0
  56. package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
  57. package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
  58. package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
  59. package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
  60. package/templates/xbox/src/v8/CodeCache.h +223 -0
  61. package/templates/xbox/src/v8/V8Runtime.cpp +76 -11
  62. package/templates/xbox/src/v8/V8Util.h +79 -0
  63. package/templates/xbox/src/v8/WeakHandle.h +45 -0
  64. package/templates/xbox/src/worker/WorkerRuntime.cpp +432 -48
  65. package/templates/xbox/src/worker/WorkerRuntime.h +43 -13
  66. package/templates/xbox/tests/check_local.sh +7 -2
  67. package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
  68. package/templates/xbox/tests/raster_test.cpp +50 -0
  69. package/templates/xbox/tests/windows_platform_test.cpp +75 -3
  70. package/templates/xbox/third_party/LICENSES.md +14 -0
  71. package/templates/xbox/third_party/dr_mp3.h +5412 -0
  72. package/templates/xbox/third_party/dr_wav.h +9131 -0
  73. package/templates/xbox/third_party/stb_image.h +7988 -0
  74. package/templates/xbox/third_party/stb_truetype.h +5079 -0
  75. package/templates/xbox/third_party/stb_vorbis.c +5584 -0
@@ -5,6 +5,10 @@
5
5
  #include "v8/V8Util.h"
6
6
 
7
7
  #include <Windows.h>
8
+ #include <bcrypt.h>
9
+ #include <cstdio>
10
+
11
+ #pragma comment(lib, "bcrypt.lib")
8
12
 
9
13
  namespace next2d {
10
14
 
@@ -107,6 +111,116 @@ void ClipboardWriteText(const v8::FunctionCallbackInfo<v8::Value>& args)
107
111
  resolver->Resolve(ctx, v8::Undefined(isolate)).Check();
108
112
  }
109
113
 
114
+ // crypto.getRandomValues(typedArray): OS 乱数で埋めて同じ view を返す
115
+ void CryptoGetRandomValues(const v8::FunctionCallbackInfo<v8::Value>& args)
116
+ {
117
+ v8::Isolate* isolate = args.GetIsolate();
118
+ if (args.Length() < 1 || !args[0]->IsArrayBufferView()) {
119
+ v8util::ThrowTypeError(isolate, "getRandomValues requires a TypedArray");
120
+ return;
121
+ }
122
+ auto view = args[0].As<v8::ArrayBufferView>();
123
+ std::vector<uint8_t> bytes(view->ByteLength());
124
+ if (!bytes.empty()) {
125
+ BCryptGenRandom(nullptr, bytes.data(), static_cast<ULONG>(bytes.size()),
126
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG);
127
+ // view のバッキングへ書き戻す
128
+ auto ab = view->Buffer();
129
+ auto* dst = static_cast<uint8_t*>(ab->GetBackingStore()->Data()) + view->ByteOffset();
130
+ memcpy(dst, bytes.data(), bytes.size());
131
+ }
132
+ args.GetReturnValue().Set(args[0]);
133
+ }
134
+
135
+ // crypto.randomUUID(): RFC 4122 v4
136
+ void CryptoRandomUUID(const v8::FunctionCallbackInfo<v8::Value>& args)
137
+ {
138
+ v8::Isolate* isolate = args.GetIsolate();
139
+ uint8_t b[16] = {};
140
+ BCryptGenRandom(nullptr, b, sizeof(b), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
141
+ b[6] = static_cast<uint8_t>((b[6] & 0x0f) | 0x40); // version 4
142
+ b[8] = static_cast<uint8_t>((b[8] & 0x3f) | 0x80); // variant
143
+ char buf[37];
144
+ std::snprintf(buf, sizeof(buf),
145
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
146
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
147
+ b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
148
+ args.GetReturnValue().Set(Str(isolate, buf));
149
+ }
150
+
151
+ // --- 名前付きストレージの永続化バックエンド ---------------------------------
152
+ // localStorage / indexedDB のセマンティクスは Polyfills.cpp の JS 側が実装し、
153
+ // ここは名前ごとのファイル I/O のみを担う。
154
+ // 保存先: %LOCALAPPDATA%\Next2D\<name>.json (PC / Gaming.Desktop / CI)。
155
+ //
156
+ // コンソール実機 (Gaming.Xbox.*) のストレージ対応 (要 devkit 検証):
157
+ // - localStorage / indexedDB のようなタイトルローカル永続化は XPersistentLocalStorage
158
+ // (XPersistentLocalStorageGetPath で得たパス配下へ同じファイル I/O) が対応先。
159
+ // 同期 API のため現行の同期ストレージ契約 (StorageLoad/StorageSave) にそのまま載る。
160
+ // - ユーザー毎のクラウド同期セーブが要る場合のみ XGameSave (非同期・XUser 必須) を使う。
161
+ // いずれも console GDK ヘッダと signed-in user / SCID 構成が必要で、devkit 実機でしか
162
+ // コンパイル・実行検証できないため未実装 (現状はファイルパスのフォールバックで動作)。
163
+ std::wstring StorageFilePath(const std::string& name)
164
+ {
165
+ // ファイル名として安全な文字だけを残す
166
+ std::wstring safe;
167
+ for (char c : name) {
168
+ const bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
169
+ (c >= '0' && c <= '9') || c == '_' || c == '-';
170
+ safe.push_back(ok ? static_cast<wchar_t>(c) : L'_');
171
+ }
172
+ if (safe.empty()) {
173
+ safe = L"storage";
174
+ }
175
+ wchar_t base[MAX_PATH] = {};
176
+ if (!GetEnvironmentVariableW(L"LOCALAPPDATA", base, MAX_PATH)) {
177
+ return safe + L".json"; // フォールバック: カレント
178
+ }
179
+ std::wstring dir = std::wstring(base) + L"\\Next2D";
180
+ CreateDirectoryW(dir.c_str(), nullptr);
181
+ return dir + L"\\" + safe + L".json";
182
+ }
183
+
184
+ // __next2d_storage_load(name) -> string
185
+ void StorageLoad(const v8::FunctionCallbackInfo<v8::Value>& args)
186
+ {
187
+ v8::Isolate* isolate = args.GetIsolate();
188
+ const std::string name = args.Length() > 0
189
+ ? v8util::ToStdString(isolate, args[0]) : "localStorage";
190
+ HANDLE h = CreateFileW(StorageFilePath(name).c_str(), GENERIC_READ, FILE_SHARE_READ,
191
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
192
+ if (h == INVALID_HANDLE_VALUE) {
193
+ args.GetReturnValue().Set(Str(isolate, ""));
194
+ return;
195
+ }
196
+ DWORD size = GetFileSize(h, nullptr);
197
+ std::string data(size, '\0');
198
+ DWORD read = 0;
199
+ ReadFile(h, data.data(), size, &read, nullptr);
200
+ CloseHandle(h);
201
+ data.resize(read);
202
+ args.GetReturnValue().Set(Str(isolate, data));
203
+ }
204
+
205
+ // __next2d_storage_save(name, data)
206
+ void StorageSave(const v8::FunctionCallbackInfo<v8::Value>& args)
207
+ {
208
+ if (args.Length() < 2) {
209
+ return;
210
+ }
211
+ v8::Isolate* isolate = args.GetIsolate();
212
+ const std::string name = v8util::ToStdString(isolate, args[0]);
213
+ const std::string data = v8util::ToStdString(isolate, args[1]);
214
+ HANDLE h = CreateFileW(StorageFilePath(name).c_str(), GENERIC_WRITE, 0,
215
+ nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
216
+ if (h == INVALID_HANDLE_VALUE) {
217
+ return;
218
+ }
219
+ DWORD written = 0;
220
+ WriteFile(h, data.data(), static_cast<DWORD>(data.size()), &written, nullptr);
221
+ CloseHandle(h);
222
+ }
223
+
110
224
  // new OffscreenCanvas(width, height)
111
225
  void OffscreenCanvasConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
112
226
  {
@@ -144,6 +258,19 @@ void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostCon
144
258
  SetValue(isolate, global, "self", global);
145
259
  NoopEventTarget(isolate, global);
146
260
 
261
+ // crypto (getRandomValues / randomUUID — OS 乱数)。
262
+ // player の WebGLUtil が ID 生成に crypto.randomUUID を使う。
263
+ {
264
+ v8::Local<v8::Object> crypto = v8::Object::New(isolate);
265
+ SetMethod(isolate, crypto, "getRandomValues", CryptoGetRandomValues);
266
+ SetMethod(isolate, crypto, "randomUUID", CryptoRandomUUID);
267
+ SetValue(isolate, global, "crypto", crypto);
268
+ }
269
+
270
+ // localStorage のファイル I/O バックエンド (Polyfills.cpp の JS が利用)
271
+ SetMethod(isolate, global, "__next2d_storage_load", StorageLoad);
272
+ SetMethod(isolate, global, "__next2d_storage_save", StorageSave);
273
+
147
274
  // devicePixelRatio
148
275
  SetValue(isolate, global, "devicePixelRatio",
149
276
  v8::Number::New(isolate, host->device_pixel_ratio));
@@ -158,6 +285,15 @@ void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostCon
158
285
  SetValue(isolate, global, "innerWidth", v8::Integer::New(isolate, host->viewport_width));
159
286
  SetValue(isolate, global, "innerHeight", v8::Integer::New(isolate, host->viewport_height));
160
287
 
288
+ // scrollX / scrollY (非スクロールの全画面アプリでは常に 0)。
289
+ // 未定義だと player の PlayerSetCurrentMousePointService 冒頭
290
+ // `let x = window.scrollX;` が undefined になり、以降 x が NaN 化して
291
+ // stage.pointer 全体が NaN → ヒットテストが全 miss → ボタンが一切反応しない。
292
+ SetValue(isolate, global, "scrollX", v8::Integer::New(isolate, 0));
293
+ SetValue(isolate, global, "scrollY", v8::Integer::New(isolate, 0));
294
+ SetValue(isolate, global, "pageXOffset", v8::Integer::New(isolate, 0));
295
+ SetValue(isolate, global, "pageYOffset", v8::Integer::New(isolate, 0));
296
+
161
297
  // navigator (gpu/getGamepads は後段のバインディングが付与)
162
298
  v8::Local<v8::Object> navigator = v8::Object::New(isolate);
163
299
  SetValue(isolate, navigator, "userAgent",
@@ -176,6 +312,8 @@ void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostCon
176
312
  // document
177
313
  v8::Local<v8::Object> document = v8::Object::New(isolate);
178
314
  SetMethod(isolate, document, "createElement", CreateElement);
315
+ // 起動時点で読み込み完了扱い (ゲームの boot 分岐が readyState === "loading" で待機しないように)
316
+ SetValue(isolate, document, "readyState", Str(isolate, "complete"));
179
317
  SetMethod(isolate, document, "getElementById",
180
318
  [](const v8::FunctionCallbackInfo<v8::Value>& a) { a.GetReturnValue().SetNull(); });
181
319
  SetMethod(isolate, document, "querySelector",
@@ -216,6 +354,11 @@ void InstallGlobalBindings(v8::Isolate* isolate, v8::Local<v8::Object> global, H
216
354
  InstallAudio(isolate, global, host);
217
355
  InstallWebGPU(isolate, global, host); // navigator.gpu
218
356
  InstallGamepad(isolate, global, host); // navigator.getGamepads
357
+
358
+ // TextEncoder/TextDecoder/queueMicrotask。bootstrap.js はメインコンテキスト
359
+ // でのみ実行されるため、worker を含む全コンテキストにはここで注入する
360
+ // (Vite バンドルの worker は評価時に TextDecoder を要求する)。
361
+ InstallPolyfills(isolate, isolate->GetCurrentContext());
219
362
  }
220
363
 
221
364
  } // namespace next2d
@@ -96,6 +96,15 @@ void DispatchEvent(v8::Isolate* isolate, v8::Local<v8::Object> target,
96
96
  return;
97
97
  }
98
98
  const std::string type = ToStdString(isolate, type_val);
99
+
100
+ // event.target / currentTarget を配送先に設定する。
101
+ // player のポインタハンドラは `let t = e.target; t && (…, t.setPointerCapture(…), …)`
102
+ // のように先頭で event.target をガードするため、未設定だと pointerdown/move が
103
+ // 丸ごと no-op になり「ボタンが全く反応しない」。DOM 準拠でここで設定する。
104
+ (void) event->Set(ctx, Str(isolate, "target"), target);
105
+ (void) event->Set(ctx, Str(isolate, "currentTarget"), target);
106
+ (void) event->Set(ctx, Str(isolate, "srcElement"), target);
107
+
99
108
  v8::Local<v8::Value> args1[1] = { event };
100
109
 
101
110
  // on<type> プロパティ
@@ -104,6 +113,7 @@ void DispatchEvent(v8::Isolate* isolate, v8::Local<v8::Object> target,
104
113
  if (target->Get(ctx, Str(isolate, onname.c_str())).ToLocal(&on) && on->IsFunction()) {
105
114
  v8::TryCatch tc(isolate);
106
115
  (void) on.As<v8::Function>()->Call(ctx, target, 1, args1);
116
+ v8util::ReportCaught(isolate, &tc, onname.c_str());
107
117
  }
108
118
 
109
119
  // __listeners[type]
@@ -118,6 +128,7 @@ void DispatchEvent(v8::Isolate* isolate, v8::Local<v8::Object> target,
118
128
  if (arr->Get(ctx, i).ToLocal(&fn) && fn->IsFunction()) {
119
129
  v8::TryCatch tc(isolate);
120
130
  (void) fn.As<v8::Function>()->Call(ctx, target, 1, args1);
131
+ v8util::ReportCaught(isolate, &tc, ("listener:" + type).c_str());
121
132
  }
122
133
  }
123
134
  }
@@ -3,6 +3,7 @@
3
3
  #include "HostContext.h"
4
4
  #include "AssetLoader.h"
5
5
  #include "v8/V8Util.h"
6
+ #include "v8/WeakHandle.h"
6
7
 
7
8
  #include <cstring>
8
9
  #include <vector>
@@ -84,12 +85,6 @@ void ResponseJson(const v8::FunctionCallbackInfo<v8::Value>& args)
84
85
  args.GetReturnValue().Set(resolver->GetPromise());
85
86
  }
86
87
 
87
- // External に持たせた vector を解放するための弱参照コールバック
88
- void ReleaseResponseData(const v8::WeakCallbackInfo<std::vector<uint8_t>>& info)
89
- {
90
- delete info.GetParameter();
91
- }
92
-
93
88
  v8::Local<v8::Object> MakeResponse(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
94
89
  std::vector<uint8_t>&& bytes, bool ok, int status)
95
90
  {
@@ -101,8 +96,7 @@ v8::Local<v8::Object> MakeResponse(v8::Isolate* isolate, v8::Local<v8::Context>
101
96
  response->SetInternalField(0, v8::External::New(isolate, data));
102
97
 
103
98
  // GC 時に解放
104
- auto* handle = new v8::Global<v8::Object>(isolate, response);
105
- handle->SetWeak(data, ReleaseResponseData, v8::WeakCallbackType::kParameter);
99
+ v8util::AttachWeak(isolate, response, data);
106
100
 
107
101
  v8util::SetValue(isolate, response, "ok", v8::Boolean::New(isolate, ok));
108
102
  v8util::SetValue(isolate, response, "status", v8::Integer::New(isolate, status));
@@ -2,13 +2,19 @@
2
2
 
3
3
  #include "HostContext.h"
4
4
  #include "AssetLoader.h"
5
+ #include "EventTarget.h"
5
6
  #include "ImageSource.h"
6
- #include "platform/WicDecoder.h"
7
+ #include "platform/DecodeQueue.h"
8
+ #include "platform/ImageDecoder.h"
7
9
  #include "v8/V8Util.h"
10
+ #include "v8/WeakHandle.h"
8
11
 
9
12
  #include <objbase.h>
10
13
 
11
14
  #include <cstring>
15
+ #include <iostream>
16
+ #include <memory>
17
+ #include <utility>
12
18
  #include <vector>
13
19
 
14
20
  namespace next2d {
@@ -18,12 +24,112 @@ using v8util::ToStdString;
18
24
 
19
25
  namespace {
20
26
 
21
- void ReleaseDecoded(const v8::WeakCallbackInfo<DecodedImage>& info)
27
+ // base64 デコード (data: URL 用。Vite は小さい画像をインライン化する)
28
+ bool ImageDecodeBase64(const std::string& in, std::vector<uint8_t>* out)
22
29
  {
23
- delete info.GetParameter();
30
+ auto val = [](char c) -> int {
31
+ if (c >= 'A' && c <= 'Z') return c - 'A';
32
+ if (c >= 'a' && c <= 'z') return c - 'a' + 26;
33
+ if (c >= '0' && c <= '9') return c - '0' + 52;
34
+ if (c == '+') return 62;
35
+ if (c == '/') return 63;
36
+ return -1;
37
+ };
38
+ out->clear();
39
+ out->reserve(in.size() / 4 * 3);
40
+ int buf = 0, bits = 0;
41
+ for (char c : in) {
42
+ if (c == '=' || c == '\r' || c == '\n') continue;
43
+ const int v = val(c);
44
+ if (v < 0) return false;
45
+ buf = buf << 6 | v;
46
+ bits += 6;
47
+ if (bits >= 8) {
48
+ bits -= 8;
49
+ out->push_back(static_cast<uint8_t>(buf >> bits & 0xFF));
50
+ }
51
+ }
52
+ return true;
53
+ }
54
+
55
+ // src に指定された URL (assets 相対 / data:) を読み込み、WIC デコードを
56
+ // バックグラウンドスレッド (DecodeQueue) へ逃がす。完了はメインループの
57
+ // decodequeue::Pump() から反映される (ブラウザ同様、src 設定後に非同期で
58
+ // load イベントが届く。img.src = url; img.onload = fn の順でも拾える)。
59
+ // 同期デコードは画面遷移時に 1 秒超のフレームヒッチになる (--perf で計測済み)。
60
+ void LoadImageFromSrc(v8::Isolate* isolate, v8::Local<v8::Object> self, const std::string& url)
61
+ {
62
+ std::vector<uint8_t> input;
63
+ if (url.rfind("data:", 0) == 0) {
64
+ const auto comma = url.find(',');
65
+ if (comma != std::string::npos) {
66
+ const std::string meta = url.substr(5, comma - 5);
67
+ const std::string payload = url.substr(comma + 1);
68
+ if (meta.find(";base64") != std::string::npos) {
69
+ ImageDecodeBase64(payload, &input);
70
+ }
71
+ }
72
+ } else {
73
+ // pak (メモリ) 読みなので同期で問題ない。重いのはデコードのみ。
74
+ AssetLoader* assets = HostContext::From(isolate)->assets;
75
+ auto bytes = assets->ReadBinary(url);
76
+ if (bytes) {
77
+ input = std::move(*bytes);
78
+ }
79
+ }
80
+
81
+ auto image_ref = std::make_shared<v8::Global<v8::Object>>(isolate, self);
82
+ auto decoded = std::make_shared<DecodedImage>();
83
+ auto ok = std::make_shared<bool>(false);
84
+
85
+ decodequeue::Submit(
86
+ // プールスレッド: WIC デコードのみ (V8 に触れない)
87
+ [input = std::move(input), decoded, ok]() {
88
+ *ok = DecodeImage(input, *decoded);
89
+ },
90
+ // メインスレッド: 結果を image オブジェクトへ反映し load を発火
91
+ [isolate, image_ref, decoded, ok]() {
92
+ v8::HandleScope hs(isolate);
93
+ v8::Local<v8::Object> img_obj = image_ref->Get(isolate);
94
+ v8::Local<v8::Context> ctx = img_obj->GetCreationContextChecked();
95
+ v8::Context::Scope cs(ctx);
96
+
97
+ // «堅牢化» デコード失敗時の扱い。
98
+ // player の ShapeLoadSrcUseCase は image に "load" ハンドラのみ登録し
99
+ // "error" を監視しない。"error" だと Event.COMPLETE が来ず ImageShapeAtom
100
+ // .load() の Promise が永久未解決になり、ページ build (=画面遷移) が
101
+ // ローディングのままフリーズする。失敗時は 1x1 透明のプレースホルダを
102
+ // 割り当てて "load" として扱う (該当画像だけが空になる)。
103
+ auto* img = new DecodedImage(std::move(*decoded));
104
+ if (!*ok) {
105
+ img->width = 1;
106
+ img->height = 1;
107
+ img->rgba.assign(4, 0);
108
+ }
109
+
110
+ img_obj->SetInternalField(0, v8::External::New(isolate, img));
111
+ v8util::AttachWeak(isolate, img_obj, img);
112
+ v8util::SetValue(isolate, img_obj, "width",
113
+ v8::Integer::NewFromUnsigned(isolate, img->width));
114
+ v8util::SetValue(isolate, img_obj, "height",
115
+ v8::Integer::NewFromUnsigned(isolate, img->height));
116
+ v8util::SetValue(isolate, img_obj, "naturalWidth",
117
+ v8::Integer::NewFromUnsigned(isolate, img->width));
118
+ v8util::SetValue(isolate, img_obj, "naturalHeight",
119
+ v8::Integer::NewFromUnsigned(isolate, img->height));
120
+ v8util::SetValue(isolate, img_obj, "complete", v8::Boolean::New(isolate, true));
121
+
122
+ // 常に "load" を発火し、遷移を進行させる
123
+ v8::Local<v8::Object> ev = v8::Object::New(isolate);
124
+ v8util::SetValue(isolate, ev, "type", Str(isolate, "load"));
125
+ v8util::SetValue(isolate, ev, "target", img_obj);
126
+ DispatchEvent(isolate, img_obj, ev);
127
+
128
+ image_ref->Reset();
129
+ });
24
130
  }
25
131
 
26
- // Image コンストラクタ: src セッターで読み込み+デコードし onload を発火する。
132
+ // Image コンストラクタ: src セッターで読み込み+デコードし load イベントを発火する。
27
133
  void ImageConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
28
134
  {
29
135
  v8::Isolate* isolate = args.GetIsolate();
@@ -31,11 +137,46 @@ void ImageConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
31
137
  v8util::ThrowTypeError(isolate, "Image must be called with new");
32
138
  return;
33
139
  }
34
- // 実体は通常オブジェクト。src セッターで decode をトリガする。
35
- args.This()->Set(isolate->GetCurrentContext(),
36
- Str(isolate, "complete"),
37
- v8::Boolean::New(isolate, false)).Check();
38
- args.GetReturnValue().Set(args.This());
140
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
141
+ v8::Local<v8::Object> self = args.This();
142
+
143
+ v8util::SetValue(isolate, self, "__isImageElement", v8::Boolean::New(isolate, true));
144
+ v8util::SetValue(isolate, self, "tagName", Str(isolate, "IMG"));
145
+ v8util::SetValue(isolate, self, "localName", Str(isolate, "img"));
146
+ v8util::SetValue(isolate, self, "complete", v8::Boolean::New(isolate, false));
147
+ v8util::SetValue(isolate, self, "width", v8::Integer::New(isolate, 0));
148
+ v8util::SetValue(isolate, self, "height", v8::Integer::New(isolate, 0));
149
+ v8util::SetValue(isolate, self, "naturalWidth", v8::Integer::New(isolate, 0));
150
+ v8util::SetValue(isolate, self, "naturalHeight", v8::Integer::New(isolate, 0));
151
+ InstallEventTarget(isolate, self); // addEventListener("load"/"error")
152
+
153
+ self->SetNativeDataProperty(ctx, Str(isolate, "src"),
154
+ [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
155
+ v8::Local<v8::Value> v;
156
+ if (info.This()->Get(info.GetIsolate()->GetCurrentContext(),
157
+ Str(info.GetIsolate(), "__src")).ToLocal(&v) && !v->IsUndefined()) {
158
+ info.GetReturnValue().Set(v);
159
+ } else {
160
+ info.GetReturnValue().Set(Str(info.GetIsolate(), ""));
161
+ }
162
+ },
163
+ [](v8::Local<v8::Name>, v8::Local<v8::Value> value,
164
+ const v8::PropertyCallbackInfo<void>& info) {
165
+ v8::Isolate* iso = info.GetIsolate();
166
+ v8util::SetValue(iso, info.This(), "__src", value);
167
+ LoadImageFromSrc(iso, info.This(), ToStdString(iso, value));
168
+ }).Check();
169
+
170
+ // decode(): 読み込み済みなら resolve、未読み込みでも resolve (簡易)
171
+ v8util::SetMethod(isolate, self, "decode",
172
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
173
+ v8::Isolate* iso = a.GetIsolate();
174
+ auto r = v8::Promise::Resolver::New(iso->GetCurrentContext()).ToLocalChecked();
175
+ r->Resolve(iso->GetCurrentContext(), v8::Undefined(iso)).Check();
176
+ a.GetReturnValue().Set(r->GetPromise());
177
+ });
178
+
179
+ args.GetReturnValue().Set(self);
39
180
  }
40
181
 
41
182
  // createImageBitmap(source) -> Promise<ImageBitmap>
@@ -71,16 +212,83 @@ void CreateImageBitmap(const v8::FunctionCallbackInfo<v8::Value>& args)
71
212
  if (bytes) {
72
213
  input = std::move(*bytes);
73
214
  }
215
+ } else if (args[0]->IsObject()) {
216
+ v8::Local<v8::Object> obj = args[0].As<v8::Object>();
217
+
218
+ // 1) ImageData ({ data: Uint8ClampedArray, width, height }):
219
+ // デコード不要でそのまま RGBA として取り込む
220
+ // (webgpu レンダラのキャプチャ/フィルタ→ImageBitmap 化の経路)。
221
+ v8::Local<v8::Value> data_v, w_v, h_v;
222
+ if (obj->Get(ctx, Str(isolate, "data")).ToLocal(&data_v) && data_v->IsArrayBufferView() &&
223
+ obj->Get(ctx, Str(isolate, "width")).ToLocal(&w_v) && w_v->IsNumber() &&
224
+ obj->Get(ctx, Str(isolate, "height")).ToLocal(&h_v) && h_v->IsNumber()) {
225
+ const auto w = static_cast<uint32_t>(w_v.As<v8::Number>()->Value());
226
+ const auto h = static_cast<uint32_t>(h_v.As<v8::Number>()->Value());
227
+ auto view = data_v.As<v8::ArrayBufferView>();
228
+ if (view->ByteLength() < static_cast<size_t>(w) * h * 4) {
229
+ resolver->Reject(ctx, v8::Exception::TypeError(
230
+ Str(isolate, "ImageData size mismatch"))).Check();
231
+ return;
232
+ }
233
+ auto* raw = new DecodedImage();
234
+ raw->width = w;
235
+ raw->height = h;
236
+ raw->rgba.resize(static_cast<size_t>(w) * h * 4);
237
+ view->CopyContents(raw->rgba.data(), raw->rgba.size());
238
+ resolver->Resolve(ctx, WrapImageBitmap(isolate, raw)).Check();
239
+ return;
240
+ }
241
+
242
+ // 2) ImageBitmap / 2D 済み canvas / video: ピクセルを複製して新規 ImageBitmap
243
+ const uint8_t* px = nullptr;
244
+ uint32_t sw = 0, sh = 0;
245
+ if (GetImageSourcePixels(isolate, obj, &px, &sw, &sh) && px) {
246
+ auto* raw = new DecodedImage();
247
+ raw->width = sw;
248
+ raw->height = sh;
249
+ raw->rgba.assign(px, px + static_cast<size_t>(sw) * sh * 4);
250
+ resolver->Resolve(ctx, WrapImageBitmap(isolate, raw)).Check();
251
+ return;
252
+ }
253
+
254
+ // 3) Blob: 内包バイト列 (エンコード済み画像) を WIC デコードへ回す
255
+ v8::Local<v8::Value> blob_v;
256
+ if (obj->Get(ctx, Str(isolate, "__blobData")).ToLocal(&blob_v) && blob_v->IsArrayBuffer()) {
257
+ auto store = blob_v.As<v8::ArrayBuffer>()->GetBackingStore();
258
+ input.assign(static_cast<uint8_t*>(store->Data()),
259
+ static_cast<uint8_t*>(store->Data()) + store->ByteLength());
260
+ }
74
261
  }
75
262
 
76
- auto* img = new DecodedImage();
77
- if (input.empty() || !DecodeImageWithWIC(input, *img)) {
78
- delete img;
263
+ if (input.empty()) {
79
264
  resolver->Reject(ctx, v8::Exception::Error(Str(isolate, "Image decode failed"))).Check();
80
265
  return;
81
266
  }
82
267
 
83
- resolver->Resolve(ctx, WrapImageBitmap(isolate, img)).Check();
268
+ // WIC デコードはバックグラウンドスレッドへ (Promise なので元々非同期契約)
269
+ auto resolver_ref =
270
+ std::make_shared<v8::Global<v8::Promise::Resolver>>(isolate, resolver);
271
+ auto decoded = std::make_shared<DecodedImage>();
272
+ auto ok = std::make_shared<bool>(false);
273
+
274
+ decodequeue::Submit(
275
+ [input = std::move(input), decoded, ok]() {
276
+ *ok = DecodeImage(input, *decoded);
277
+ },
278
+ [isolate, resolver_ref, decoded, ok]() {
279
+ v8::HandleScope hs(isolate);
280
+ v8::Local<v8::Promise::Resolver> r = resolver_ref->Get(isolate);
281
+ v8::Local<v8::Context> c = r->GetCreationContextChecked();
282
+ v8::Context::Scope cs(c);
283
+ if (*ok) {
284
+ auto* img = new DecodedImage(std::move(*decoded));
285
+ r->Resolve(c, WrapImageBitmap(isolate, img)).Check();
286
+ } else {
287
+ r->Reject(c, v8::Exception::Error(
288
+ Str(isolate, "Image decode failed"))).Check();
289
+ }
290
+ resolver_ref->Reset();
291
+ });
84
292
  }
85
293
 
86
294
  } // namespace
@@ -99,8 +307,7 @@ v8::Local<v8::Object> WrapImageBitmap(v8::Isolate* isolate, DecodedImage* image)
99
307
  v8util::SetValue(isolate, obj, "height", v8::Integer::NewFromUnsigned(isolate, image->height));
100
308
  v8util::SetMethod(isolate, obj, "close", [](const v8::FunctionCallbackInfo<v8::Value>&) {});
101
309
 
102
- auto* handle = new v8::Global<v8::Object>(isolate, obj);
103
- handle->SetWeak(image, ReleaseDecoded, v8::WeakCallbackType::kParameter);
310
+ v8util::AttachWeak(isolate, obj, image);
104
311
  return obj;
105
312
  }
106
313
 
@@ -126,6 +333,25 @@ bool GetImageSourcePixels(v8::Isolate* isolate, v8::Local<v8::Value> source,
126
333
  return true;
127
334
  }
128
335
 
336
+ // Image 要素 (new Image() + src): 読み込み完了後は内部フィールドに DecodedImage
337
+ v8::Local<v8::Value> is_image;
338
+ if (obj->Get(ctx, Str(isolate, "__isImageElement")).ToLocal(&is_image) &&
339
+ is_image->IsBoolean() && is_image.As<v8::Boolean>()->Value()) {
340
+ if (obj->InternalFieldCount() < 1) {
341
+ return false;
342
+ }
343
+ v8::Local<v8::Data> field = obj->GetInternalField(0);
344
+ if (!field->IsValue() || !field.As<v8::Value>()->IsExternal()) {
345
+ return false; // 未読み込み
346
+ }
347
+ auto* img = static_cast<DecodedImage*>(
348
+ field.As<v8::Value>().As<v8::External>()->Value());
349
+ *out_rgba = img->rgba.data();
350
+ *out_width = img->width;
351
+ *out_height = img->height;
352
+ return true;
353
+ }
354
+
129
355
  // video 要素: 現在フレームを返す
130
356
  v8::Local<v8::Value> is_video;
131
357
  if (obj->Get(ctx, Str(isolate, "__isVideoElement")).ToLocal(&is_video) &&
@@ -151,6 +377,8 @@ void InstallImage(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContex
151
377
  v8::Local<v8::FunctionTemplate> image_tmpl =
152
378
  v8::FunctionTemplate::New(isolate, ImageConstructor);
153
379
  image_tmpl->SetClassName(Str(isolate, "Image"));
380
+ // 内部フィールド0: デコード済み DecodedImage* (src セッターが設定)
381
+ image_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
154
382
  global->Set(ctx, Str(isolate, "Image"),
155
383
  image_tmpl->GetFunction(ctx).ToLocalChecked()).Check();
156
384