xbintsc 0.5.37 → 0.5.38
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/dist/src/cli/help.d.ts +1 -1
- package/dist/src/codegen/generator/state.js +1 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.js +1 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/deferred.d.ts +63 -0
- package/dist/src/driver/bundler/deferred.js +279 -0
- package/dist/src/driver/bundler/deferred.js.map +1 -0
- package/dist/src/driver/bundler/merge.js +214 -2
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/runtime-objects.d.ts +1 -1
- package/dist/src/driver/runtime-objects.js +1 -0
- package/dist/src/driver/runtime-objects.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/html.js +89 -13
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/gui/index.js +1 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/gui/three.d.ts +4 -3
- package/dist/src/extensions/gui/three.js +4 -3
- package/dist/src/extensions/gui/three.js.map +1 -1
- package/dist/tests/e2e/gui-script-import-order.test.d.ts +10 -0
- package/dist/tests/e2e/gui-script-import-order.test.js +161 -0
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.d.ts +10 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
- package/dist/tests/e2e/url.test.d.ts +6 -0
- package/dist/tests/e2e/url.test.js +114 -0
- package/dist/tests/e2e/url.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +22 -10
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/doc/gui/webgl.md +20 -10
- package/doc/gui-scripts.md +19 -19
- package/doc/gui.md +10 -8
- package/doc/implemented/fetch.md +5 -0
- package/doc/implemented/modules.md +13 -0
- package/doc/implemented/stdlib.md +17 -0
- package/doc/unimplemented/deviations.md +1 -0
- package/doc/zh-CN/gui/webgl.md +3 -3
- package/doc/zh-CN/gui-scripts.md +14 -13
- package/doc/zh-CN/gui.md +7 -6
- package/doc/zh-CN/implemented/fetch.md +4 -0
- package/doc/zh-CN/implemented/modules.md +10 -0
- package/doc/zh-CN/implemented/stdlib.md +15 -0
- package/doc/zh-CN/unimplemented/deviations.md +1 -0
- package/package.json +7 -7
- package/runtime/ext_gui/dom_api_webgl.cpp +10 -0
- package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
- package/runtime/ext_gui/gui_engine.h +2 -0
- package/runtime/ext_gui/script.cpp +31 -0
- package/runtime/ext_gui/webgl_glsl.h +3 -0
- package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
- package/runtime/ext_gui/webgl_msl.cpp +4 -0
- package/runtime/ext_gui/webgl_shader.cpp +12 -6
- package/runtime/rt.h +3 -0
- package/runtime/xt_fetch/async.inc +60 -0
- package/runtime/xt_url_object.c +463 -0
- package/src/codegen/generator/state.ts +1 -0
- package/src/codegen/generator/tables.ts +1 -0
- package/src/driver/bundler/deferred.ts +351 -0
- package/src/driver/bundler/merge.ts +219 -2
- package/src/driver/runtime-objects.ts +1 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/html.ts +91 -15
- package/src/extensions/gui/index.ts +1 -0
- package/src/extensions/gui/three.ts +4 -3
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc runtime — URL.
|
|
3
|
+
*
|
|
4
|
+
* Relative-reference resolution against a base URL, plus the read-only
|
|
5
|
+
* components a compiled page or CLI tool typically reads. The WHATWG URL
|
|
6
|
+
* constructor is `URL`; `URLSearchParams` lives in xt_url.c.
|
|
7
|
+
*
|
|
8
|
+
* A reference with no scheme is a filesystem path: it resolves against a
|
|
9
|
+
* `file://` base (the working directory when the caller supplied no base), so
|
|
10
|
+
* `new URL("./model.glb", import.meta.url)` and `new URL("./model.glb")`
|
|
11
|
+
* both produce a usable file URL. `fetch` uses the same rule for relative
|
|
12
|
+
* requests (see `xt_url_file_path`), which is how a compiled page can load a
|
|
13
|
+
* local asset without going through an HTTP server.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include "rt_internal.h"
|
|
17
|
+
|
|
18
|
+
#include <ctype.h>
|
|
19
|
+
#include <stdio.h>
|
|
20
|
+
#include <stdlib.h>
|
|
21
|
+
#include <string.h>
|
|
22
|
+
|
|
23
|
+
#if defined(_WIN32)
|
|
24
|
+
#include <direct.h>
|
|
25
|
+
#define xt_url_getcwd _getcwd
|
|
26
|
+
#else
|
|
27
|
+
#include <unistd.h>
|
|
28
|
+
#define xt_url_getcwd getcwd
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
/* URLSearchParams is defined in xt_url.c. */
|
|
32
|
+
extern xt_value xt_url_search_params_ctor(int32_t argc, xt_value *argv);
|
|
33
|
+
|
|
34
|
+
/* -- URL ------------------------------------------------------------------ */
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* A deliberately small WHATWG-ish `URL`: resolution of relative references
|
|
38
|
+
* against a base (the shape a browser page and `import.meta.url` produce),
|
|
39
|
+
* plus the read-only components the games and tools we compile actually use.
|
|
40
|
+
* It is not a full URL parser: percent-encoding of the path, IDNA hosts and
|
|
41
|
+
* the `URL` setters are out of scope.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/** Longest URL this runtime will build. Longer inputs are truncated. */
|
|
45
|
+
#define XT_URL_MAX 8192
|
|
46
|
+
|
|
47
|
+
/** Length of a leading `scheme:` prefix, or 0 when `text` has no scheme. */
|
|
48
|
+
static size_t xt_url_scheme_length(const char *text) {
|
|
49
|
+
if (!isalpha((unsigned char)text[0])) return 0;
|
|
50
|
+
size_t i = 1;
|
|
51
|
+
for (; text[i]; i++) {
|
|
52
|
+
if (text[i] == ':') {
|
|
53
|
+
#if defined(_WIN32)
|
|
54
|
+
/* A drive letter (`C:\dir` / `C:/dir`) is a path, not a scheme. */
|
|
55
|
+
if (i == 1 && (text[2] == '\\' || text[2] == '/')) return 0;
|
|
56
|
+
#endif
|
|
57
|
+
return i;
|
|
58
|
+
}
|
|
59
|
+
unsigned char c = (unsigned char)text[i];
|
|
60
|
+
if (!(isalnum(c) || c == '+' || c == '-' || c == '.')) return 0;
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Join `scheme://authority` (already written) with an absolute path. */
|
|
66
|
+
static void xt_url_append(char *out, size_t size, const char *text) {
|
|
67
|
+
size_t used = strlen(out);
|
|
68
|
+
size_t length = strlen(text);
|
|
69
|
+
if (used + length >= size) length = used < size ? size - used - 1 : 0;
|
|
70
|
+
memcpy(out + used, text, length);
|
|
71
|
+
out[used + length] = 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Normalize `.` and `..` segments in `path` into `out`. */
|
|
75
|
+
static void xt_url_normalize_path(const char *path, char *out, size_t size) {
|
|
76
|
+
int absolute = path[0] == '/';
|
|
77
|
+
size_t length = strlen(path);
|
|
78
|
+
int trailing = length > 0 && path[length - 1] == '/';
|
|
79
|
+
size_t write = 0;
|
|
80
|
+
if (absolute && write + 1 < size) out[write++] = '/';
|
|
81
|
+
const char *cursor = path;
|
|
82
|
+
while (*cursor == '/') cursor++;
|
|
83
|
+
while (*cursor) {
|
|
84
|
+
const char *start = cursor;
|
|
85
|
+
while (*cursor && *cursor != '/') cursor++;
|
|
86
|
+
size_t segment = (size_t)(cursor - start);
|
|
87
|
+
if (segment == 1 && start[0] == '.') {
|
|
88
|
+
/* `./` adds nothing. */
|
|
89
|
+
} else if (segment == 2 && start[0] == '.' && start[1] == '.') {
|
|
90
|
+
size_t floor = absolute ? 1 : 0;
|
|
91
|
+
if (write > floor) {
|
|
92
|
+
while (write > floor && out[write - 1] != '/') write--;
|
|
93
|
+
if (write > floor && out[write - 1] == '/') write--;
|
|
94
|
+
}
|
|
95
|
+
} else if (segment > 0) {
|
|
96
|
+
if (write > 0 && out[write - 1] != '/' && write + 1 < size) out[write++] = '/';
|
|
97
|
+
size_t copy = segment;
|
|
98
|
+
if (write + copy >= size) copy = write < size ? size - write - 1 : 0;
|
|
99
|
+
memcpy(out + write, start, copy);
|
|
100
|
+
write += copy;
|
|
101
|
+
}
|
|
102
|
+
while (*cursor == '/') cursor++;
|
|
103
|
+
}
|
|
104
|
+
if (trailing && write > 0 && out[write - 1] != '/' && write + 1 < size) out[write++] = '/';
|
|
105
|
+
if (write == 0 && size > 1) out[write++] = '/';
|
|
106
|
+
out[write < size ? write : size - 1] = 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The `file://` URL of the working directory, with a trailing slash. */
|
|
110
|
+
static void xt_url_cwd_base(char *out, size_t size) {
|
|
111
|
+
char cwd[4096];
|
|
112
|
+
cwd[0] = 0;
|
|
113
|
+
if (xt_url_getcwd(cwd, sizeof(cwd)) == NULL) cwd[0] = 0;
|
|
114
|
+
size_t length = strlen(cwd);
|
|
115
|
+
while (length > 1 && (cwd[length - 1] == '/' || cwd[length - 1] == '\\')) cwd[--length] = 0;
|
|
116
|
+
/* Windows reports `C:\dir`; a file URL spells that `/C:/dir`. */
|
|
117
|
+
char path[4300];
|
|
118
|
+
size_t j = 0;
|
|
119
|
+
#if defined(_WIN32)
|
|
120
|
+
if (length > 0 && cwd[0] != '/') path[j++] = '/';
|
|
121
|
+
#endif
|
|
122
|
+
for (size_t i = 0; i < length && j + 1 < sizeof(path); i++) {
|
|
123
|
+
path[j++] = cwd[i] == '\\' ? '/' : cwd[i];
|
|
124
|
+
}
|
|
125
|
+
path[j] = 0;
|
|
126
|
+
if (j == 0 || path[j - 1] != '/') {
|
|
127
|
+
if (j + 1 < sizeof(path)) {
|
|
128
|
+
path[j++] = '/';
|
|
129
|
+
path[j] = 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
snprintf(out, size, "file://%s", path);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Build `href` for `input` resolved against `base` (either may be empty). */
|
|
136
|
+
static void xt_url_resolve(const char *input, const char *base, char *out, size_t size) {
|
|
137
|
+
out[0] = 0;
|
|
138
|
+
if (input[0] == 0) {
|
|
139
|
+
snprintf(out, size, "%s", base);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (xt_url_scheme_length(input) > 0) {
|
|
143
|
+
snprintf(out, size, "%s", input);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (base[0] == 0) {
|
|
147
|
+
snprintf(out, size, "%s", input);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
size_t schemeLength = xt_url_scheme_length(base);
|
|
152
|
+
if (schemeLength == 0) {
|
|
153
|
+
snprintf(out, size, "%s", input);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
int hasAuthority = base[schemeLength + 1] == '/' && base[schemeLength + 2] == '/';
|
|
157
|
+
if (input[0] == '/' && input[1] == '/') {
|
|
158
|
+
snprintf(out, size, "%.*s:%s", (int)schemeLength, base, input);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const char *authorityStart = base + schemeLength + (hasAuthority ? 3 : 1);
|
|
162
|
+
const char *authorityEnd = authorityStart;
|
|
163
|
+
while (*authorityEnd && *authorityEnd != '/' && *authorityEnd != '?' && *authorityEnd != '#') authorityEnd++;
|
|
164
|
+
const char *pathStart = authorityEnd;
|
|
165
|
+
if (*pathStart != '/') pathStart = "";
|
|
166
|
+
|
|
167
|
+
/* scheme: + //authority — the fixed prefix every resolved URL keeps. */
|
|
168
|
+
size_t prefix = 0;
|
|
169
|
+
if (prefix + schemeLength + 1 < size) {
|
|
170
|
+
memcpy(out + prefix, base, schemeLength);
|
|
171
|
+
prefix += schemeLength;
|
|
172
|
+
out[prefix++] = ':';
|
|
173
|
+
}
|
|
174
|
+
if (hasAuthority) {
|
|
175
|
+
if (prefix + 2 < size) {
|
|
176
|
+
out[prefix++] = '/';
|
|
177
|
+
out[prefix++] = '/';
|
|
178
|
+
}
|
|
179
|
+
size_t authorityLength = (size_t)(authorityEnd - authorityStart);
|
|
180
|
+
if (prefix + authorityLength >= size) authorityLength = size - prefix - 1;
|
|
181
|
+
memcpy(out + prefix, authorityStart, authorityLength);
|
|
182
|
+
prefix += authorityLength;
|
|
183
|
+
}
|
|
184
|
+
out[prefix] = 0;
|
|
185
|
+
|
|
186
|
+
/* Split the reference into its path and its `?query#fragment` suffix. */
|
|
187
|
+
const char *suffix = input;
|
|
188
|
+
while (*suffix && *suffix != '?' && *suffix != '#') suffix++;
|
|
189
|
+
size_t inputPathLength = (size_t)(suffix - input);
|
|
190
|
+
|
|
191
|
+
char joined[XT_URL_MAX];
|
|
192
|
+
joined[0] = 0;
|
|
193
|
+
if (input[0] == '/') {
|
|
194
|
+
if (inputPathLength >= sizeof(joined)) inputPathLength = sizeof(joined) - 1;
|
|
195
|
+
memcpy(joined, input, inputPathLength);
|
|
196
|
+
joined[inputPathLength] = 0;
|
|
197
|
+
} else {
|
|
198
|
+
size_t basePathLength = 0;
|
|
199
|
+
const char *basePathEnd = pathStart;
|
|
200
|
+
while (*basePathEnd && *basePathEnd != '?' && *basePathEnd != '#') basePathEnd++;
|
|
201
|
+
basePathLength = (size_t)(basePathEnd - pathStart);
|
|
202
|
+
size_t directory = 0;
|
|
203
|
+
for (size_t i = 0; i < basePathLength; i++) {
|
|
204
|
+
if (pathStart[i] == '/') directory = i + 1;
|
|
205
|
+
}
|
|
206
|
+
if (directory == 0 && hasAuthority) directory = 0;
|
|
207
|
+
if (directory >= sizeof(joined)) directory = sizeof(joined) - 1;
|
|
208
|
+
memcpy(joined, pathStart, directory);
|
|
209
|
+
joined[directory] = 0;
|
|
210
|
+
size_t room = sizeof(joined) - directory - 1;
|
|
211
|
+
if (inputPathLength > room) inputPathLength = room;
|
|
212
|
+
memcpy(joined + directory, input, inputPathLength);
|
|
213
|
+
joined[directory + inputPathLength] = 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
char normalized[XT_URL_MAX];
|
|
217
|
+
xt_url_normalize_path(joined, normalized, sizeof(normalized));
|
|
218
|
+
xt_url_append(out, size, normalized);
|
|
219
|
+
xt_url_append(out, size, suffix);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Canonicalize the Windows spellings of a `file:` URL. `file://C:/dir` and
|
|
224
|
+
* `file://C:\dir` both name the path `C:\dir`, and a special URL treats a
|
|
225
|
+
* backslash as a path separator, so both become `file:///C:/dir` — the same
|
|
226
|
+
* href `xt_url_cwd_base` produces and the same one Node reports. Anything
|
|
227
|
+
* else (including a real host) is copied unchanged.
|
|
228
|
+
*/
|
|
229
|
+
static void xt_url_canonical_file(const char *url, char *out, size_t size) {
|
|
230
|
+
snprintf(out, size, "%s", url);
|
|
231
|
+
if (xt_url_scheme_length(url) != 4 || strncmp(url, "file:", 5) != 0) return;
|
|
232
|
+
char normalized[XT_URL_MAX];
|
|
233
|
+
snprintf(normalized, sizeof(normalized), "%s", url);
|
|
234
|
+
for (char *p = normalized; *p && *p != '?' && *p != '#'; p++) {
|
|
235
|
+
if (*p == '\\') *p = '/';
|
|
236
|
+
}
|
|
237
|
+
const char *rest = normalized + 5;
|
|
238
|
+
if (rest[0] != '/' || rest[1] != '/') return;
|
|
239
|
+
const char *authority = rest + 2;
|
|
240
|
+
const char *end = authority;
|
|
241
|
+
while (*end && *end != '/' && *end != '?' && *end != '#') end++;
|
|
242
|
+
if (end - authority != 2 || !isalpha((unsigned char)authority[0]) || authority[1] != ':') return;
|
|
243
|
+
snprintf(out, size, "file:///%.*s%s", (int)(end - authority), authority, end);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Publish the read-only components parsed from `href` on the URL object. */
|
|
247
|
+
static void xt_url_set_components(xt_value url, const char *href) {
|
|
248
|
+
size_t schemeLength = xt_url_scheme_length(href);
|
|
249
|
+
char scheme[32];
|
|
250
|
+
if (schemeLength >= sizeof(scheme)) schemeLength = sizeof(scheme) - 1;
|
|
251
|
+
memcpy(scheme, href, schemeLength);
|
|
252
|
+
scheme[schemeLength] = 0;
|
|
253
|
+
char protocol[36];
|
|
254
|
+
snprintf(protocol, sizeof(protocol), "%s:", scheme);
|
|
255
|
+
|
|
256
|
+
const char *rest = href + schemeLength + (schemeLength > 0 && href[schemeLength] == ':' ? 1 : 0);
|
|
257
|
+
int hasAuthority = rest[0] == '/' && rest[1] == '/';
|
|
258
|
+
const char *authorityStart = rest + (hasAuthority ? 2 : 0);
|
|
259
|
+
const char *authorityEnd = authorityStart;
|
|
260
|
+
while (*authorityEnd && *authorityEnd != '/' && *authorityEnd != '?' && *authorityEnd != '#') authorityEnd++;
|
|
261
|
+
const char *pathStart = authorityEnd;
|
|
262
|
+
const char *pathEnd = pathStart;
|
|
263
|
+
while (*pathEnd && *pathEnd != '?' && *pathEnd != '#') pathEnd++;
|
|
264
|
+
const char *search = (*pathEnd == '?') ? pathEnd : "";
|
|
265
|
+
const char *searchEnd = search;
|
|
266
|
+
if (*search) while (*searchEnd && *searchEnd != '#') searchEnd++;
|
|
267
|
+
const char *hash = (*searchEnd == '#') ? searchEnd : (pathEnd && *pathEnd == '#' ? pathEnd : "");
|
|
268
|
+
|
|
269
|
+
char authority[512];
|
|
270
|
+
size_t authorityLength = (size_t)(authorityEnd - authorityStart);
|
|
271
|
+
if (authorityLength >= sizeof(authority)) authorityLength = sizeof(authority) - 1;
|
|
272
|
+
memcpy(authority, authorityStart, authorityLength);
|
|
273
|
+
authority[authorityLength] = 0;
|
|
274
|
+
|
|
275
|
+
char hostname[512];
|
|
276
|
+
snprintf(hostname, sizeof(hostname), "%s", authority);
|
|
277
|
+
char *colon = strrchr(hostname, ':');
|
|
278
|
+
char port[16] = "";
|
|
279
|
+
if (colon && (colon > hostname) && (colon[1] == 0 || (colon[1] >= '0' && colon[1] <= '9'))) {
|
|
280
|
+
snprintf(port, sizeof(port), "%s", colon + 1);
|
|
281
|
+
*colon = 0;
|
|
282
|
+
}
|
|
283
|
+
char pathname[XT_URL_MAX];
|
|
284
|
+
size_t pathLength = (size_t)(pathEnd - pathStart);
|
|
285
|
+
if (pathLength >= sizeof(pathname)) pathLength = sizeof(pathname) - 1;
|
|
286
|
+
memcpy(pathname, pathStart, pathLength);
|
|
287
|
+
pathname[pathLength] = 0;
|
|
288
|
+
char searchText[XT_URL_MAX];
|
|
289
|
+
size_t searchLength = (size_t)(searchEnd - search);
|
|
290
|
+
if (searchLength >= sizeof(searchText)) searchLength = sizeof(searchText) - 1;
|
|
291
|
+
memcpy(searchText, search, searchLength);
|
|
292
|
+
searchText[searchLength] = 0;
|
|
293
|
+
char hashText[XT_URL_MAX];
|
|
294
|
+
snprintf(hashText, sizeof(hashText), "%s", hash);
|
|
295
|
+
char origin[XT_URL_MAX];
|
|
296
|
+
if (!hasAuthority) snprintf(origin, sizeof(origin), "%s", protocol);
|
|
297
|
+
else if (strcmp(scheme, "file") == 0) snprintf(origin, sizeof(origin), "null");
|
|
298
|
+
else snprintf(origin, sizeof(origin), "%s://%s", scheme, authority);
|
|
299
|
+
|
|
300
|
+
xt_object_set(url, xt_string_from_cstr("href"), xt_string_from_cstr(href));
|
|
301
|
+
xt_object_set(url, xt_string_from_cstr("protocol"), xt_string_from_cstr(protocol));
|
|
302
|
+
xt_object_set(url, xt_string_from_cstr("origin"), xt_string_from_cstr(origin));
|
|
303
|
+
xt_object_set(url, xt_string_from_cstr("host"), xt_string_from_cstr(authority));
|
|
304
|
+
xt_object_set(url, xt_string_from_cstr("hostname"), xt_string_from_cstr(hostname));
|
|
305
|
+
xt_object_set(url, xt_string_from_cstr("port"), xt_string_from_cstr(port));
|
|
306
|
+
xt_object_set(url, xt_string_from_cstr("pathname"), xt_string_from_cstr(pathname));
|
|
307
|
+
xt_object_set(url, xt_string_from_cstr("search"), xt_string_from_cstr(searchText));
|
|
308
|
+
xt_object_set(url, xt_string_from_cstr("hash"), xt_string_from_cstr(hashText));
|
|
309
|
+
|
|
310
|
+
/* `searchParams` is a snapshot of `search`; the WHATWG live link between the
|
|
311
|
+
* two is out of scope, like the `URL` setters. */
|
|
312
|
+
xt_value query = xt_string_from_cstr(searchText);
|
|
313
|
+
xt_object_set(url, xt_string_from_cstr("searchParams"), xt_url_search_params_ctor(1, &query));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
static xt_value xt_url_method_href(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
|
|
317
|
+
(void)env;
|
|
318
|
+
(void)argc;
|
|
319
|
+
(void)argv;
|
|
320
|
+
return xt_object_get(thisValue, xt_string_from_cstr("href"));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
xt_value xt_url_ctor(int32_t argc, xt_value *argv) {
|
|
324
|
+
/* `new URL(input, base)`: a base may itself be a URL object. */
|
|
325
|
+
xt_value inputValue = xt_arg_at(argc, argv, 0);
|
|
326
|
+
xt_value baseValue = xt_arg_at(argc, argv, 1);
|
|
327
|
+
xt_value input = inputValue == XT_UNDEFINED ? xt_string_from_cstr("") : xt_to_string(inputValue);
|
|
328
|
+
char base[XT_URL_MAX];
|
|
329
|
+
base[0] = 0;
|
|
330
|
+
if (baseValue != XT_UNDEFINED) {
|
|
331
|
+
xt_value href = XT_IS_OBJECT(baseValue)
|
|
332
|
+
? xt_object_get(baseValue, xt_string_from_cstr("href"))
|
|
333
|
+
: xt_to_string(baseValue);
|
|
334
|
+
snprintf(base, sizeof(base), "%s", href == XT_UNDEFINED ? "" : xt_string_data(href));
|
|
335
|
+
}
|
|
336
|
+
const char *inputText = xt_string_data(input);
|
|
337
|
+
if (!inputText) inputText = "";
|
|
338
|
+
|
|
339
|
+
/* A reference without a scheme is a filesystem path. With no explicit base
|
|
340
|
+
* it resolves against the working directory, so `new URL("./a.txt")` is a
|
|
341
|
+
* `file://` URL rather than an unparseable relative one. */
|
|
342
|
+
char adjusted[XT_URL_MAX];
|
|
343
|
+
if (base[0] == 0 && xt_url_scheme_length(inputText) == 0) {
|
|
344
|
+
xt_url_cwd_base(base, sizeof(base));
|
|
345
|
+
}
|
|
346
|
+
#if defined(_WIN32)
|
|
347
|
+
if (xt_url_scheme_length(inputText) == 0 && isalpha((unsigned char)inputText[0]) &&
|
|
348
|
+
inputText[1] == ':' && (inputText[2] == '\\' || inputText[2] == '/')) {
|
|
349
|
+
/* `C:\dir\file` -> `/C:/dir/file`, the path part of a file URL. */
|
|
350
|
+
size_t j = 0;
|
|
351
|
+
adjusted[j++] = '/';
|
|
352
|
+
for (size_t i = 0; inputText[i] && j + 1 < sizeof(adjusted); i++) {
|
|
353
|
+
adjusted[j++] = inputText[i] == '\\' ? '/' : inputText[i];
|
|
354
|
+
}
|
|
355
|
+
adjusted[j] = 0;
|
|
356
|
+
inputText = adjusted;
|
|
357
|
+
}
|
|
358
|
+
#endif
|
|
359
|
+
|
|
360
|
+
char canonical[XT_URL_MAX];
|
|
361
|
+
xt_url_canonical_file(inputText, canonical, sizeof(canonical));
|
|
362
|
+
inputText = canonical;
|
|
363
|
+
|
|
364
|
+
char href[XT_URL_MAX];
|
|
365
|
+
xt_url_resolve(inputText, base, href, sizeof(href));
|
|
366
|
+
xt_value url = xt_object_new();
|
|
367
|
+
xt_url_set_components(url, href);
|
|
368
|
+
xt_object_set(url, xt_string_from_cstr("toString"),
|
|
369
|
+
xt_closure_new((void *)xt_url_method_href, 0, NULL));
|
|
370
|
+
xt_object_set(url, xt_string_from_cstr("toJSON"),
|
|
371
|
+
xt_closure_new((void *)xt_url_method_href, 0, NULL));
|
|
372
|
+
return url;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* -- filesystem paths ----------------------------------------------------- */
|
|
376
|
+
|
|
377
|
+
static int xt_url_hex(char c) {
|
|
378
|
+
if (c >= '0' && c <= '9') return c - '0';
|
|
379
|
+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
380
|
+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
381
|
+
return -1;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Append the percent-decoded `text` to `out` (a decoded `%2F` stays escaped). */
|
|
385
|
+
static void xt_url_append_decoded(char *out, size_t size, const char *text, size_t length) {
|
|
386
|
+
size_t used = strlen(out);
|
|
387
|
+
for (size_t i = 0; i < length && used + 2 < size; i++) {
|
|
388
|
+
if (text[i] == '%' && i + 2 < length && xt_url_hex(text[i + 1]) >= 0 && xt_url_hex(text[i + 2]) >= 0) {
|
|
389
|
+
out[used++] = (char)((xt_url_hex(text[i + 1]) << 4) | xt_url_hex(text[i + 2]));
|
|
390
|
+
i += 2;
|
|
391
|
+
} else {
|
|
392
|
+
out[used++] = text[i] == '\\' ? '/' : text[i];
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
out[used] = 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Turn a `fetch` reference into a filesystem path.
|
|
400
|
+
*
|
|
401
|
+
* Returns 1 and writes an absolute path for `file:` URLs and for relative
|
|
402
|
+
* references with no scheme (resolved against the working directory), 0 for
|
|
403
|
+
* another scheme. The `?query` / `#fragment` suffix is not part of the path.
|
|
404
|
+
*/
|
|
405
|
+
int xt_url_file_path(const char *url, char *out, size_t size) {
|
|
406
|
+
if (!url || url[0] == '\0') return 0;
|
|
407
|
+
out[0] = 0;
|
|
408
|
+
char work[XT_URL_MAX];
|
|
409
|
+
snprintf(work, sizeof(work), "%s", url);
|
|
410
|
+
char *fragment = work;
|
|
411
|
+
while (*fragment && *fragment != '?' && *fragment != '#') fragment++;
|
|
412
|
+
*fragment = 0;
|
|
413
|
+
/* A reference that is only a query/fragment has no file to read; let the
|
|
414
|
+
* caller report it as an invalid URL like an empty `fetch("")`. */
|
|
415
|
+
if (work[0] == '\0') return 0;
|
|
416
|
+
|
|
417
|
+
size_t schemeLength = xt_url_scheme_length(work);
|
|
418
|
+
const char *path = work;
|
|
419
|
+
if (schemeLength > 0) {
|
|
420
|
+
if (schemeLength != 4 || strncmp(work, "file:", 5) != 0) return 0;
|
|
421
|
+
/* `file://C:\dir` is the Windows spelling of `file:///C:/dir`; rewrite it
|
|
422
|
+
* before separating the authority so the drive is not mistaken for a host
|
|
423
|
+
* (and a backslash is a separator, as in a browser). */
|
|
424
|
+
char canonical[XT_URL_MAX];
|
|
425
|
+
xt_url_canonical_file(work, canonical, sizeof(canonical));
|
|
426
|
+
path = canonical + 5;
|
|
427
|
+
if (path[0] == '/' && path[1] == '/') {
|
|
428
|
+
path += 2;
|
|
429
|
+
const char *slash = strchr(path, '/');
|
|
430
|
+
if (slash != NULL) {
|
|
431
|
+
size_t hostLength = (size_t)(slash - path);
|
|
432
|
+
int localhost = hostLength == 9 && strncmp(path, "localhost", 9) == 0;
|
|
433
|
+
if (hostLength > 0 && !localhost) return 0;
|
|
434
|
+
path = slash;
|
|
435
|
+
} else {
|
|
436
|
+
path = "/";
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
xt_url_append_decoded(out, size, path, strlen(path));
|
|
440
|
+
} else {
|
|
441
|
+
/* Relative references are relative to the process working directory. */
|
|
442
|
+
char cwd[4096];
|
|
443
|
+
cwd[0] = 0;
|
|
444
|
+
if (xt_url_getcwd(cwd, sizeof(cwd)) == NULL) cwd[0] = 0;
|
|
445
|
+
int absolute = work[0] == '/' || work[0] == '\\';
|
|
446
|
+
#if defined(_WIN32)
|
|
447
|
+
if (!absolute && isalpha((unsigned char)work[0]) && work[1] == ':' &&
|
|
448
|
+
(work[2] == '/' || work[2] == '\\')) {
|
|
449
|
+
absolute = 1;
|
|
450
|
+
}
|
|
451
|
+
#endif
|
|
452
|
+
if (!absolute && cwd[0] != 0) {
|
|
453
|
+
snprintf(out, size, "%s/", cwd);
|
|
454
|
+
}
|
|
455
|
+
xt_url_append_decoded(out, size, work, strlen(work));
|
|
456
|
+
}
|
|
457
|
+
#if defined(_WIN32)
|
|
458
|
+
if (out[0] == '/' && isalpha((unsigned char)out[1]) && out[2] == ':') {
|
|
459
|
+
memmove(out, out + 1, strlen(out));
|
|
460
|
+
}
|
|
461
|
+
#endif
|
|
462
|
+
return out[0] != 0;
|
|
463
|
+
}
|
|
@@ -200,6 +200,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
200
200
|
"declare i64 @xt_text_decoder_ctor(i32, i64*)",
|
|
201
201
|
"declare i64 @xt_date_ctor(i32, i64*)",
|
|
202
202
|
"declare i64 @xt_regexp_ctor(i32, i64*)",
|
|
203
|
+
"declare i64 @xt_url_ctor(i32, i64*)",
|
|
203
204
|
"declare i64 @xt_url_search_params_ctor(i32, i64*)",
|
|
204
205
|
"declare i64 @xt_array_buffer_ctor(i32, i64*)",
|
|
205
206
|
"declare i64 @xt_array_buffer_static(i64, i32, i64*)",
|
|
@@ -316,6 +316,7 @@ export const CTOR_FUNCTIONS: Record<string, string> = {
|
|
|
316
316
|
Socket: "xt_net_socket_ctor",
|
|
317
317
|
Server: "xt_net_server_ctor",
|
|
318
318
|
EventEmitter: "xt_event_emitter_ctor",
|
|
319
|
+
URL: "xt_url_ctor",
|
|
319
320
|
URLSearchParams: "xt_url_search_params_ctor",
|
|
320
321
|
TextEncoder: "xt_text_encoder_ctor",
|
|
321
322
|
TextDecoder: "xt_text_decoder_ctor",
|