xbintsc 0.5.32 → 0.5.37
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 +10 -7
- package/README.zh-CN.md +10 -8
- package/dist/scripts/check-version.d.ts +27 -0
- package/dist/scripts/check-version.js +71 -0
- package/dist/scripts/check-version.js.map +1 -0
- package/dist/scripts/package-platform.d.ts +58 -0
- package/dist/scripts/package-platform.js +142 -0
- package/dist/scripts/package-platform.js.map +1 -0
- package/dist/scripts/verify-platform-package.d.ts +34 -0
- package/dist/scripts/verify-platform-package.js +161 -0
- package/dist/scripts/verify-platform-package.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/main.js +3 -1
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/driver/runtime-lib.d.ts +29 -4
- package/dist/src/driver/runtime-lib.js +68 -10
- package/dist/src/driver/runtime-lib.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/index.js +18 -5
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/media/index.js +4 -3
- package/dist/src/extensions/media/index.js.map +1 -1
- package/dist/src/parser/context.d.ts +41 -0
- package/dist/src/parser/context.js +89 -4
- package/dist/src/parser/context.js.map +1 -1
- package/dist/src/parser/literals.js +6 -12
- package/dist/src/parser/literals.js.map +1 -1
- package/dist/tests/driver/platform-package.test.d.ts +1 -0
- package/dist/tests/driver/platform-package.test.js +49 -0
- package/dist/tests/driver/platform-package.test.js.map +1 -0
- package/dist/tests/driver/runtime-lib.test.js +51 -4
- package/dist/tests/driver/runtime-lib.test.js.map +1 -1
- package/dist/tests/e2e/gui-audio.test.d.ts +14 -0
- package/dist/tests/e2e/gui-audio.test.js +287 -0
- package/dist/tests/e2e/gui-audio.test.js.map +1 -0
- package/dist/tests/e2e/gui-browser.test.js +65 -0
- package/dist/tests/e2e/gui-browser.test.js.map +1 -1
- package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
- package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
- package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
- package/dist/tests/e2e/gui-dom.test.js +215 -0
- package/dist/tests/e2e/gui-dom.test.js.map +1 -1
- package/dist/tests/e2e/gui-events.test.js +112 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -1
- package/dist/tests/e2e/gui-helpers.d.ts +7 -0
- package/dist/tests/e2e/gui-helpers.js +1 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.js +78 -0
- package/dist/tests/e2e/gui-media.test.js.map +1 -1
- package/dist/tests/e2e/gui-style.test.js +55 -0
- package/dist/tests/e2e/gui-style.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl.test.js +258 -0
- package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl2-3d.test.d.ts +4 -0
- package/dist/tests/e2e/gui-webgl2-3d.test.js +264 -0
- package/dist/tests/e2e/gui-webgl2-3d.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.d.ts +10 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.js +262 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.d.ts +6 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.js +395 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.d.ts +4 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.js +105 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.d.ts +8 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.js +112 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2.test.js +45 -1
- package/dist/tests/e2e/gui-webgl2.test.js.map +1 -1
- package/dist/tests/extensions/gui-index.test.js +1 -0
- package/dist/tests/extensions/gui-index.test.js.map +1 -1
- package/dist/tests/extensions/media.test.js +1 -0
- package/dist/tests/extensions/media.test.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +30 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/platform-tarball.test.d.ts +1 -0
- package/dist/tests/platform-tarball.test.js +111 -0
- package/dist/tests/platform-tarball.test.js.map +1 -0
- package/dist/tests/version.test.js +6 -0
- package/dist/tests/version.test.js.map +1 -1
- package/doc/ai/contributing.md +11 -0
- package/doc/ai/extensions.md +7 -4
- package/doc/ai/troubleshooting.md +7 -5
- package/doc/ai/zh-CN/contributing.md +9 -0
- package/doc/ai/zh-CN/extensions.md +4 -2
- package/doc/ai/zh-CN/troubleshooting.md +5 -3
- package/doc/gui/api.md +99 -22
- package/doc/gui/build.md +11 -4
- package/doc/gui/canvas.md +49 -18
- package/doc/gui/dom.md +57 -7
- package/doc/gui/media.md +4 -0
- package/doc/gui/webgl.md +105 -46
- package/doc/gui.md +7 -3
- package/doc/npm-package.md +55 -27
- package/doc/requirements.md +6 -2
- package/doc/zh-CN/gui/api.md +84 -14
- package/doc/zh-CN/gui/build.md +7 -2
- package/doc/zh-CN/gui/canvas.md +44 -19
- package/doc/zh-CN/gui/dom.md +48 -4
- package/doc/zh-CN/gui/media.md +3 -0
- package/doc/zh-CN/gui/webgl.md +22 -16
- package/doc/zh-CN/gui.md +3 -2
- package/doc/zh-CN/npm-package.md +44 -23
- package/doc/zh-CN/requirements.md +4 -1
- package/package.json +17 -1
- package/runtime/ext_gui/audio.cpp +365 -153
- package/runtime/ext_gui/audio_buffer.cpp +166 -0
- package/runtime/ext_gui/audio_buffer.h +37 -0
- package/runtime/ext_gui/audio_decode.cpp +219 -0
- package/runtime/ext_gui/audio_decode.h +39 -0
- package/runtime/ext_gui/audio_device.cpp +87 -0
- package/runtime/ext_gui/audio_effects.cpp +163 -0
- package/runtime/ext_gui/audio_effects.h +25 -0
- package/runtime/ext_gui/audio_internal.h +30 -0
- package/runtime/ext_gui/audio_mixer.cpp +494 -0
- package/runtime/ext_gui/audio_mixer.h +238 -0
- package/runtime/ext_gui/audio_param.cpp +104 -0
- package/runtime/ext_gui/audio_param_object.cpp +155 -0
- package/runtime/ext_gui/browser.cpp +45 -50
- package/runtime/ext_gui/browser_abort.cpp +249 -0
- package/runtime/ext_gui/browser_dialogs.cpp +76 -0
- package/runtime/ext_gui/browser_media.cpp +176 -0
- package/runtime/ext_gui/browser_navigation.cpp +130 -0
- package/runtime/ext_gui/canvas.cpp +88 -30
- package/runtime/ext_gui/canvas.h +74 -9
- package/runtime/ext_gui/canvas_image_data.cpp +143 -0
- package/runtime/ext_gui/canvas_internal.h +8 -5
- package/runtime/ext_gui/canvas_pattern.cpp +90 -0
- package/runtime/ext_gui/canvas_raster.cpp +31 -9
- package/runtime/ext_gui/canvas_style.cpp +4 -2
- package/runtime/ext_gui/css_selector.cpp +21 -3
- package/runtime/ext_gui/dom.h +4 -0
- package/runtime/ext_gui/dom_api.cpp +1 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
- package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
- package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
- package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
- package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
- package/runtime/ext_gui/dom_api_document.cpp +58 -5
- package/runtime/ext_gui/dom_api_events.cpp +93 -17
- package/runtime/ext_gui/dom_api_internal.h +71 -2
- package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
- package/runtime/ext_gui/dom_api_node.cpp +40 -19
- package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
- package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
- package/runtime/ext_gui/dom_api_proto.cpp +35 -0
- package/runtime/ext_gui/dom_api_style.cpp +223 -13
- package/runtime/ext_gui/dom_api_webgl.cpp +7 -0
- package/runtime/ext_gui/dom_api_webgl2.cpp +151 -36
- package/runtime/ext_gui/dom_api_webgl2_draw.cpp +3 -3
- package/runtime/ext_gui/dom_api_webgl_buffer.cpp +6 -2
- package/runtime/ext_gui/dom_api_webgl_compressed.cpp +167 -0
- package/runtime/ext_gui/dom_api_webgl_framebuffer.cpp +160 -0
- package/runtime/ext_gui/dom_api_webgl_shader.cpp +4 -4
- package/runtime/ext_gui/dom_api_webgl_source.cpp +161 -0
- package/runtime/ext_gui/dom_api_webgl_state.cpp +169 -23
- package/runtime/ext_gui/dom_api_webgl_texture.cpp +276 -236
- package/runtime/ext_gui/dom_api_webgl_ubo.cpp +81 -0
- package/runtime/ext_gui/gui.cpp +103 -14
- package/runtime/ext_gui/gui_engine.h +56 -4
- package/runtime/ext_gui/gui_events.cpp +80 -9
- package/runtime/ext_gui/image_bitmap.cpp +28 -0
- package/runtime/ext_gui/image_bitmap.h +26 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +136 -0
- package/runtime/ext_gui/listener_options.h +43 -0
- package/runtime/ext_gui/media.cpp +32 -0
- package/runtime/ext_gui/media.h +13 -0
- package/runtime/ext_gui/media_api.h +23 -0
- package/runtime/ext_gui/paint.cpp +28 -0
- package/runtime/ext_gui/paint.h +4 -0
- package/runtime/ext_gui/webgl.h +152 -18
- package/runtime/ext_gui/webgl_blit.cpp +95 -0
- package/runtime/ext_gui/webgl_buffer.cpp +178 -263
- package/runtime/ext_gui/webgl_buffer_objects.cpp +270 -0
- package/runtime/ext_gui/webgl_clear.cpp +142 -0
- package/runtime/ext_gui/webgl_compressed.cpp +180 -0
- package/runtime/ext_gui/webgl_context.cpp +241 -156
- package/runtime/ext_gui/webgl_draw.cpp +68 -3
- package/runtime/ext_gui/webgl_draw_buffers.cpp +75 -0
- package/runtime/ext_gui/webgl_glsl.h +40 -0
- package/runtime/ext_gui/webgl_glsl_compile.cpp +6 -0
- package/runtime/ext_gui/webgl_glsl_rewrite.cpp +18 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +20 -11
- package/runtime/ext_gui/webgl_glsl_scan_block.cpp +126 -0
- package/runtime/ext_gui/webgl_glsl_tokens.h +28 -0
- package/runtime/ext_gui/webgl_pipeline.cpp +71 -19
- package/runtime/ext_gui/webgl_program_blocks.cpp +58 -0
- package/runtime/ext_gui/webgl_program_internal.h +6 -0
- package/runtime/ext_gui/webgl_shader.cpp +13 -1
- package/runtime/ext_gui/webgl_state.cpp +228 -0
- package/runtime/ext_gui/webgl_texture.cpp +144 -17
- package/runtime/ext_gui/webgl_texture_3d.cpp +215 -0
- package/runtime/ext_gui/webgl_uniform_block.h +31 -0
- package/runtime/ext_gui/webgl_webgl2.cpp +58 -5
- package/runtime/ext_gui/window.cpp +14 -65
- package/runtime/ext_gui/window_events.cpp +125 -0
- package/runtime/ext_media/decode_audio.cpp +308 -0
- package/runtime/ext_media/media_bridge.c +2 -0
- package/runtime/rt.h +43 -0
- package/runtime/rt_internal.h +4 -0
- package/runtime/xt_array_buffer.c +30 -0
- package/runtime/xt_builtins.c +0 -21
- package/runtime/xt_containers/access.inc +94 -2
- package/runtime/xt_containers/objects.inc +29 -31
- package/runtime/xt_typed_array/elements.inc +13 -0
- package/scripts/build-media.ts +14 -6
- package/scripts/build-runtime.ts +21 -16
- package/scripts/check-version.ts +39 -9
- package/scripts/package-platform.ts +183 -0
- package/scripts/set-platform-version.ts +62 -0
- package/scripts/verify-platform-package.ts +182 -0
- package/src/cli/main.ts +5 -1
- package/src/driver/runtime-lib.ts +73 -9
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/index.ts +18 -5
- package/src/extensions/media/index.ts +4 -3
- package/src/parser/context.ts +99 -4
- package/src/parser/literals.ts +6 -12
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc media extension — in-memory audio decoding.
|
|
3
|
+
*
|
|
4
|
+
* `AudioContext.decodeAudioData` hands over bytes, not a path, so the file
|
|
5
|
+
* oriented `MediaPlayer` cannot be reused. This unit opens the bytes through a
|
|
6
|
+
* custom `AVIOContext`, decodes the best audio stream to interleaved float PCM
|
|
7
|
+
* at the stream's own sample rate, and leaves resampling to the mixer (which
|
|
8
|
+
* already resamples an AudioBuffer whose rate differs from 48 kHz).
|
|
9
|
+
*
|
|
10
|
+
* The ABI is C (`media_api.h`): `gui.a` stays FFmpeg-free and reaches this
|
|
11
|
+
* object through the table the media bridge installs. Both archives are built
|
|
12
|
+
* against the same static CRT, so the caller may `free()` the buffer through
|
|
13
|
+
* `xt_media_backend_free_audio`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include <cerrno>
|
|
17
|
+
#include <cstdio>
|
|
18
|
+
#include <cstdlib>
|
|
19
|
+
#include <cstring>
|
|
20
|
+
#include <vector>
|
|
21
|
+
|
|
22
|
+
extern "C" {
|
|
23
|
+
#include <libavcodec/avcodec.h>
|
|
24
|
+
#include <libavformat/avformat.h>
|
|
25
|
+
#include <libavutil/channel_layout.h>
|
|
26
|
+
#include <libswresample/swresample.h>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#include "../ext_gui/media_api.h"
|
|
30
|
+
|
|
31
|
+
namespace {
|
|
32
|
+
|
|
33
|
+
struct MemoryReader {
|
|
34
|
+
const unsigned char *data;
|
|
35
|
+
size_t size;
|
|
36
|
+
size_t position;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
int read_packet(void *opaque, uint8_t *buffer, int size) {
|
|
40
|
+
auto *reader = static_cast<MemoryReader *>(opaque);
|
|
41
|
+
if (reader->position >= reader->size) return AVERROR_EOF;
|
|
42
|
+
size_t available = reader->size - reader->position;
|
|
43
|
+
int take = size < static_cast<int>(available) ? size : static_cast<int>(available);
|
|
44
|
+
if (take <= 0) return AVERROR_EOF;
|
|
45
|
+
std::memcpy(buffer, reader->data + reader->position, static_cast<size_t>(take));
|
|
46
|
+
reader->position += static_cast<size_t>(take);
|
|
47
|
+
return take;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
int64_t seek_stream(void *opaque, int64_t offset, int whence) {
|
|
51
|
+
auto *reader = static_cast<MemoryReader *>(opaque);
|
|
52
|
+
if (whence == AVSEEK_SIZE) return static_cast<int64_t>(reader->size);
|
|
53
|
+
int64_t base;
|
|
54
|
+
if (whence == SEEK_SET) {
|
|
55
|
+
base = 0;
|
|
56
|
+
} else if (whence == SEEK_CUR) {
|
|
57
|
+
base = static_cast<int64_t>(reader->position);
|
|
58
|
+
} else if (whence == SEEK_END) {
|
|
59
|
+
base = static_cast<int64_t>(reader->size);
|
|
60
|
+
} else {
|
|
61
|
+
return AVERROR(EINVAL);
|
|
62
|
+
}
|
|
63
|
+
int64_t target = base + offset;
|
|
64
|
+
if (target < 0 || target > static_cast<int64_t>(reader->size)) return AVERROR(EINVAL);
|
|
65
|
+
reader->position = static_cast<size_t>(target);
|
|
66
|
+
return target;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
void set_error(char *error, size_t size, const char *message) {
|
|
70
|
+
if (error != nullptr && size > 0) std::snprintf(error, size, "%s", message);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void set_ffmpeg_error(char *error, size_t size, const char *prefix, int code) {
|
|
74
|
+
char detail[AV_ERROR_MAX_STRING_SIZE] = {0};
|
|
75
|
+
av_strerror(code, detail, sizeof(detail));
|
|
76
|
+
if (error != nullptr && size > 0) {
|
|
77
|
+
std::snprintf(error, size, "%s: %s", prefix, detail);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The decoder pipeline for one in-memory file. Destructor releases everything,
|
|
82
|
+
* which keeps the early-return paths in `decode` readable. */
|
|
83
|
+
struct Decoder {
|
|
84
|
+
MemoryReader reader{nullptr, 0, 0};
|
|
85
|
+
AVIOContext *io = nullptr;
|
|
86
|
+
AVFormatContext *format = nullptr;
|
|
87
|
+
AVCodecContext *codec = nullptr;
|
|
88
|
+
AVPacket *packet = nullptr;
|
|
89
|
+
AVFrame *frame = nullptr;
|
|
90
|
+
SwrContext *resampler = nullptr;
|
|
91
|
+
AVChannelLayout out_layout{};
|
|
92
|
+
int out_channels = 0;
|
|
93
|
+
int sample_rate = 0;
|
|
94
|
+
int stream_index = -1;
|
|
95
|
+
std::vector<float> samples;
|
|
96
|
+
bool failed = false;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
void close_decoder(Decoder *decoder) {
|
|
100
|
+
swr_free(&decoder->resampler);
|
|
101
|
+
av_channel_layout_uninit(&decoder->out_layout);
|
|
102
|
+
if (decoder->frame != nullptr) av_frame_free(&decoder->frame);
|
|
103
|
+
if (decoder->packet != nullptr) av_packet_free(&decoder->packet);
|
|
104
|
+
if (decoder->codec != nullptr) avcodec_free_context(&decoder->codec);
|
|
105
|
+
if (decoder->format != nullptr) avformat_close_input(&decoder->format);
|
|
106
|
+
if (decoder->io != nullptr) {
|
|
107
|
+
av_freep(&decoder->io->buffer);
|
|
108
|
+
avio_context_free(&decoder->io);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Lazily configure the resampler from the first decoded frame: some files only
|
|
113
|
+
* reveal their channel layout once decoding starts. */
|
|
114
|
+
bool prepare_resampler(Decoder *decoder, const AVFrame *frame, char *error, size_t error_size) {
|
|
115
|
+
if (decoder->resampler != nullptr) return true;
|
|
116
|
+
AVChannelLayout in_layout = frame->ch_layout;
|
|
117
|
+
if (in_layout.nb_channels <= 0) in_layout = decoder->codec->ch_layout;
|
|
118
|
+
int channels = in_layout.nb_channels > 0 ? in_layout.nb_channels : 1;
|
|
119
|
+
int rate = frame->sample_rate > 0 ? frame->sample_rate : decoder->codec->sample_rate;
|
|
120
|
+
if (rate <= 0) {
|
|
121
|
+
set_error(error, error_size, "audio stream has no usable sample rate");
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
decoder->out_channels = channels > 2 ? 2 : channels;
|
|
125
|
+
decoder->sample_rate = rate;
|
|
126
|
+
av_channel_layout_default(&decoder->out_layout, decoder->out_channels);
|
|
127
|
+
int result = swr_alloc_set_opts2(&decoder->resampler, &decoder->out_layout, AV_SAMPLE_FMT_FLT,
|
|
128
|
+
decoder->sample_rate, &in_layout,
|
|
129
|
+
static_cast<AVSampleFormat>(frame->format), rate, 0, nullptr);
|
|
130
|
+
if (result < 0 || decoder->resampler == nullptr ||
|
|
131
|
+
swr_init(decoder->resampler) < 0) {
|
|
132
|
+
set_error(error, error_size, "cannot set up audio resampling");
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Convert one decoded frame and append it to `decoder->samples`. */
|
|
139
|
+
bool append_frame(Decoder *decoder, const AVFrame *frame, char *error, size_t error_size) {
|
|
140
|
+
if (!prepare_resampler(decoder, frame, error, error_size)) return false;
|
|
141
|
+
int64_t delay = swr_get_delay(decoder->resampler, frame->sample_rate);
|
|
142
|
+
int capacity = static_cast<int>(av_rescale_rnd(delay + frame->nb_samples, decoder->sample_rate,
|
|
143
|
+
frame->sample_rate, AV_ROUND_UP));
|
|
144
|
+
if (capacity <= 0) return true;
|
|
145
|
+
std::vector<float> converted(static_cast<size_t>(capacity) * decoder->out_channels);
|
|
146
|
+
uint8_t *out_data[1] = {reinterpret_cast<uint8_t *>(converted.data())};
|
|
147
|
+
int got = swr_convert(decoder->resampler, out_data, capacity,
|
|
148
|
+
const_cast<const uint8_t **>(frame->extended_data), frame->nb_samples);
|
|
149
|
+
if (got < 0) {
|
|
150
|
+
set_ffmpeg_error(error, error_size, "cannot resample audio", got);
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
size_t count = static_cast<size_t>(got) * decoder->out_channels;
|
|
154
|
+
decoder->samples.insert(decoder->samples.end(), converted.begin(),
|
|
155
|
+
converted.begin() + static_cast<std::ptrdiff_t>(count));
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Drain the decoder after the last packet. */
|
|
160
|
+
bool flush_decoder(Decoder *decoder, char *error, size_t error_size) {
|
|
161
|
+
int result = avcodec_send_packet(decoder->codec, nullptr);
|
|
162
|
+
if (result < 0 && result != AVERROR_EOF) {
|
|
163
|
+
set_ffmpeg_error(error, error_size, "cannot flush audio decoder", result);
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
while (true) {
|
|
167
|
+
result = avcodec_receive_frame(decoder->codec, decoder->frame);
|
|
168
|
+
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) return true;
|
|
169
|
+
if (result < 0) {
|
|
170
|
+
set_ffmpeg_error(error, error_size, "cannot read audio frame", result);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (!append_frame(decoder, decoder->frame, error, error_size)) return false;
|
|
174
|
+
av_frame_unref(decoder->frame);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
bool decode_packets(Decoder *decoder, char *error, size_t error_size) {
|
|
179
|
+
while (true) {
|
|
180
|
+
int result = av_read_frame(decoder->format, decoder->packet);
|
|
181
|
+
if (result == AVERROR_EOF) break;
|
|
182
|
+
if (result < 0) {
|
|
183
|
+
set_ffmpeg_error(error, error_size, "cannot read audio packet", result);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (decoder->packet->stream_index != decoder->stream_index) {
|
|
187
|
+
av_packet_unref(decoder->packet);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
int sent = avcodec_send_packet(decoder->codec, decoder->packet);
|
|
191
|
+
av_packet_unref(decoder->packet);
|
|
192
|
+
if (sent < 0 && sent != AVERROR(EAGAIN)) {
|
|
193
|
+
set_ffmpeg_error(error, error_size, "cannot decode audio packet", sent);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
while (true) {
|
|
197
|
+
result = avcodec_receive_frame(decoder->codec, decoder->frame);
|
|
198
|
+
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) break;
|
|
199
|
+
if (result < 0) {
|
|
200
|
+
set_ffmpeg_error(error, error_size, "cannot decode audio frame", result);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (!append_frame(decoder, decoder->frame, error, error_size)) return false;
|
|
204
|
+
av_frame_unref(decoder->frame);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return flush_decoder(decoder, error, error_size);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
bool open_decoder(Decoder *decoder, int *stream_index, char *error, size_t error_size) {
|
|
211
|
+
decoder->io = avio_alloc_context(
|
|
212
|
+
static_cast<unsigned char *>(av_malloc(32768)), 32768, 0, &decoder->reader, read_packet,
|
|
213
|
+
nullptr, seek_stream);
|
|
214
|
+
if (decoder->io == nullptr) {
|
|
215
|
+
set_error(error, error_size, "cannot allocate the audio reader");
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
decoder->format = avformat_alloc_context();
|
|
219
|
+
if (decoder->format == nullptr) {
|
|
220
|
+
set_error(error, error_size, "cannot allocate the audio container");
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
decoder->format->pb = decoder->io;
|
|
224
|
+
decoder->format->flags |= AVFMT_FLAG_CUSTOM_IO;
|
|
225
|
+
int result = avformat_open_input(&decoder->format, nullptr, nullptr, nullptr);
|
|
226
|
+
if (result < 0) {
|
|
227
|
+
set_ffmpeg_error(error, error_size, "cannot open audio data", result);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
result = avformat_find_stream_info(decoder->format, nullptr);
|
|
231
|
+
if (result < 0) {
|
|
232
|
+
set_ffmpeg_error(error, error_size, "cannot read audio metadata", result);
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
*stream_index = av_find_best_stream(decoder->format, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
|
|
236
|
+
if (*stream_index < 0) {
|
|
237
|
+
set_error(error, error_size, "audio data has no audio stream");
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
decoder->stream_index = *stream_index;
|
|
241
|
+
AVCodecParameters *parameters = decoder->format->streams[*stream_index]->codecpar;
|
|
242
|
+
const AVCodec *codec = avcodec_find_decoder(parameters->codec_id);
|
|
243
|
+
if (codec == nullptr) {
|
|
244
|
+
set_error(error, error_size, "no decoder for this audio codec");
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
decoder->codec = avcodec_alloc_context3(codec);
|
|
248
|
+
if (decoder->codec == nullptr) {
|
|
249
|
+
set_error(error, error_size, "cannot allocate the audio decoder");
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
result = avcodec_parameters_to_context(decoder->codec, parameters);
|
|
253
|
+
if (result < 0) {
|
|
254
|
+
set_ffmpeg_error(error, error_size, "cannot configure the audio decoder", result);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
result = avcodec_open2(decoder->codec, codec, nullptr);
|
|
258
|
+
if (result < 0) {
|
|
259
|
+
set_ffmpeg_error(error, error_size, "cannot start the audio decoder", result);
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
decoder->packet = av_packet_alloc();
|
|
263
|
+
decoder->frame = av_frame_alloc();
|
|
264
|
+
if (decoder->packet == nullptr || decoder->frame == nullptr) {
|
|
265
|
+
set_error(error, error_size, "cannot allocate decoder buffers");
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
} // namespace
|
|
272
|
+
|
|
273
|
+
extern "C" int xt_media_backend_decode_audio(const unsigned char *data, size_t size,
|
|
274
|
+
XtMediaAudioBuffer *out, char *error,
|
|
275
|
+
size_t error_size) {
|
|
276
|
+
if (data == nullptr || size == 0 || out == nullptr) {
|
|
277
|
+
set_error(error, error_size, "empty audio data");
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
std::memset(out, 0, sizeof(*out));
|
|
281
|
+
Decoder decoder;
|
|
282
|
+
decoder.reader = {data, size, 0};
|
|
283
|
+
int stream_index = -1;
|
|
284
|
+
bool ok = open_decoder(&decoder, &stream_index, error, error_size) &&
|
|
285
|
+
decode_packets(&decoder, error, error_size);
|
|
286
|
+
if (!ok || decoder.out_channels <= 0 || decoder.samples.empty()) {
|
|
287
|
+
if (ok) set_error(error, error_size, "audio data has no samples");
|
|
288
|
+
close_decoder(&decoder);
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
int frames = static_cast<int>(decoder.samples.size() / decoder.out_channels);
|
|
292
|
+
size_t bytes = decoder.samples.size() * sizeof(float);
|
|
293
|
+
auto *samples = static_cast<float *>(std::malloc(bytes));
|
|
294
|
+
if (samples == nullptr) {
|
|
295
|
+
set_error(error, error_size, "cannot allocate the decoded audio buffer");
|
|
296
|
+
close_decoder(&decoder);
|
|
297
|
+
return 0;
|
|
298
|
+
}
|
|
299
|
+
std::memcpy(samples, decoder.samples.data(), bytes);
|
|
300
|
+
out->channels = decoder.out_channels;
|
|
301
|
+
out->sample_rate = decoder.sample_rate;
|
|
302
|
+
out->frames = frames;
|
|
303
|
+
out->samples = samples;
|
|
304
|
+
close_decoder(&decoder);
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
extern "C" void xt_media_backend_free_audio(void *samples) { std::free(samples); }
|
package/runtime/rt.h
CHANGED
|
@@ -185,6 +185,32 @@ xt_value xt_not(xt_value a);
|
|
|
185
185
|
/* -- objects -------------------------------------------------------------- */
|
|
186
186
|
xt_value xt_object_new(void);
|
|
187
187
|
xt_value xt_object_new_with_proto(xt_value proto);
|
|
188
|
+
|
|
189
|
+
/*
|
|
190
|
+
* Optional dynamic-property hooks.
|
|
191
|
+
*
|
|
192
|
+
* A host object that represents a *view* over external state — the GUI engine's
|
|
193
|
+
* `element.style`, `element.dataset` and `getComputedStyle()` — cannot know its
|
|
194
|
+
* property names when the prototype is built. Such an object installs these
|
|
195
|
+
* hooks; they are consulted only when ordinary property lookup/assignment finds
|
|
196
|
+
* nothing, so real properties and accessors keep precedence.
|
|
197
|
+
*
|
|
198
|
+
* `get` returns `XT_UNDEFINED` when it does not handle the key. `set`, `has`
|
|
199
|
+
* and `remove` return non-zero when they handled the operation; `keys` returns
|
|
200
|
+
* an array of the dynamically visible string keys (or `XT_UNDEFINED`).
|
|
201
|
+
* `data` is the host pointer handed to `xt_object_set_hooks`. Hooks must not
|
|
202
|
+
* store `xt_value`s outside a runtime object the collector can reach.
|
|
203
|
+
*/
|
|
204
|
+
typedef struct xt_object_hooks {
|
|
205
|
+
xt_value (*get)(xt_value self, xt_value key, void *data);
|
|
206
|
+
int (*set)(xt_value self, xt_value key, xt_value value, void *data);
|
|
207
|
+
int (*has)(xt_value self, xt_value key, void *data);
|
|
208
|
+
int (*remove)(xt_value self, xt_value key, void *data);
|
|
209
|
+
xt_value (*keys)(xt_value self, void *data);
|
|
210
|
+
} xt_object_hooks;
|
|
211
|
+
|
|
212
|
+
/** Install (or clear, with NULL) the dynamic-property hooks of `obj`. */
|
|
213
|
+
xt_value xt_object_set_hooks(xt_value obj, const xt_object_hooks *hooks, void *data);
|
|
188
214
|
/** The shared `Object.prototype` object (root of the ordinary object chain). */
|
|
189
215
|
xt_value xt_object_prototype(void);
|
|
190
216
|
/** The shared `Function.prototype` object. */
|
|
@@ -250,6 +276,23 @@ xt_value xt_string_static(xt_value name, int32_t argc, xt_value *argv);
|
|
|
250
276
|
xt_value xt_typed_array_ctor_by_name(xt_value name, int32_t argc, xt_value *argv);
|
|
251
277
|
xt_value xt_typed_array_static_by_name(xt_value name, xt_value method, int32_t argc, xt_value *argv);
|
|
252
278
|
xt_value xt_typed_array_prototype(xt_value name);
|
|
279
|
+
/** Bulk access to the bytes an `ArrayBuffer` (or a typed array's backing
|
|
280
|
+
* store) holds, for decoders and encoders that cannot afford one property read
|
|
281
|
+
* per byte. `offset` and `count` are clamped to the buffer and the number of
|
|
282
|
+
* bytes actually transferred is returned. Extensions pass the backing buffer
|
|
283
|
+
* plus a view's `byteOffset`/`byteLength`; see `runtime/ext_gui/audio.cpp`. */
|
|
284
|
+
uint32_t xt_array_buffer_copy_bytes_out(xt_value buffer, uint32_t offset, uint8_t *dst,
|
|
285
|
+
uint32_t count);
|
|
286
|
+
uint32_t xt_array_buffer_copy_bytes_in(xt_value buffer, uint32_t offset, const uint8_t *src,
|
|
287
|
+
uint32_t count);
|
|
288
|
+
/** Allocate a zero-filled `ArrayBuffer` of `byte_length` bytes (the native
|
|
289
|
+
* counterpart of `new ArrayBuffer(n)`). */
|
|
290
|
+
xt_value xt_array_buffer_alloc(uint32_t byte_length);
|
|
291
|
+
/** Typed-array view internals for extensions: whether `value` is a view, the
|
|
292
|
+
* `ArrayBuffer` it views, and the byte offset of the view inside it. */
|
|
293
|
+
int xt_is_typed_array(xt_value value);
|
|
294
|
+
xt_value xt_typed_array_backing_buffer(xt_value value);
|
|
295
|
+
uint32_t xt_typed_array_view_offset(xt_value value);
|
|
253
296
|
/** Error-family construction and prototype lookup by constructor name. */
|
|
254
297
|
xt_value xt_error_ctor_by_name(xt_value name, int32_t argc, xt_value *argv);
|
|
255
298
|
xt_value xt_error_prototype(xt_value name);
|
package/runtime/rt_internal.h
CHANGED
|
@@ -141,6 +141,10 @@ typedef struct {
|
|
|
141
141
|
* Deliberately not a property: `Object.keys`, `for...in` and JSON.stringify
|
|
142
142
|
* must not see it, while `error.stack` returns it through `xt_get`. */
|
|
143
143
|
xt_value error_stack;
|
|
144
|
+
/* Optional dynamic-property view (host objects such as `element.style`); see
|
|
145
|
+
* `xt_object_hooks` in `rt.h`. NULL for ordinary objects. */
|
|
146
|
+
const xt_object_hooks *hooks;
|
|
147
|
+
void *hook_data;
|
|
144
148
|
} xt_object;
|
|
145
149
|
|
|
146
150
|
typedef struct {
|
|
@@ -60,6 +60,8 @@ xt_value xt_array_buffer_ctor(int32_t argc, xt_value *argv) {
|
|
|
60
60
|
return buffer;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
xt_value xt_array_buffer_alloc(uint32_t byte_length) { return xt_ab_new(byte_length); }
|
|
64
|
+
|
|
63
65
|
int xt_is_array_buffer(xt_value value) { return xt_ab_is_buffer(value); }
|
|
64
66
|
|
|
65
67
|
uint32_t xt_array_buffer_byte_length(xt_value value) {
|
|
@@ -79,6 +81,34 @@ void xt_array_buffer_set_byte(xt_value buffer, uint32_t index, uint8_t value) {
|
|
|
79
81
|
xt_set(data, xt_number((double)index), xt_number((double)value));
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
/* Bulk byte access. A decoder (WAV, image, …) would otherwise pay one hidden
|
|
85
|
+
* property read per byte, which is far too slow for a multi-megabyte file. The
|
|
86
|
+
* backing store is an `xt_array` of numbers; reading its `items` directly is
|
|
87
|
+
* the same representation `xt_ab_new` writes. */
|
|
88
|
+
uint32_t xt_array_buffer_copy_bytes_out(xt_value buffer, uint32_t offset, uint8_t *dst,
|
|
89
|
+
uint32_t count) {
|
|
90
|
+
xt_array *data = xt_as_array(xt_ab_data(buffer));
|
|
91
|
+
if (data == NULL || dst == NULL) return 0;
|
|
92
|
+
if (offset >= data->length) return 0;
|
|
93
|
+
if (count > data->length - offset) count = data->length - offset;
|
|
94
|
+
for (uint32_t i = 0; i < count; i++) {
|
|
95
|
+
dst[i] = (uint8_t)(int32_t)xt_to_number(data->items[offset + i]);
|
|
96
|
+
}
|
|
97
|
+
return count;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
uint32_t xt_array_buffer_copy_bytes_in(xt_value buffer, uint32_t offset, const uint8_t *src,
|
|
101
|
+
uint32_t count) {
|
|
102
|
+
xt_array *data = xt_as_array(xt_ab_data(buffer));
|
|
103
|
+
if (data == NULL || src == NULL) return 0;
|
|
104
|
+
if (offset >= data->length) return 0;
|
|
105
|
+
if (count > data->length - offset) count = data->length - offset;
|
|
106
|
+
for (uint32_t i = 0; i < count; i++) {
|
|
107
|
+
data->items[offset + i] = xt_number((double)src[i]);
|
|
108
|
+
}
|
|
109
|
+
return count;
|
|
110
|
+
}
|
|
111
|
+
|
|
82
112
|
static xt_value xt_ab_slice(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
83
113
|
(void)env;
|
|
84
114
|
xt_value data = xt_ab_data(thisValue);
|
package/runtime/xt_builtins.c
CHANGED
|
@@ -406,27 +406,6 @@ xt_value xt_in(xt_value key, xt_value value) {
|
|
|
406
406
|
return XT_FALSE;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
-
xt_value xt_delete(xt_value value, xt_value key) {
|
|
410
|
-
if (XT_IS_OBJECT(value)) {
|
|
411
|
-
xt_object *obj = (xt_object *)XT_GET_PTR(value);
|
|
412
|
-
xt_value k = xt_to_property_key(key);
|
|
413
|
-
for (uint32_t i = 0; i < obj->count; i++) {
|
|
414
|
-
if (xt_property_key_equals(obj->properties[i].key, k)) {
|
|
415
|
-
for (uint32_t j = i + 1; j < obj->count; j++) obj->properties[j - 1] = obj->properties[j];
|
|
416
|
-
obj->count--;
|
|
417
|
-
xt_object_shape_changed(obj);
|
|
418
|
-
break;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
return XT_TRUE;
|
|
422
|
-
}
|
|
423
|
-
if (XT_IS_ARRAY(value)) {
|
|
424
|
-
if (!xt_is_symbol(key)) xt_array_set(value, key, XT_UNDEFINED);
|
|
425
|
-
return XT_TRUE;
|
|
426
|
-
}
|
|
427
|
-
return XT_TRUE;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
409
|
xt_value xt_rest_args(int32_t argc, xt_value *argv, int32_t start) {
|
|
431
410
|
if (start < 0) start = 0;
|
|
432
411
|
int32_t count = argc - start;
|
|
@@ -105,8 +105,7 @@ static xt_value xt_array_iterator_method(void) {
|
|
|
105
105
|
return xt_array_iterator_method_value;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
xt_value xt_get(xt_value target, xt_value key) {
|
|
109
|
-
if (target == XT_UNDEFINED || target == XT_NULL) xt_throw_read_of_nullish(target, key);
|
|
108
|
+
xt_value xt_get(xt_value target, xt_value key) { if (target == XT_UNDEFINED || target == XT_NULL) xt_throw_read_of_nullish(target, key);
|
|
110
109
|
if (XT_IS_ARRAY(target)) {
|
|
111
110
|
/* `arr.length` must read the array's length, never an element. */
|
|
112
111
|
if (XT_IS_STRING(key)) {
|
|
@@ -289,6 +288,8 @@ xt_value xt_get_ic(xt_value target, xt_value key, int64_t *cache) {
|
|
|
289
288
|
if (obj->header.kind != XT_OBJECT_KIND_OBJECT && obj->header.kind != XT_OBJECT_KIND_ERROR) {
|
|
290
289
|
return xt_get(target, key);
|
|
291
290
|
}
|
|
291
|
+
/* A host view answers keys dynamically, outside the shape cache. */
|
|
292
|
+
if (obj->hooks != NULL) return xt_get(target, key);
|
|
292
293
|
if (!XT_IS_STRING(key) && !xt_is_symbol(key)) return xt_get(target, key);
|
|
293
294
|
xt_shape *shape = obj->shape;
|
|
294
295
|
uintptr_t cachedShape = (uintptr_t)cache[0];
|
|
@@ -387,3 +388,94 @@ xt_value xt_set(xt_value target, xt_value key, xt_value value) {
|
|
|
387
388
|
}
|
|
388
389
|
return value;
|
|
389
390
|
}
|
|
391
|
+
|
|
392
|
+
/* ------------------------------------------------------------------------- */
|
|
393
|
+
/* Dynamic host-view properties */
|
|
394
|
+
/* ------------------------------------------------------------------------- */
|
|
395
|
+
|
|
396
|
+
xt_value xt_object_set_hooks(xt_value value, const xt_object_hooks *hooks, void *data) {
|
|
397
|
+
xt_object *obj = xt_as_object(value);
|
|
398
|
+
if (!obj) return value;
|
|
399
|
+
if (obj->header.kind != XT_OBJECT_KIND_OBJECT && obj->header.kind != XT_OBJECT_KIND_ERROR) {
|
|
400
|
+
return value;
|
|
401
|
+
}
|
|
402
|
+
obj->hooks = hooks;
|
|
403
|
+
obj->hook_data = data;
|
|
404
|
+
return value;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
void xt_object_append_dynamic_keys(xt_object *obj, xt_value value, xt_value result) {
|
|
408
|
+
if (obj->hooks == NULL || obj->hooks->keys == NULL) return;
|
|
409
|
+
xt_value dynamic = obj->hooks->keys(value, obj->hook_data);
|
|
410
|
+
xt_array *list = xt_as_array(dynamic);
|
|
411
|
+
if (list == NULL) return;
|
|
412
|
+
for (uint32_t i = 0; i < list->length; i++) {
|
|
413
|
+
xt_value key = list->items[i];
|
|
414
|
+
if (!XT_IS_STRING(key)) continue;
|
|
415
|
+
if (xt_object_find(obj, key) != NULL) continue;
|
|
416
|
+
xt_array_push(result, key);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/* `delete obj[key]`: remove a real property (arrays clear the element) and let
|
|
420
|
+
* a host view delete its dynamic key. */
|
|
421
|
+
xt_value xt_delete(xt_value value, xt_value key) {
|
|
422
|
+
if (XT_IS_OBJECT(value)) {
|
|
423
|
+
xt_object *obj = (xt_object *)XT_GET_PTR(value);
|
|
424
|
+
xt_value k = xt_to_property_key(key);
|
|
425
|
+
for (uint32_t i = 0; i < obj->count; i++) {
|
|
426
|
+
if (xt_property_key_equals(obj->properties[i].key, k)) {
|
|
427
|
+
for (uint32_t j = i + 1; j < obj->count; j++) obj->properties[j - 1] = obj->properties[j];
|
|
428
|
+
obj->count--;
|
|
429
|
+
xt_object_shape_changed(obj);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/* A host view (`element.style`, `element.dataset`) owns no real property for
|
|
434
|
+
* its keys, so `delete` must reach it even when the bag stayed empty. */
|
|
435
|
+
if (obj->hooks != NULL && obj->hooks->remove != NULL) {
|
|
436
|
+
obj->hooks->remove(value, k, obj->hook_data);
|
|
437
|
+
}
|
|
438
|
+
return XT_TRUE;
|
|
439
|
+
}
|
|
440
|
+
if (XT_IS_ARRAY(value)) {
|
|
441
|
+
if (!xt_is_symbol(key)) xt_array_set(value, key, XT_UNDEFINED);
|
|
442
|
+
return XT_TRUE;
|
|
443
|
+
}
|
|
444
|
+
return XT_TRUE;
|
|
445
|
+
}
|
|
446
|
+
xt_value xt_object_has_own(xt_value value, xt_value key) {
|
|
447
|
+
if (XT_IS_ARRAY(value)) {
|
|
448
|
+
xt_array *array = (xt_array *)XT_GET_PTR(value);
|
|
449
|
+
xt_value propertyKey = xt_to_property_key(key);
|
|
450
|
+
if (XT_IS_STRING(propertyKey)) {
|
|
451
|
+
xt_string *k = xt_as_string(propertyKey);
|
|
452
|
+
if (k->length == 6 && memcmp(xt_string_bytes(k), "length", 6) == 0) return XT_TRUE;
|
|
453
|
+
uint32_t index = 0;
|
|
454
|
+
if (xt_key_is_array_index(k, &index)) return xt_bool(index < array->length);
|
|
455
|
+
}
|
|
456
|
+
return xt_bool(XT_IS_OBJECT(array->extra) &&
|
|
457
|
+
xt_object_find((xt_object *)XT_GET_PTR(array->extra), propertyKey));
|
|
458
|
+
}
|
|
459
|
+
if (XT_IS_STRING(value)) {
|
|
460
|
+
xt_string *string = xt_as_string(value);
|
|
461
|
+
if (xt_is_symbol(key)) return XT_FALSE;
|
|
462
|
+
xt_string *k = xt_as_string(xt_to_string(key));
|
|
463
|
+
if (k->length == 6 && memcmp(xt_string_bytes(k), "length", 6) == 0) return XT_TRUE;
|
|
464
|
+
uint32_t index = 0;
|
|
465
|
+
return xt_bool(xt_key_is_array_index(k, &index) && index < xt_string_utf16_length(string));
|
|
466
|
+
}
|
|
467
|
+
xt_object *obj = xt_as_object(value);
|
|
468
|
+
if (!obj) return XT_FALSE;
|
|
469
|
+
/* Map/Set/Date/RegExp/Promise/Symbol use their own struct layout and have no
|
|
470
|
+
* property bag; interpreting them as `xt_object` would read past the end. */
|
|
471
|
+
if (obj->header.kind != XT_OBJECT_KIND_OBJECT && obj->header.kind != XT_OBJECT_KIND_ERROR) {
|
|
472
|
+
return XT_FALSE;
|
|
473
|
+
}
|
|
474
|
+
xt_value propertyKey = xt_to_property_key(key);
|
|
475
|
+
if (xt_object_find(obj, propertyKey) != NULL) return XT_TRUE;
|
|
476
|
+
if (obj->hooks != NULL && obj->hooks->has != NULL &&
|
|
477
|
+
obj->hooks->has(value, propertyKey, obj->hook_data)) {
|
|
478
|
+
return XT_TRUE;
|
|
479
|
+
}
|
|
480
|
+
return XT_FALSE;
|
|
481
|
+
}
|
|
@@ -170,6 +170,9 @@ xt_value xt_object_new_with_proto(xt_value proto) {
|
|
|
170
170
|
obj->prototype = proto;
|
|
171
171
|
obj->frozen = 0;
|
|
172
172
|
obj->is_array_base = 0;
|
|
173
|
+
obj->error_stack = XT_UNDEFINED;
|
|
174
|
+
obj->hooks = NULL;
|
|
175
|
+
obj->hook_data = NULL;
|
|
173
176
|
return XT_FROM_PTR(XT_TAG_OBJECT, obj);
|
|
174
177
|
}
|
|
175
178
|
|
|
@@ -191,6 +194,10 @@ static xt_property *xt_object_find(xt_object *obj, xt_value key) {
|
|
|
191
194
|
return NULL;
|
|
192
195
|
}
|
|
193
196
|
|
|
197
|
+
/* Defined in `access.inc`: append the dynamic keys of a host view whose
|
|
198
|
+
* `xt_object_hooks::keys` returns them. */
|
|
199
|
+
static void xt_object_append_dynamic_keys(xt_object *obj, xt_value value, xt_value result);
|
|
200
|
+
|
|
194
201
|
/*
|
|
195
202
|
* JavaScript enumerates own string keys in a canonical order: array-index
|
|
196
203
|
* keys (the canonical decimal form of an integer in [0, 2^32 - 2]) first, in
|
|
@@ -313,6 +320,13 @@ xt_value xt_object_get(xt_value value, xt_value key) {
|
|
|
313
320
|
if (!XT_IS_OBJECT(cur->prototype)) break;
|
|
314
321
|
cur = (xt_object *)XT_GET_PTR(cur->prototype);
|
|
315
322
|
}
|
|
323
|
+
/* A host view (`element.style`, `element.dataset`, computed style) answers the
|
|
324
|
+
* keys it owns dynamically. Only a miss reaches this point, so prototype
|
|
325
|
+
* methods and real properties win. */
|
|
326
|
+
if (obj->hooks != NULL && obj->hooks->get != NULL) {
|
|
327
|
+
xt_value dynamic = obj->hooks->get(value, k, obj->hook_data);
|
|
328
|
+
if (!XT_IS_UNDEFINED(dynamic)) return dynamic;
|
|
329
|
+
}
|
|
316
330
|
return XT_UNDEFINED;
|
|
317
331
|
}
|
|
318
332
|
|
|
@@ -368,6 +382,11 @@ xt_value xt_object_set(xt_value value, xt_value key, xt_value newValue) {
|
|
|
368
382
|
}
|
|
369
383
|
ancestor = xt_object_prototype_of(ancestor->prototype, ancestor);
|
|
370
384
|
}
|
|
385
|
+
/* A host view handles the assignment itself (e.g. `style.width = "50%"` or
|
|
386
|
+
* `dataset.foo = "1"`); it never stores a shadowing data property. */
|
|
387
|
+
if (obj->hooks != NULL && obj->hooks->set != NULL) {
|
|
388
|
+
if (obj->hooks->set(value, k, newValue, obj->hook_data)) return newValue;
|
|
389
|
+
}
|
|
371
390
|
xt_property *inserted = xt_object_insert_property(obj, k);
|
|
372
391
|
inserted->value = newValue;
|
|
373
392
|
return newValue;
|
|
@@ -446,36 +465,6 @@ int xt_object_is_frozen(xt_value value) {
|
|
|
446
465
|
return obj ? obj->frozen : 0;
|
|
447
466
|
}
|
|
448
467
|
|
|
449
|
-
xt_value xt_object_has_own(xt_value value, xt_value key) {
|
|
450
|
-
if (XT_IS_ARRAY(value)) {
|
|
451
|
-
xt_array *array = (xt_array *)XT_GET_PTR(value);
|
|
452
|
-
xt_value propertyKey = xt_to_property_key(key);
|
|
453
|
-
if (XT_IS_STRING(propertyKey)) {
|
|
454
|
-
xt_string *k = xt_as_string(propertyKey);
|
|
455
|
-
if (k->length == 6 && memcmp(xt_string_bytes(k), "length", 6) == 0) return XT_TRUE;
|
|
456
|
-
uint32_t index = 0;
|
|
457
|
-
if (xt_key_is_array_index(k, &index)) return xt_bool(index < array->length);
|
|
458
|
-
}
|
|
459
|
-
return xt_bool(XT_IS_OBJECT(array->extra) &&
|
|
460
|
-
xt_object_find((xt_object *)XT_GET_PTR(array->extra), propertyKey));
|
|
461
|
-
}
|
|
462
|
-
if (XT_IS_STRING(value)) {
|
|
463
|
-
xt_string *string = xt_as_string(value);
|
|
464
|
-
if (xt_is_symbol(key)) return XT_FALSE;
|
|
465
|
-
xt_string *k = xt_as_string(xt_to_string(key));
|
|
466
|
-
if (k->length == 6 && memcmp(xt_string_bytes(k), "length", 6) == 0) return XT_TRUE;
|
|
467
|
-
uint32_t index = 0;
|
|
468
|
-
return xt_bool(xt_key_is_array_index(k, &index) && index < xt_string_utf16_length(string));
|
|
469
|
-
}
|
|
470
|
-
xt_object *obj = xt_as_object(value);
|
|
471
|
-
if (!obj) return XT_FALSE;
|
|
472
|
-
/* Map/Set/Date/RegExp/Promise/Symbol use their own struct layout and have no
|
|
473
|
-
* property bag; interpreting them as `xt_object` would read past the end. */
|
|
474
|
-
if (obj->header.kind != XT_OBJECT_KIND_OBJECT && obj->header.kind != XT_OBJECT_KIND_ERROR) {
|
|
475
|
-
return XT_FALSE;
|
|
476
|
-
}
|
|
477
|
-
return xt_bool(xt_object_find(obj, xt_to_property_key(key)) != NULL);
|
|
478
|
-
}
|
|
479
468
|
|
|
480
469
|
xt_value xt_object_from_entries(xt_value entries) {
|
|
481
470
|
xt_value result = xt_object_new();
|
|
@@ -495,7 +484,13 @@ xt_value xt_object_has(xt_value value, xt_value key) {
|
|
|
495
484
|
if (obj->header.kind != XT_OBJECT_KIND_OBJECT && obj->header.kind != XT_OBJECT_KIND_ERROR) {
|
|
496
485
|
return XT_FALSE;
|
|
497
486
|
}
|
|
498
|
-
|
|
487
|
+
xt_value propertyKey = xt_to_property_key(key);
|
|
488
|
+
if (xt_object_find(obj, propertyKey) != NULL) return XT_TRUE;
|
|
489
|
+
if (obj->hooks != NULL && obj->hooks->has != NULL &&
|
|
490
|
+
obj->hooks->has(value, propertyKey, obj->hook_data)) {
|
|
491
|
+
return XT_TRUE;
|
|
492
|
+
}
|
|
493
|
+
return XT_FALSE;
|
|
499
494
|
}
|
|
500
495
|
|
|
501
496
|
/* `Object.getOwnPropertySymbols`: the symbol-keyed own properties, in
|
|
@@ -531,6 +526,9 @@ xt_value xt_object_keys(xt_value value) {
|
|
|
531
526
|
for (uint32_t i = 0; i < obj->count; i++) {
|
|
532
527
|
if (XT_IS_STRING(obj->properties[i].key)) xt_array_push(result, obj->properties[i].key);
|
|
533
528
|
}
|
|
529
|
+
/* Dynamic keys of a host view (`element.dataset`, ...), appended without
|
|
530
|
+
* repeating a real property that was just pushed. */
|
|
531
|
+
xt_object_append_dynamic_keys(obj, value, result);
|
|
534
532
|
return result;
|
|
535
533
|
}
|
|
536
534
|
if (XT_IS_ARRAY(value)) {
|