@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
@@ -4,9 +4,12 @@
4
4
  #include "AssetLoader.h"
5
5
  #include "EventTarget.h"
6
6
  #include "platform/AudioEngine.h"
7
+ #include "platform/DecodeQueue.h"
7
8
  #include "v8/V8Util.h"
9
+ #include "v8/WeakHandle.h"
8
10
 
9
11
  #include <memory>
12
+ #include <utility>
10
13
  #include <vector>
11
14
 
12
15
  namespace next2d {
@@ -22,11 +25,6 @@ struct GainParam {
22
25
  std::shared_ptr<AudioVoice> voice; // source.connect(gain) 時にリンクされる
23
26
  };
24
27
 
25
- void ReleaseGainParam(const v8::WeakCallbackInfo<GainParam>& info)
26
- {
27
- delete info.GetParameter();
28
- }
29
-
30
28
  // 再生中(非ループ)の source ノード。毎フレーム IsFinished を見て "ended" を発火する。
31
29
  std::vector<v8::Global<v8::Object>>& PlayingSources()
32
30
  {
@@ -40,19 +38,12 @@ struct Holder {
40
38
  std::shared_ptr<T> ptr;
41
39
  };
42
40
 
43
- template <typename T>
44
- void ReleaseHolder(const v8::WeakCallbackInfo<Holder<T>>& info)
45
- {
46
- delete info.GetParameter();
47
- }
48
-
49
41
  template <typename T>
50
42
  void Attach(v8::Isolate* isolate, v8::Local<v8::Object> obj, std::shared_ptr<T> ptr)
51
43
  {
52
44
  auto* holder = new Holder<T>{std::move(ptr)};
53
45
  obj->SetInternalField(0, v8::External::New(isolate, holder));
54
- auto* handle = new v8::Global<v8::Object>(isolate, obj);
55
- handle->SetWeak(holder, ReleaseHolder<T>, v8::WeakCallbackType::kParameter);
46
+ v8util::AttachWeak(isolate, obj, holder);
56
47
  }
57
48
 
58
49
  template <typename T>
@@ -198,8 +189,7 @@ void CreateGain(const v8::FunctionCallbackInfo<v8::Value>& args)
198
189
  v8::Local<v8::Object> gain = InternalTemplate(isolate)->NewInstance(ctx).ToLocalChecked();
199
190
  auto* param = new GainParam();
200
191
  gain->SetInternalField(0, v8::External::New(isolate, param));
201
- auto* handle = new v8::Global<v8::Object>(isolate, gain);
202
- handle->SetWeak(param, ReleaseGainParam, v8::WeakCallbackType::kParameter);
192
+ v8util::AttachWeak(isolate, gain, param);
203
193
 
204
194
  gain->SetNativeDataProperty(ctx, Str(isolate, "value"),
205
195
  [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
@@ -226,26 +216,53 @@ void CreateGain(const v8::FunctionCallbackInfo<v8::Value>& args)
226
216
  // 毎フレーム呼ばれ、再生終了した source ノードへ "ended" を発火する (main.cpp のループから)。
227
217
  } // namespace
228
218
 
219
+ // V8 破棄前に呼ぶ。再生追跡リストの v8::Global を解放する。
220
+ // (放置すると static デストラクタが V8 破棄後に走りアクセス違反になる)
221
+ void ShutdownAudioEvents()
222
+ {
223
+ for (auto& g : PlayingSources()) {
224
+ g.Reset();
225
+ }
226
+ PlayingSources().clear();
227
+ }
228
+
229
229
  void PumpAudioEvents(v8::Isolate* isolate)
230
230
  {
231
231
  auto& sources = PlayingSources();
232
232
  if (sources.empty()) return;
233
233
  v8::HandleScope scope(isolate);
234
234
  v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
235
+
236
+ // 1) 終了した source を集めて先に登録リストから外す。ここでは JS を呼ばない。
237
+ // (この時点の erase による v8::Global の move は他の再入が無いので安全)
238
+ std::vector<v8::Global<v8::Object>> finished;
235
239
  for (size_t i = 0; i < sources.size();) {
236
240
  v8::Local<v8::Object> node = sources[i].Get(isolate);
237
241
  auto voice = Get<AudioVoice>(node);
238
242
  if (!voice || voice->IsFinished()) {
239
- if (voice) {
240
- v8::Local<v8::Object> ev = v8::Object::New(isolate);
241
- ev->Set(ctx, Str(isolate, "type"), Str(isolate, "ended")).Check();
242
- DispatchEvent(isolate, node, ev);
243
- }
243
+ finished.push_back(std::move(sources[i]));
244
244
  sources.erase(sources.begin() + i);
245
245
  } else {
246
246
  ++i;
247
247
  }
248
248
  }
249
+
250
+ // 2) リストが安定した状態で "ended" を配送する。
251
+ // "ended" ハンドラは source.disconnect() や新規再生で PlayingSources を
252
+ // 再入的に変更しうる。以前は配送と erase を同一ループで行っていたため、
253
+ // 配送中にベクタが変更されると直後の erase 中の v8::Global move が壊れ
254
+ // GlobalHandles::MoveGlobal でアクセス違反していた。独立した finished を
255
+ // 走査することで再入変更から隔離する。
256
+ for (auto& g : finished) {
257
+ v8::Local<v8::Object> node = g.Get(isolate);
258
+ auto voice = Get<AudioVoice>(node);
259
+ if (voice) {
260
+ v8::Local<v8::Object> ev = v8::Object::New(isolate);
261
+ ev->Set(ctx, Str(isolate, "type"), Str(isolate, "ended")).Check();
262
+ DispatchEvent(isolate, node, ev);
263
+ }
264
+ g.Reset();
265
+ }
249
266
  }
250
267
 
251
268
  namespace {
@@ -268,21 +285,158 @@ void DecodeAudioData(const v8::FunctionCallbackInfo<v8::Value>& args)
268
285
  static_cast<uint8_t*>(store->Data()) + store->ByteLength());
269
286
  }
270
287
 
288
+ // Media Foundation デコードはバックグラウンドスレッドへ (Promise なので元々
289
+ // 非同期契約)。BGM の同期デコードは画面遷移時の数百 ms ヒッチになっていた。
290
+ auto resolver_ref =
291
+ std::make_shared<v8::Global<v8::Promise::Resolver>>(isolate, resolver);
271
292
  auto pcm = std::make_shared<PcmBuffer>();
272
- if (input.empty() || !AudioEngine::Decode(input, *pcm)) {
273
- resolver->Reject(ctx, v8::Exception::Error(Str(isolate, "audio decode failed"))).Check();
293
+ auto decoded = std::make_shared<bool>(false);
294
+
295
+ decodequeue::Submit(
296
+ [input = std::move(input), pcm, decoded]() {
297
+ *decoded = !input.empty() && AudioEngine::Decode(input, *pcm);
298
+ },
299
+ [isolate, resolver_ref, pcm, decoded]() {
300
+ v8::HandleScope hs(isolate);
301
+ v8::Local<v8::Promise::Resolver> r = resolver_ref->Get(isolate);
302
+ v8::Local<v8::Context> c = r->GetCreationContextChecked();
303
+ v8::Context::Scope cs(c);
304
+
305
+ // 重要: デコード失敗でも Reject しない。@next2d/media の SoundDecodeService は
306
+ // decodeAudioData の reject を catch して先頭バイトをスキップし再帰リトライするが、
307
+ // そのループ条件 (`idx > byteLength`) が常に false のため同一バッファで無限再帰し、
308
+ // マイクロタスクが枯渇してフレームワークのローディング画面ごとフリーズする。
309
+ // ここで無音の空バッファを resolve すれば、そのサウンドは無音になるだけでアプリは
310
+ // 進行できる (graceful degradation)。実データが壊れているか MF 非対応かは上のログで判別。
311
+ if (!*decoded) {
312
+ pcm->samples.clear(); // 0 サンプル = 無音
313
+ pcm->channels = pcm->channels ? pcm->channels : 2;
314
+ pcm->sample_rate = pcm->sample_rate ? pcm->sample_rate : 48000;
315
+ }
316
+
317
+ v8::Local<v8::Object> buffer =
318
+ InternalTemplate(isolate)->NewInstance(c).ToLocalChecked();
319
+ Attach<PcmBuffer>(isolate, buffer, pcm);
320
+ v8util::SetValue(isolate, buffer, "sampleRate",
321
+ v8::Number::New(isolate, pcm->sample_rate));
322
+ v8util::SetValue(isolate, buffer, "numberOfChannels",
323
+ v8::Integer::New(isolate, pcm->channels));
324
+ v8util::SetValue(isolate, buffer, "duration",
325
+ v8::Number::New(isolate, pcm->channels
326
+ ? static_cast<double>(pcm->samples.size()) / pcm->channels / pcm->sample_rate
327
+ : 0.0));
328
+ r->Resolve(c, buffer).Check();
329
+ resolver_ref->Reset();
330
+ });
331
+ }
332
+
333
+ // --- Audio (HTMLAudioElement 最小実装) -------------------------------------
334
+ // ゲームコードが `new Audio(url)` で効果音/BGM を再生する経路。
335
+ // play/pause/paused/loop/volume/currentTime/preload をサポートする。
336
+ // pause→play は先頭からの再生になる (位置レジュームは未対応の近似)。
337
+
338
+ void AudioElementPlay(const v8::FunctionCallbackInfo<v8::Value>& args)
339
+ {
340
+ v8::Isolate* isolate = args.GetIsolate();
341
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
342
+ v8::Local<v8::Object> self = args.This();
343
+
344
+ auto resolver = v8::Promise::Resolver::New(ctx).ToLocalChecked();
345
+ args.GetReturnValue().Set(resolver->GetPromise());
346
+
347
+ auto voice = Get<AudioVoice>(self);
348
+ if (voice) {
349
+ bool loop = false;
350
+ double volume = 1.0;
351
+ v8::Local<v8::Value> v;
352
+ if (self->Get(ctx, Str(isolate, "loop")).ToLocal(&v)) {
353
+ loop = v->BooleanValue(isolate);
354
+ }
355
+ if (self->Get(ctx, Str(isolate, "volume")).ToLocal(&v) && v->IsNumber()) {
356
+ volume = v.As<v8::Number>()->Value();
357
+ }
358
+ voice->SetVolume(static_cast<float>(volume));
359
+ voice->Start(loop);
360
+ }
361
+ v8util::SetValue(isolate, self, "paused", v8::Boolean::New(isolate, false));
362
+ resolver->Resolve(ctx, v8::Undefined(isolate)).Check();
363
+ }
364
+
365
+ void AudioElementPause(const v8::FunctionCallbackInfo<v8::Value>& args)
366
+ {
367
+ v8::Isolate* isolate = args.GetIsolate();
368
+ auto voice = Get<AudioVoice>(args.This());
369
+ if (voice) {
370
+ voice->Stop();
371
+ }
372
+ v8util::SetValue(isolate, args.This(), "paused", v8::Boolean::New(isolate, true));
373
+ }
374
+
375
+ void AudioElementConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
376
+ {
377
+ v8::Isolate* isolate = args.GetIsolate();
378
+ if (!args.IsConstructCall()) {
379
+ v8util::ThrowTypeError(isolate, "Audio must be called with new");
274
380
  return;
275
381
  }
382
+ v8::Local<v8::Object> self = args.This();
383
+ Attach<AudioVoice>(isolate, self, nullptr);
384
+
385
+ // URL 指定があればロードし、デコードはバックグラウンドスレッドへ (失敗時は
386
+ // 無音の no-op)。デコード完了前に play() が呼ばれていた場合 (paused=false) は
387
+ // 完了時に再生を開始する (同期デコード時代の「即時再生」挙動を維持)。
388
+ if (args.Length() > 0 && args[0]->IsString()) {
389
+ const std::string src = ToStdString(isolate, args[0]);
390
+ v8util::SetValue(isolate, self, "src", args[0]);
391
+ HostContext* host = HostContext::From(isolate);
392
+ auto bytes = host->assets->ReadBinary(src);
393
+ if (bytes) {
394
+ auto self_ref = std::make_shared<v8::Global<v8::Object>>(isolate, self);
395
+ auto pcm = std::make_shared<PcmBuffer>();
396
+ auto decoded = std::make_shared<bool>(false);
397
+ decodequeue::Submit(
398
+ [input = std::move(*bytes), pcm, decoded]() {
399
+ *decoded = AudioEngine::Decode(input, *pcm);
400
+ },
401
+ [isolate, self_ref, pcm, decoded, host]() {
402
+ v8::HandleScope hs(isolate);
403
+ v8::Local<v8::Object> el = self_ref->Get(isolate);
404
+ v8::Local<v8::Context> c = el->GetCreationContextChecked();
405
+ v8::Context::Scope cs(c);
406
+ if (*decoded) {
407
+ auto voice = host->audio->CreateVoice(pcm);
408
+ Attach<AudioVoice>(isolate, el, voice);
409
+ // デコード中に play() 済みなら開始する
410
+ v8::Local<v8::Value> paused_v, loop_v, vol_v;
411
+ const bool paused =
412
+ !el->Get(c, Str(isolate, "paused")).ToLocal(&paused_v) ||
413
+ paused_v->BooleanValue(isolate);
414
+ if (!paused && voice) {
415
+ if (el->Get(c, Str(isolate, "volume")).ToLocal(&vol_v) &&
416
+ vol_v->IsNumber()) {
417
+ voice->SetVolume(static_cast<float>(
418
+ vol_v.As<v8::Number>()->Value()));
419
+ }
420
+ const bool loop =
421
+ el->Get(c, Str(isolate, "loop")).ToLocal(&loop_v) &&
422
+ loop_v->BooleanValue(isolate);
423
+ voice->Start(loop);
424
+ }
425
+ }
426
+ self_ref->Reset();
427
+ });
428
+ }
429
+ }
276
430
 
277
- v8::Local<v8::Object> buffer = InternalTemplate(isolate)->NewInstance(ctx).ToLocalChecked();
278
- Attach<PcmBuffer>(isolate, buffer, pcm);
279
- v8util::SetValue(isolate, buffer, "sampleRate", v8::Number::New(isolate, pcm->sample_rate));
280
- v8util::SetValue(isolate, buffer, "numberOfChannels", v8::Integer::New(isolate, pcm->channels));
281
- v8util::SetValue(isolate, buffer, "duration",
282
- v8::Number::New(isolate, pcm->channels
283
- ? static_cast<double>(pcm->samples.size()) / pcm->channels / pcm->sample_rate
284
- : 0.0));
285
- resolver->Resolve(ctx, buffer).Check();
431
+ v8util::SetValue(isolate, self, "loop", v8::Boolean::New(isolate, false));
432
+ v8util::SetValue(isolate, self, "volume", v8::Number::New(isolate, 1.0));
433
+ v8util::SetValue(isolate, self, "paused", v8::Boolean::New(isolate, true));
434
+ v8util::SetValue(isolate, self, "preload", Str(isolate, "auto"));
435
+ v8util::SetValue(isolate, self, "currentTime", v8::Number::New(isolate, 0));
436
+ v8util::SetMethod(isolate, self, "play", AudioElementPlay);
437
+ v8util::SetMethod(isolate, self, "pause", AudioElementPause);
438
+ InstallEventTarget(isolate, self);
439
+ args.GetReturnValue().Set(self);
286
440
  }
287
441
 
288
442
  // AudioContext コンストラクタ
@@ -299,6 +453,23 @@ void AudioContextConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
299
453
  v8util::SetMethod(isolate, self, "createBufferSource", CreateBufferSource);
300
454
  v8util::SetMethod(isolate, self, "createGain", CreateGain);
301
455
  v8util::SetMethod(isolate, self, "decodeAudioData", DecodeAudioData);
456
+
457
+ // state / resume / suspend / close: ブラウザの AudioContext はユーザージェスチャまで
458
+ // suspended だが、ホストの XAudio2 は常に有効なので "running" 固定。player の音声
459
+ // アンロック (初回 pointerup で ctx.resume()) が未実装だと TypeError で throw し、
460
+ // そのハンドラ(ミュート解除等)以降が中断する。解決済み Promise を返す no-op にする。
461
+ v8util::SetValue(isolate, self, "state", v8util::Str(isolate, "running"));
462
+ auto resolvedPromise = [](const v8::FunctionCallbackInfo<v8::Value>& a) {
463
+ v8::Isolate* iso = a.GetIsolate();
464
+ v8::Local<v8::Context> c = iso->GetCurrentContext();
465
+ auto r = v8::Promise::Resolver::New(c).ToLocalChecked();
466
+ (void) r->Resolve(c, v8::Undefined(iso));
467
+ a.GetReturnValue().Set(r->GetPromise());
468
+ };
469
+ v8util::SetMethod(isolate, self, "resume", resolvedPromise);
470
+ v8util::SetMethod(isolate, self, "suspend", resolvedPromise);
471
+ v8util::SetMethod(isolate, self, "close", resolvedPromise);
472
+
302
473
  args.GetReturnValue().Set(self);
303
474
  }
304
475
 
@@ -315,6 +486,15 @@ void InstallAudio(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContex
315
486
 
316
487
  global->Set(ctx, Str(isolate, "AudioContext"), fn).Check();
317
488
  global->Set(ctx, Str(isolate, "webkitAudioContext"), fn).Check();
489
+
490
+ // Audio (HTMLAudioElement 最小): ゲームコードの `new Audio(url)` 用。
491
+ // インスタンスは内部フィールドに AudioVoice ホルダを持つ。
492
+ v8::Local<v8::FunctionTemplate> audio_tmpl =
493
+ v8::FunctionTemplate::New(isolate, AudioElementConstructor);
494
+ audio_tmpl->SetClassName(Str(isolate, "Audio"));
495
+ audio_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
496
+ global->Set(ctx, Str(isolate, "Audio"),
497
+ audio_tmpl->GetFunction(ctx).ToLocalChecked()).Check();
318
498
  }
319
499
 
320
500
  } // namespace next2d
@@ -41,6 +41,12 @@ void InstallAudio(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContex
41
41
  // 再生終了した AudioBufferSourceNode へ "ended" を発火する。main.cpp のループから毎フレーム呼ぶ。
42
42
  void PumpAudioEvents(v8::Isolate* isolate);
43
43
 
44
+ // V8 破棄前に呼ぶ。音声の再生追跡リスト (v8::Global) を解放する。
45
+ void ShutdownAudioEvents();
46
+
47
+ // V8 破棄前に呼ぶ。WebGPU テンプレートの v8::Global (static) を解放する。
48
+ void ShutdownWebGPU();
49
+
44
50
  // window / document / navigator / screen 等の DOM 相当スタブと canvas 生成
45
51
  void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
46
52
 
@@ -69,6 +75,10 @@ bool GetVideoFramePixels(v8::Isolate* isolate, v8::Local<v8::Object> obj,
69
75
  // blob: URL のバイト列をテキストとして取得する (Network.cpp)。
70
76
  bool ResolveObjectURL(const std::string& url, std::string* out_text);
71
77
 
78
+ // TextEncoder/TextDecoder/queueMicrotask 等の JS ポリフィルを評価する (Polyfills.cpp)。
79
+ // bootstrap.js はメインのみのため、worker を含む全コンテキストにはこちらで注入する。
80
+ void InstallPolyfills(v8::Isolate* isolate, v8::Local<v8::Context> context);
81
+
72
82
  // 上記をまとめてインストールする
73
83
  void InstallGlobalBindings(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
74
84
 
@@ -105,6 +105,16 @@ void TransferControlToOffscreen(const v8::FunctionCallbackInfo<v8::Value>& args)
105
105
  if (args.This()->Get(ctx, Str(isolate, "height")).ToLocal(&hv) && hv->IsNumber()) {
106
106
  height = static_cast<int>(hv.As<v8::Number>()->Value());
107
107
  }
108
+
109
+ // このソース canvas (args.This()) こそがプレイヤーの主表示 canvas。
110
+ // player は「document.createElement('canvas') → pointer リスナ登録 → その canvas を
111
+ // transferControlToOffscreen()」の順で主 canvas を作る (input と描画で同一要素)。
112
+ // 一方 createElement は毎回 main_canvas を上書きするため、後続のヒットテスト用
113
+ // Canvas2D 生成 (getContext('2d') 用に 7 個も作る) で main_canvas が別要素に化け、
114
+ // WndProc の入力配送先とリスナ登録先がズレて「ボタンが全く反応しない」状態になる。
115
+ // transferControlToOffscreen が呼ばれる唯一の canvas = 正しい入力要素なので確定させる。
116
+ host->main_canvas.Reset(isolate, args.This());
117
+
108
118
  args.GetReturnValue().Set(CreateOffscreenCanvas(isolate, host, width, height));
109
119
  }
110
120
 
@@ -135,6 +145,9 @@ v8::Local<v8::Object> CreateCanvasElement(v8::Isolate* isolate, HostContext* hos
135
145
  int width, int height)
136
146
  {
137
147
  v8::Local<v8::Object> canvas = v8::Object::New(isolate);
148
+ // player の canvasSetPositionService は children[0].localName === "canvas" を確認する
149
+ SetValue(isolate, canvas, "tagName", Str(isolate, "CANVAS"));
150
+ SetValue(isolate, canvas, "localName", Str(isolate, "canvas"));
138
151
  SetValue(isolate, canvas, "width",
139
152
  v8::Integer::New(isolate, width ? width : host->viewport_width));
140
153
  SetValue(isolate, canvas, "height",
@@ -144,10 +157,72 @@ v8::Local<v8::Object> CreateCanvasElement(v8::Isolate* isolate, HostContext* hos
144
157
  SetValue(isolate, canvas, "style", v8::Object::New(isolate));
145
158
  SetMethod(isolate, canvas, "getContext", Canvas_GetContext);
146
159
  SetMethod(isolate, canvas, "transferControlToOffscreen", TransferControlToOffscreen);
160
+ // Pointer capture: player の pointerdown ハンドラは t.setPointerCapture(pointerId) を
161
+ // 呼ぶ。未実装だと TypeError で DispatchEvent の TryCatch に飲まれ、以降の座標解決
162
+ // (dl) に到達せずボタンが反応しない。no-op として実装する (単一ウィンドウでは
163
+ // capture の有無で挙動は変わらない)。hasPointerCapture は false を返す。
164
+ SetMethod(isolate, canvas, "setPointerCapture",
165
+ [](const v8::FunctionCallbackInfo<v8::Value>&) {});
166
+ SetMethod(isolate, canvas, "releasePointerCapture",
167
+ [](const v8::FunctionCallbackInfo<v8::Value>&) {});
168
+ SetMethod(isolate, canvas, "hasPointerCapture",
169
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
170
+ a.GetReturnValue().Set(false);
171
+ });
147
172
  InstallEventTarget(isolate, canvas); // pointer/wheel リスナを保持
148
173
  SetMethod(isolate, canvas, "getBoundingClientRect", GetBoundingClientRect);
149
- // 主要 canvas を HostContext から参照できるよう記録 (WndProc の入力配送先)
150
- HostContext::From(isolate)->main_canvas.Reset(isolate, canvas);
174
+
175
+ // player の CanvasInitializeService が canvas.setAttribute("style", ...) を呼ぶ。
176
+ // 属性は __attrs に保存する (見た目の CSS はネイティブ描画では不要)。
177
+ SetMethod(isolate, canvas, "setAttribute",
178
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
179
+ if (a.Length() < 2) return;
180
+ v8::Isolate* iso = a.GetIsolate();
181
+ v8::Local<v8::Context> c = iso->GetCurrentContext();
182
+ v8::Local<v8::Value> attrs;
183
+ v8::Local<v8::Object> store;
184
+ if (a.This()->Get(c, v8util::Str(iso, "__attrs")).ToLocal(&attrs) && attrs->IsObject()) {
185
+ store = attrs.As<v8::Object>();
186
+ } else {
187
+ store = v8::Object::New(iso);
188
+ v8util::SetValue(iso, a.This(), "__attrs", store);
189
+ }
190
+ store->Set(c, a[0], a[1]).Check();
191
+ });
192
+ SetMethod(isolate, canvas, "getAttribute",
193
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
194
+ a.GetReturnValue().SetNull();
195
+ if (a.Length() < 1) return;
196
+ v8::Isolate* iso = a.GetIsolate();
197
+ v8::Local<v8::Context> c = iso->GetCurrentContext();
198
+ v8::Local<v8::Value> attrs;
199
+ if (a.This()->Get(c, v8util::Str(iso, "__attrs")).ToLocal(&attrs) && attrs->IsObject()) {
200
+ v8::Local<v8::Value> v;
201
+ if (attrs.As<v8::Object>()->Get(c, a[0]).ToLocal(&v) && !v->IsUndefined()) {
202
+ a.GetReturnValue().Set(v);
203
+ }
204
+ }
205
+ });
206
+ SetMethod(isolate, canvas, "removeAttribute",
207
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
208
+ if (a.Length() < 1) return;
209
+ v8::Isolate* iso = a.GetIsolate();
210
+ v8::Local<v8::Context> c = iso->GetCurrentContext();
211
+ v8::Local<v8::Value> attrs;
212
+ if (a.This()->Get(c, v8util::Str(iso, "__attrs")).ToLocal(&attrs) && attrs->IsObject()) {
213
+ (void) attrs.As<v8::Object>()->Delete(c, a[0]);
214
+ }
215
+ });
216
+ // 主要 canvas の記録 (WndProc の入力配送先)。
217
+ // 確定は transferControlToOffscreen 側で行う (player が input を張る主 canvas はそちら)。
218
+ // ここでは「まだ未設定なら暫定で最初の canvas を採用」するフォールバックに留め、
219
+ // 後続の Canvas2D 生成で上書きしない (上書きすると入力配送先がヒットテスト用 canvas に化ける)。
220
+ {
221
+ HostContext* host = HostContext::From(isolate);
222
+ if (host->main_canvas.IsEmpty()) {
223
+ host->main_canvas.Reset(isolate, canvas);
224
+ }
225
+ }
151
226
  return canvas;
152
227
  }
153
228