@wix/himalaya-cli 0.1579.0 → 0.1580.0
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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/motion/src/index.d.ts +51 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/media/src/index.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/media/src/rendition.d.ts +42 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/motion.mjs +22 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-media.mjs +78 -0
- package/dist/cli.mjs +9019 -7448
- package/dist/preview-assets.generated.bin +0 -0
- package/dist/quickjs-intl.js +462 -0
- package/dist/quickjs-web-globals.js +540 -0
- package/dist/quickjs-worker-entry.js +313 -0
- package/guides/himi-json-output.md +30 -1
- package/guides/one-app-section-mappings.md +4 -5
- package/guides/one-app-to-himi-app.md +214 -6
- package/guides/one-app-viewer.md +2 -2
- package/guides/sdk-reference.json +36 -6
- package/package.json +3 -2
|
Binary file
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// ../../core/dev-server/src/quickjs-intl.ts
|
|
2
|
+
var NONFINITE_TAG = "\0himi-nonfinite:";
|
|
3
|
+
var INTL_SHIM = `
|
|
4
|
+
// ############################################################################################
|
|
5
|
+
// NO BACKTICKS AND NO LITERAL NUL ANYWHERE BELOW. This whole block is a TEMPLATE LITERAL in the
|
|
6
|
+
// TypeScript source, so a backtick in a comment terminates it and a NUL byte makes grep treat
|
|
7
|
+
// the entire file as binary and silently match nothing. Both have happened, twice and once
|
|
8
|
+
// respectively, while editing these comments. Write length and this and Intl.X unquoted.
|
|
9
|
+
// ############################################################################################
|
|
10
|
+
(function () {
|
|
11
|
+
var NF_TAG = ${JSON.stringify(NONFINITE_TAG)};
|
|
12
|
+
|
|
13
|
+
// EVERY BUILTIN THIS SHIM USES IS CAPTURED HERE, BEFORE AUTHOR CODE RUNS.
|
|
14
|
+
//
|
|
15
|
+
// The shim installs at prelude time and its functions run LATER, inside the bundle's call
|
|
16
|
+
// stack \u2014 so any global it resolves at call time is a global the bundle can replace first.
|
|
17
|
+
// Measured: a bundle doing globalThis.Symbol = undefined made every ToPrimitive reduction throw
|
|
18
|
+
// TypeError, where native Intl kept using the intrinsic well-known symbol and fell back to
|
|
19
|
+
// valueOf. That is a false FAILURE, and it reached the format path as well as the locale walk
|
|
20
|
+
// because both share the reduction.
|
|
21
|
+
//
|
|
22
|
+
// Same discipline the prelude already applies to its readback channel, and for the same stated
|
|
23
|
+
// reason: anything reachable after untrusted code runs is reachable BY it. JSON.stringify is
|
|
24
|
+
// called out there by name as a global that was reassigned in exactly this way.
|
|
25
|
+
var $Sym = typeof Symbol !== "undefined" && Symbol ? Symbol.toPrimitive : undefined;
|
|
26
|
+
var $stringify = JSON.stringify, $parse = JSON.parse;
|
|
27
|
+
var $is = Object.is, $trunc = Math.trunc, $isFinite = isFinite;
|
|
28
|
+
var $String = String, $Number = Number, $Boolean = Boolean, $TypeError = TypeError;
|
|
29
|
+
var $now = Date.now;
|
|
30
|
+
// Reflect.apply, because INVOKING a user function must not read a property off it. The spec's
|
|
31
|
+
// Call operation goes straight to the function's internal [[Call]]; sp.call(...) consults the
|
|
32
|
+
// mutable .call slot, which a bundle can poison:
|
|
33
|
+
// var f = function () { return 5; }; f.call = function () { throw new Error("poison"); };
|
|
34
|
+
// o[Symbol.toPrimitive] = f; -> host formats 5, the shim threw "poison"
|
|
35
|
+
var $apply = Reflect.apply;
|
|
36
|
+
var D = globalThis.__himiIntlDate, N = globalThis.__himiIntlNumber,
|
|
37
|
+
C = globalThis.__himiIntlCompare, V = globalThis.__himiIntlValidate,
|
|
38
|
+
L = globalThis.__himiIntlToLocale;
|
|
39
|
+
// Captured before any bundle runs, for the same reason every other intrinsic here is: a bundle
|
|
40
|
+
// that replaces 'Date.prototype.getTime' must not be able to steer what this hands the host.
|
|
41
|
+
var $Date = Date, $getTime = Date.prototype.getTime;
|
|
42
|
+
delete globalThis.__himiIntlDate; delete globalThis.__himiIntlNumber;
|
|
43
|
+
delete globalThis.__himiIntlCompare; delete globalThis.__himiIntlValidate;
|
|
44
|
+
delete globalThis.__himiIntlToLocale;
|
|
45
|
+
// Non-finite numbers are TAGGED on the way out. JSON has no spelling for them, so a plain
|
|
46
|
+
// stringify turns NaN into null, the host reads To$Number(null) === 0, and the one value Intl
|
|
47
|
+
// rejects becomes a value it accepts.
|
|
48
|
+
var enc = function (v) {
|
|
49
|
+
try {
|
|
50
|
+
if (v === undefined) return "";
|
|
51
|
+
return $stringify(v, function (k, val) {
|
|
52
|
+
return typeof val === "number" && !$isFinite(val) ? NF_TAG + $String(val) : val;
|
|
53
|
+
});
|
|
54
|
+
} catch (e) { return ""; }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// OPTION BAGS ARE READ BY PROPERTY LOOKUP, NOT SERIALIZED. JSON.stringify captures own
|
|
58
|
+
// ENUMERABLE properties only, while Intl constructors do an ordinary [[Get]] per recognized
|
|
59
|
+
// key \u2014 so a non-enumerable or prototype-inherited option is meaningful to the host and
|
|
60
|
+
// invisible to a serializer:
|
|
61
|
+
//
|
|
62
|
+
// var o = {}; Object.defineProperty(o, "style", { value: "currency" });
|
|
63
|
+
// new Intl.NumberFormat("en", o) // host: throws, currency is missing
|
|
64
|
+
// // $stringify(o) === "{}" -> validated as defaults
|
|
65
|
+
//
|
|
66
|
+
// which is a false pass again. So each bag is rebuilt by reading it through the same lookup the
|
|
67
|
+
// host would use (getters included), and only THAT flat object is serialized.
|
|
68
|
+
//
|
|
69
|
+
// READ EXACTLY THE KEYS THE HOST READS, AND NOT ONE MORE. An earlier cut added a for..in
|
|
70
|
+
// pass so that an option ECMA-402 adds later would still travel rather than be silently
|
|
71
|
+
// dropped. That was being cleverer than the spec, and it cost a FALSE FAILURE in the other
|
|
72
|
+
// direction: host Intl never enumerates the bag, it does one [[Get]] per recognized key, so
|
|
73
|
+
//
|
|
74
|
+
// new Intl.NumberFormat("en", { style: "percent", get unrelated() { throw new Error("boom"); } })
|
|
75
|
+
// host "50%" \u2014 never touches the unrelated getter
|
|
76
|
+
// for..in \u2014 invokes it, and the run dies on someone else's getter
|
|
77
|
+
//
|
|
78
|
+
// The residual cost is accepted deliberately: when the host gains an option this list has not
|
|
79
|
+
// heard of, a bundle using it early would have that option dropped. That needs the host to grow
|
|
80
|
+
// AND a bundle to adopt it before this list is updated, whereas the getter above breaks a valid
|
|
81
|
+
// bundle today. Matching the host's read set exactly is the only rule that cannot diverge in
|
|
82
|
+
// either direction now, and "now" is what a crawl reports on.
|
|
83
|
+
//
|
|
84
|
+
// A NON-PRIMITIVE VALUE IS COERCED HERE, NOT DROPPED. Skipping it read as "absent" and the host
|
|
85
|
+
// does no such thing \u2014 it coerces, and then usually rejects:
|
|
86
|
+
//
|
|
87
|
+
// { style: function () {} } host: ToString -> invalid enum -> RangeError
|
|
88
|
+
// { style: {} } host: ToString -> invalid enum -> RangeError
|
|
89
|
+
// { minimumFractionDigits: { valueOf: () => 3 } } host: ToNumber -> 3 -> ACCEPTED
|
|
90
|
+
//
|
|
91
|
+
// so dropping produced a false pass on the first two, and failing closed would produce a false
|
|
92
|
+
// FAILURE on the third. Both are forbidden. The coercion is per option class because that is
|
|
93
|
+
// what the spec does \u2014 ToNumber for the digit counts, ToBoolean for the flags, ToString for
|
|
94
|
+
// everything else \u2014 and those are language-level operations, so performing them in the guest is
|
|
95
|
+
// identical to the host performing them. After coercion the value is a primitive and marshals
|
|
96
|
+
// exactly. A primitive is passed through verbatim and left for the host to coerce, which matters
|
|
97
|
+
// for useGrouping, where a real boolean and the string "true" are not interchangeable.
|
|
98
|
+
var NUMK = ["minimumIntegerDigits","minimumFractionDigits","maximumFractionDigits",
|
|
99
|
+
"minimumSignificantDigits","maximumSignificantDigits","roundingIncrement","fractionalSecondDigits"];
|
|
100
|
+
var BOOLK = ["hour12","ignorePunctuation","numeric"];
|
|
101
|
+
var has = function (arr, k) { for (var i = 0; i < arr.length; i++) if (arr[i] === k) return true; return false; };
|
|
102
|
+
// ToString, which is NOT the $String() function. They differ on exactly one input: $String()
|
|
103
|
+
// stringifies a Symbol, spec ToString throws. Intl applies ToString to string-valued options
|
|
104
|
+
// and to both sides of a collation, so $String() reported an ordering where the host threw.
|
|
105
|
+
var toStr = function (v) {
|
|
106
|
+
if (typeof v === "symbol") throw new $TypeError("Cannot convert a Symbol value to a string");
|
|
107
|
+
return $String(v);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// CanonicalizeLocaleList, which JSON cannot stand in for. A locale list is walked by INDEX with
|
|
111
|
+
// a HasProperty check, so a hole is skipped \u2014 while JSON.stringify renders it as null and the
|
|
112
|
+
// host then rejects the null:
|
|
113
|
+
//
|
|
114
|
+
// new Intl.NumberFormat(Array(1)) host: fine, the hole is skipped
|
|
115
|
+
// $stringify(Array(1)) "[null]" -> host TypeError
|
|
116
|
+
//
|
|
117
|
+
// which failed a bundle that works. Normalised here to a dense array of strings instead.
|
|
118
|
+
var encLocales = function (v) {
|
|
119
|
+
if (v === undefined) return "";
|
|
120
|
+
if (typeof v === "string") return $stringify([v]);
|
|
121
|
+
// ToObject: throws for null/undefined and succeeds for everything else. A PRIMITIVE becomes a
|
|
122
|
+
// wrapper whose length is undefined, which LengthOfArrayLike reads as 0 \u2014 an EMPTY list,
|
|
123
|
+
// i.e. the default locale. So {} and 5 and even a Symbol are all accepted by the host and
|
|
124
|
+
// format with the default locale; stringifying them into a locale was a false failure.
|
|
125
|
+
if (v === null) throw new $TypeError("Cannot convert undefined or null to object");
|
|
126
|
+
// LengthOfArrayLike, not $Number(): ToLength truncates toward zero and clamps, and ToNumber of
|
|
127
|
+
// a BigInt throws. length 1.9 means index 0 ONLY \u2014 reading index 1 invoked a getter the host
|
|
128
|
+
// never touches \u2014 and length 1n is a TypeError rather than 1.
|
|
129
|
+
var len = v.length;
|
|
130
|
+
// Reduce it FIRST, the same way every other numeric argument is reduced. LengthOfArrayLike
|
|
131
|
+
// applies spec ToNumber, which is ToPrimitive then reject-a-BigInt \u2014 so an object whose
|
|
132
|
+
// primitive is a BigInt is a TypeError, while explicit $Number() on that same object walks
|
|
133
|
+
// valueOf, gets 1n, and quietly returns 1. This is the round-8 class; the reduction existed
|
|
134
|
+
// and simply was not applied to length when this walk was written.
|
|
135
|
+
var lt = typeof len;
|
|
136
|
+
if (len !== null && (lt === "object" || lt === "function")) len = toPrim(len, "number");
|
|
137
|
+
if (typeof len === "bigint") throw new $TypeError("Cannot convert a BigInt value to a number");
|
|
138
|
+
var n = $Number(len);
|
|
139
|
+
n = n !== n ? 0 : $trunc(n);
|
|
140
|
+
if (n < 0) n = 0;
|
|
141
|
+
if (n > 9007199254740991) n = 9007199254740991;
|
|
142
|
+
var out = [];
|
|
143
|
+
for (var i = 0; i < n; i++) {
|
|
144
|
+
if (i in v) {
|
|
145
|
+
var el = v[i];
|
|
146
|
+
var et = typeof el;
|
|
147
|
+
// Each element must be a String or an Object. Anything else is a TypeError on the host,
|
|
148
|
+
// not the bad-locale RangeError that stringifying it would have produced.
|
|
149
|
+
if (et !== "string" && !(el !== null && (et === "object" || et === "function"))) {
|
|
150
|
+
throw new $TypeError("Locale must be a string or an object");
|
|
151
|
+
}
|
|
152
|
+
out.push(toStr(el));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return $stringify(out);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ToPrimitive, so an OBJECT reduces to a primitive before anything classifies it.
|
|
159
|
+
//
|
|
160
|
+
// This is what closes the coercion axis instead of adding a third BigInt special case. Explicit
|
|
161
|
+
// $Number() differs from spec ToNumber in exactly ONE way \u2014 it accepts a BigInt where ToNumber
|
|
162
|
+
// throws \u2014 so every object whose primitive is a BigInt slipped past a typeof check:
|
|
163
|
+
//
|
|
164
|
+
// { [Symbol.toPrimitive]: () => 1n } host TypeError, $Number() gave 1
|
|
165
|
+
// { [Symbol.toPrimitive]: () => 9007199254740993n } host exact, $Number() rounded it
|
|
166
|
+
//
|
|
167
|
+
// Reduce first and the primitive paths below \u2014 already exact as of the previous commit \u2014 handle
|
|
168
|
+
// every input there is. Ordinary order is valueOf-then-toString for a number hint and the
|
|
169
|
+
// reverse for a string hint, same as the spec.
|
|
170
|
+
var toPrim = function (v, hint) {
|
|
171
|
+
if (v === null) return v;
|
|
172
|
+
var t = typeof v;
|
|
173
|
+
if (t !== "object" && t !== "function") return v;
|
|
174
|
+
var sp = v[$Sym];
|
|
175
|
+
if (sp !== undefined && sp !== null) {
|
|
176
|
+
var r = $apply(sp, v, [hint]);
|
|
177
|
+
var rt = typeof r;
|
|
178
|
+
if (r === null || (rt !== "object" && rt !== "function")) return r;
|
|
179
|
+
throw new $TypeError("Cannot convert object to primitive value");
|
|
180
|
+
}
|
|
181
|
+
var order = hint === "string" ? ["toString", "valueOf"] : ["valueOf", "toString"];
|
|
182
|
+
for (var i = 0; i < order.length; i++) {
|
|
183
|
+
var f = v[order[i]];
|
|
184
|
+
if (typeof f === "function") {
|
|
185
|
+
var res = $apply(f, v, []);
|
|
186
|
+
var rest = typeof res;
|
|
187
|
+
if (res === null || (rest !== "object" && rest !== "function")) return res;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
throw new $TypeError("Cannot convert object to primitive value");
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
var coerce = function (k, v) {
|
|
194
|
+
var t = typeof v;
|
|
195
|
+
// A primitive travels VERBATIM and the host coerces it. That is what keeps useGrouping:false
|
|
196
|
+
// a boolean rather than the string "false", which the host would reject.
|
|
197
|
+
if (v === null || t === "string" || t === "number" || t === "boolean") return v;
|
|
198
|
+
// ToBoolean never calls ToPrimitive \u2014 an object is simply truthy \u2014 so the boolean class must
|
|
199
|
+
// not be reduced first.
|
|
200
|
+
if (has(BOOLK, k)) return $Boolean(v);
|
|
201
|
+
if (t === "bigint") {
|
|
202
|
+
if (has(NUMK, k)) throw new $TypeError("Cannot convert a BigInt value to a number");
|
|
203
|
+
return toStr(v);
|
|
204
|
+
}
|
|
205
|
+
var p = toPrim(v, has(NUMK, k) ? "number" : "string");
|
|
206
|
+
if (has(NUMK, k)) {
|
|
207
|
+
if (typeof p === "bigint") throw new $TypeError("Cannot convert a BigInt value to a number");
|
|
208
|
+
return $Number(p);
|
|
209
|
+
}
|
|
210
|
+
return toStr(p);
|
|
211
|
+
};
|
|
212
|
+
var pick = function (o, keys) {
|
|
213
|
+
var out = {};
|
|
214
|
+
if (o === undefined) return out;
|
|
215
|
+
// null IS NOT THE SAME AS OMITTED. The constructors special-case undefined only \u2014 an options
|
|
216
|
+
// bag goes through ToObject, and ToObject(null) throws \u2014 so treating null as "use defaults"
|
|
217
|
+
// passed a bundle the host rejects:
|
|
218
|
+
// new Intl.NumberFormat("en", null) host TypeError, this returned {} and validated clean
|
|
219
|
+
if (o === null) throw new $TypeError("Cannot convert undefined or null to object");
|
|
220
|
+
for (var i = 0; i < keys.length; i++) {
|
|
221
|
+
var v = o[keys[i]];
|
|
222
|
+
if (v !== undefined) out[keys[i]] = coerce(keys[i], v);
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
};
|
|
226
|
+
var DTF = ["localeMatcher","calendar","numberingSystem","hour12","hourCycle","timeZone","weekday",
|
|
227
|
+
"era","year","month","day","dayPeriod","hour","minute","second","fractionalSecondDigits",
|
|
228
|
+
"timeZoneName","formatMatcher","dateStyle","timeStyle"];
|
|
229
|
+
var NF = ["localeMatcher","style","currency","currencyDisplay","currencySign","unit","unitDisplay",
|
|
230
|
+
"numberingSystem","minimumIntegerDigits","minimumFractionDigits","maximumFractionDigits",
|
|
231
|
+
"minimumSignificantDigits","maximumSignificantDigits","useGrouping","notation","compactDisplay",
|
|
232
|
+
"signDisplay","roundingMode","roundingIncrement","roundingPriority","trailingZeroDisplay"];
|
|
233
|
+
var COL = ["localeMatcher","usage","sensitivity","ignorePunctuation","numeric","caseFirst","collation"];
|
|
234
|
+
|
|
235
|
+
// NO resolvedOptions(). It would have to invent resolved values \u2014 a locale, a calendar, a
|
|
236
|
+
// numberingSystem, a timeZone \u2014 and echoing the caller's own option bag back is a confident
|
|
237
|
+
// wrong answer, not an implementation: a bundle branching on the resolved timeZone would read
|
|
238
|
+
// undefined here and a real zone on device. Absent, it throws where it is unsupported, which
|
|
239
|
+
// is the honest failure and the rule the header states.
|
|
240
|
+
// (No backticks in this string: it is a template literal in the TS source.)
|
|
241
|
+
//
|
|
242
|
+
// EVERY CONSTRUCTOR VALIDATES THROUGH THE HOST, because host Intl rejects a bad locale or
|
|
243
|
+
// option bag AT CONSTRUCTION, not at format time. Deferring meant
|
|
244
|
+
// new Intl.NumberFormat("en", { style: "currency" }) // no currency
|
|
245
|
+
// succeeded here and threw under node:vm, so a bundle that built a bad formatter without
|
|
246
|
+
// reaching a format call passed the crawl and died on device. Same false-pass class the
|
|
247
|
+
// swallowed-throw bug was.
|
|
248
|
+
function DateTimeFormat(locales, options) {
|
|
249
|
+
if (!(this instanceof DateTimeFormat)) return new DateTimeFormat(locales, options);
|
|
250
|
+
var l = encLocales(locales), o = enc(pick(options, DTF));
|
|
251
|
+
V("DateTimeFormat", l, o);
|
|
252
|
+
// ToNumber on the date argument, which means ToPrimitive first: an object whose primitive is
|
|
253
|
+
// a BigInt is a TypeError on the host, not a date.
|
|
254
|
+
this.format = function (d) {
|
|
255
|
+
var v = d;
|
|
256
|
+
var t = typeof v;
|
|
257
|
+
if (v !== null && (t === "object" || t === "function")) v = toPrim(v, "number");
|
|
258
|
+
if (typeof v === "bigint") throw new $TypeError("Cannot convert a BigInt value to a number");
|
|
259
|
+
return D(l, o, v === undefined ? $now() : $Number(v));
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function NumberFormat(locales, options) {
|
|
263
|
+
if (!(this instanceof NumberFormat)) return new NumberFormat(locales, options);
|
|
264
|
+
var l = encLocales(locales), o = enc(pick(options, NF));
|
|
265
|
+
V("NumberFormat", l, o);
|
|
266
|
+
// THE VALUE CROSSES AS A STRING PLUS A KIND, never as a coerced double. Host NumberFormat
|
|
267
|
+
// takes an Intl mathematical value, which is EXACT for a BigInt and for a numeric string:
|
|
268
|
+
// format(9007199254740993n) -> 9,007,199,254,740,993
|
|
269
|
+
// format($Number(that)) -> 9,007,199,254,740,992 <- last digit gone
|
|
270
|
+
// format("9007199254740993") -> 9,007,199,254,740,993
|
|
271
|
+
// so $Number() here silently reformatted anything past double precision. A number still goes
|
|
272
|
+
// as a number: $String(Number) round-trips a double exactly.
|
|
273
|
+
this.format = function (n) {
|
|
274
|
+
var v = n;
|
|
275
|
+
var t = typeof v;
|
|
276
|
+
// ToPrimitive first \u2014 the exactness below is only exact if it sees what the host sees.
|
|
277
|
+
if (v !== null && (t === "object" || t === "function")) v = toPrim(v, "number");
|
|
278
|
+
t = typeof v;
|
|
279
|
+
// $String, not v.toString(): ToString of a BigInt is an internal operation, so it is
|
|
280
|
+
// unaffected by a replaced BigInt.prototype.toString, which the method call was not.
|
|
281
|
+
if (t === "bigint") return N(l, o, $String(v), "b");
|
|
282
|
+
if (t === "string") return N(l, o, v, "s");
|
|
283
|
+
// NEGATIVE ZERO survives the crossing explicitly. The host prints "-0" \u2014 it keeps the sign
|
|
284
|
+
// \u2014 while $String(-0) is "0", so the sign was lost on the way out and never came back.
|
|
285
|
+
// $Number("-0") is -0, so spelling it is enough to restore it.
|
|
286
|
+
var n2 = $Number(v);
|
|
287
|
+
return N(l, o, $is(n2, -0) ? "-0" : $String(n2), "n");
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
// Collator carries its OPTIONS through. Dropping them made { numeric: true } compare "2" and
|
|
291
|
+
// "10" lexically \u2014 2 after 10 \u2014 which is a plausible-looking wrong order, the worst kind.
|
|
292
|
+
function Collator(locales, options) {
|
|
293
|
+
if (!(this instanceof Collator)) return new Collator(locales, options);
|
|
294
|
+
var l = encLocales(locales), o = enc(pick(options, COL));
|
|
295
|
+
V("Collator", l, o);
|
|
296
|
+
this.compare = function (a, b) { return C(toStr(a), toStr(b), l, o); };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
globalThis.Intl = { DateTimeFormat: DateTimeFormat, NumberFormat: NumberFormat, Collator: Collator };
|
|
300
|
+
// QuickJS ships a localeCompare that is a code-unit subtraction, so "a".localeCompare("B") is
|
|
301
|
+
// 31 where ICU says -1 \u2014 a SIGN FLIP, not a magnitude difference, which silently reverses
|
|
302
|
+
// every sort a bundle performs. Replaced rather than left alone, and its third argument is
|
|
303
|
+
// threaded too: localeCompare(that, locales, { numeric: true }) is the idiomatic natural sort.
|
|
304
|
+
String.prototype.localeCompare = function (that, locales, options) {
|
|
305
|
+
// STRICT, so this is not boxed. The native method does RequireObjectCoercible on its
|
|
306
|
+
// receiver and throws for null/undefined; a sloppy replacement has this already rebound to
|
|
307
|
+
// globalThis by the time it runs, so it stringified the global object and compared happily
|
|
308
|
+
// where the host threw.
|
|
309
|
+
"use strict";
|
|
310
|
+
if (this === null || this === undefined) {
|
|
311
|
+
throw new $TypeError("String.prototype.localeCompare called on null or undefined");
|
|
312
|
+
}
|
|
313
|
+
return C(toStr(this), toStr(that), encLocales(locales), enc(pick(options, COL)));
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Date.prototype.toLocale{,Date,Time}String \u2014 SEPARATE built-ins from Intl.DateTimeFormat, and
|
|
317
|
+
// leaving them native was a real hole. QuickJS-ng implements all three without ICU: it accepts
|
|
318
|
+
// the options bag and ignores it, so a bundle asking for a short month got a full numeric date
|
|
319
|
+
// and no error. Measured against node:vm on the sync realm before this existed:
|
|
320
|
+
//
|
|
321
|
+
// toLocaleTimeString("en-US", {hour:"numeric",minute:"2-digit"}) "2:00 AM" vs "02:00:00 AM"
|
|
322
|
+
// toLocaleDateString("en-US", {month:"short"}) "Jan" vs "01/01/1970"
|
|
323
|
+
// toLocaleString("en-US", {dateStyle:"medium"}) "Jan 1, 1970" vs "01/01/1970, 02:00:00 AM"
|
|
324
|
+
//
|
|
325
|
+
// Options are passed through the SAME 'pick(options, DTF)' whitelist the formatter uses, so one
|
|
326
|
+
// option vocabulary serves both paths, and the host applies ECMA-402's own defaulting by calling
|
|
327
|
+
// the very method that was called here.
|
|
328
|
+
//
|
|
329
|
+
// STRICT and receiver-checked for the same reason localeCompare above is: the native methods do
|
|
330
|
+
// their brand check on the receiver, and a sloppy replacement would have 'this' rebound to
|
|
331
|
+
// globalThis and format the current time instead of throwing.
|
|
332
|
+
var toLocale = function (kind) {
|
|
333
|
+
return function (locales, options) {
|
|
334
|
+
"use strict";
|
|
335
|
+
if (!(this instanceof $Date)) {
|
|
336
|
+
throw new $TypeError("Date.prototype.toLocaleString called on a non-Date object");
|
|
337
|
+
}
|
|
338
|
+
var t = $getTime.call(this);
|
|
339
|
+
// An Invalid Date formats as "Invalid Date" on the host too, and NaN cannot cross as a
|
|
340
|
+
// number \u2014 so it is answered here rather than sent, matching what node:vm returns.
|
|
341
|
+
if (t !== t) return "Invalid Date";
|
|
342
|
+
return L(kind, t, encLocales(locales), enc(pick(options, DTF)));
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
$Date.prototype.toLocaleString = toLocale("a");
|
|
346
|
+
$Date.prototype.toLocaleDateString = toLocale("d");
|
|
347
|
+
$Date.prototype.toLocaleTimeString = toLocale("t");
|
|
348
|
+
})();
|
|
349
|
+
`;
|
|
350
|
+
function installIntl(ctx) {
|
|
351
|
+
const parse = (s) => {
|
|
352
|
+
if (!s) return void 0;
|
|
353
|
+
try {
|
|
354
|
+
return JSON.parse(
|
|
355
|
+
s,
|
|
356
|
+
(_k, val) => typeof val === "string" && val.startsWith(NONFINITE_TAG) ? Number(val.slice(NONFINITE_TAG.length)) : val
|
|
357
|
+
);
|
|
358
|
+
} catch {
|
|
359
|
+
return void 0;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
const hooks = [
|
|
363
|
+
[
|
|
364
|
+
"__himiIntlDate",
|
|
365
|
+
(l, o, t) => {
|
|
366
|
+
const fmt = new Intl.DateTimeFormat(
|
|
367
|
+
parse(ctx.getString(l)),
|
|
368
|
+
parse(ctx.getString(o))
|
|
369
|
+
);
|
|
370
|
+
return ctx.newString(fmt.format(new Date(ctx.getNumber(t))));
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
[
|
|
374
|
+
// `Date.prototype.toLocale*` is NOT `Intl.DateTimeFormat`, and missing that was a real defect
|
|
375
|
+
// shipped in #1684: QuickJS-ng implements these three natively with no ICU, so it accepts the
|
|
376
|
+
// options argument and then IGNORES it. Measured against node:vm on the sync realm, before
|
|
377
|
+
// this hook existed:
|
|
378
|
+
//
|
|
379
|
+
// toLocaleTimeString("en-US", {hour:"numeric",minute:"2-digit"})
|
|
380
|
+
// node:vm "2:00 AM" realm "02:00:00 AM"
|
|
381
|
+
// toLocaleDateString("en-US", {month:"short"})
|
|
382
|
+
// node:vm "Jan" realm "01/01/1970"
|
|
383
|
+
//
|
|
384
|
+
// A wrong LABEL, silently — no error, and invisible to a render test, which is the class this
|
|
385
|
+
// whole bridge exists to close. Bundles reach for `toLocaleTimeString` at least as often as
|
|
386
|
+
// for `Intl.DateTimeFormat`, so covering only the latter closed half the hole.
|
|
387
|
+
//
|
|
388
|
+
// The HOST does the defaulting rather than the guest reimplementing ECMA-402's
|
|
389
|
+
// ToDateTimeOptions: node is the reference implementation, so calling the very method the
|
|
390
|
+
// guest called is correct by construction and cannot drift from it.
|
|
391
|
+
"__himiIntlToLocale",
|
|
392
|
+
(k, t, l, o) => {
|
|
393
|
+
const kind = ctx.getString(k);
|
|
394
|
+
const date = new Date(ctx.getNumber(t));
|
|
395
|
+
const locales = parse(ctx.getString(l));
|
|
396
|
+
const options = parse(ctx.getString(o));
|
|
397
|
+
const out = kind === "d" ? date.toLocaleDateString(locales, options) : kind === "t" ? date.toLocaleTimeString(locales, options) : date.toLocaleString(locales, options);
|
|
398
|
+
return ctx.newString(out);
|
|
399
|
+
}
|
|
400
|
+
],
|
|
401
|
+
[
|
|
402
|
+
"__himiIntlNumber",
|
|
403
|
+
(l, o, v, k) => {
|
|
404
|
+
const fmt = new Intl.NumberFormat(
|
|
405
|
+
parse(ctx.getString(l)),
|
|
406
|
+
parse(ctx.getString(o))
|
|
407
|
+
);
|
|
408
|
+
const raw = ctx.getString(v);
|
|
409
|
+
const kind = k === void 0 ? "n" : ctx.getString(k);
|
|
410
|
+
const value = kind === "b" ? BigInt(raw) : kind === "s" ? raw : Number(raw);
|
|
411
|
+
return ctx.newString(fmt.format(value));
|
|
412
|
+
}
|
|
413
|
+
],
|
|
414
|
+
[
|
|
415
|
+
"__himiIntlCompare",
|
|
416
|
+
(a, b, l, o) => {
|
|
417
|
+
const collator = new Intl.Collator(
|
|
418
|
+
parse(ctx.getString(l)),
|
|
419
|
+
parse(o === void 0 ? "" : ctx.getString(o))
|
|
420
|
+
);
|
|
421
|
+
return ctx.newNumber(collator.compare(ctx.getString(a), ctx.getString(b)));
|
|
422
|
+
}
|
|
423
|
+
],
|
|
424
|
+
[
|
|
425
|
+
// Construct-and-discard on the host, purely for its throw. Host Intl validates at
|
|
426
|
+
// CONSTRUCTION; the guest constructors call this so a bad locale or option bag fails at
|
|
427
|
+
// the same moment it would under node:vm, rather than surviving until a format call the
|
|
428
|
+
// crawled path may never reach.
|
|
429
|
+
"__himiIntlValidate",
|
|
430
|
+
(k, l, o) => {
|
|
431
|
+
const locales = parse(ctx.getString(l));
|
|
432
|
+
const options = parse(ctx.getString(o));
|
|
433
|
+
switch (ctx.getString(k)) {
|
|
434
|
+
case "DateTimeFormat":
|
|
435
|
+
new Intl.DateTimeFormat(locales, options);
|
|
436
|
+
break;
|
|
437
|
+
case "NumberFormat":
|
|
438
|
+
new Intl.NumberFormat(locales, options);
|
|
439
|
+
break;
|
|
440
|
+
default:
|
|
441
|
+
new Intl.Collator(locales, options);
|
|
442
|
+
}
|
|
443
|
+
return ctx.undefined;
|
|
444
|
+
}
|
|
445
|
+
]
|
|
446
|
+
];
|
|
447
|
+
for (const [name, fn] of hooks) {
|
|
448
|
+
const handle = ctx.newFunction(name, fn);
|
|
449
|
+
ctx.setProp(ctx.global, name, handle);
|
|
450
|
+
handle.dispose();
|
|
451
|
+
}
|
|
452
|
+
const res = ctx.evalCode(INTL_SHIM, "himi-intl.js");
|
|
453
|
+
if (res.error) {
|
|
454
|
+
const detail = ctx.dump(res.error);
|
|
455
|
+
res.error.dispose();
|
|
456
|
+
throw new Error(`Intl shim failed: ${String(detail?.message ?? detail)}`);
|
|
457
|
+
}
|
|
458
|
+
res.value.dispose();
|
|
459
|
+
}
|
|
460
|
+
export {
|
|
461
|
+
installIntl
|
|
462
|
+
};
|